Fossil SCM

fossil-scm / src / unversioned.c
Blame History Raw 773 lines
1
/*
2
** Copyright (c) 2016 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to implement unversioned file interfaces.
19
*/
20
#include "config.h"
21
#include <assert.h>
22
#include <zlib.h>
23
#include "unversioned.h"
24
#include <time.h>
25
26
/*
27
** SQL code to implement the tables needed by the unversioned.
28
*/
29
static const char zUnversionedInit[] =
30
@ CREATE TABLE IF NOT EXISTS repository.unversioned(
31
@ uvid INTEGER PRIMARY KEY AUTOINCREMENT, -- unique ID for this file
32
@ name TEXT UNIQUE, -- Name of the uv file
33
@ rcvid INTEGER, -- Where received from
34
@ mtime DATETIME, -- timestamp. Seconds since 1970.
35
@ hash TEXT, -- Content hash. NULL if a delete marker
36
@ sz INTEGER, -- size of content after decompression
37
@ encoding INT, -- 0: plaintext. 1: zlib compressed
38
@ content BLOB -- content of the file. NULL if oversized
39
@ );
40
;
41
42
/*
43
** Make sure the unversioned table exists in the repository.
44
*/
45
void unversioned_schema(void){
46
if( !db_table_exists("repository", "unversioned") ){
47
db_multi_exec(zUnversionedInit/*works-like:""*/);
48
}
49
}
50
51
/*
52
** Return a string which is the hash of the unversioned content.
53
** This is the hash used by repositories to compare content before
54
** exchanging a catalog. So all repositories must compute this hash
55
** in exactly the same way.
56
**
57
** If debugFlag is set, force the value to be recomputed and write
58
** the text of the hashed string to stdout.
59
*/
60
const char *unversioned_content_hash(int debugFlag){
61
const char *zHash = debugFlag ? 0 : db_get("uv-hash", 0);
62
if( zHash ) return zHash;
63
if( !db_table_exists("repository","unversioned") ){
64
return "da39a3ee5e6b4b0d3255bfef95601890afd80709";
65
}else{
66
Stmt q;
67
db_prepare(&q,
68
"SELECT printf('%%s %%s %%s\n',name,datetime(mtime,'unixepoch'),hash)"
69
" FROM unversioned"
70
" WHERE hash IS NOT NULL"
71
" ORDER BY name"
72
);
73
while( db_step(&q)==SQLITE_ROW ){
74
const char *z = db_column_text(&q, 0);
75
if( debugFlag ) fossil_print("%s", z);
76
sha1sum_step_text(z,-1);
77
}
78
db_finalize(&q);
79
db_set("uv-hash", sha1sum_finish(0), 0);
80
zHash = db_get("uv-hash",0);
81
}
82
return zHash;
83
}
84
85
/*
86
** Initialize pContent to be the content of an unversioned file zName.
87
**
88
** Return 0 on failures.
89
** Return 1 if the file is found by name.
90
** Return 2 if the file is found by hash.
91
*/
92
int unversioned_content(const char *zName, Blob *pContent){
93
Stmt q;
94
int rc = 0;
95
blob_init(pContent, 0, 0);
96
db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE name=%Q",
97
zName);
98
if( db_step(&q)==SQLITE_ROW ){
99
db_column_blob(&q, 1, pContent);
100
if( db_column_int(&q, 0)==1 ){
101
blob_uncompress(pContent, pContent);
102
}
103
rc = 1;
104
}
105
db_finalize(&q);
106
if( rc==0 && validate16(zName,-1) ){
107
db_prepare(&q, "SELECT encoding, content FROM unversioned WHERE hash=%Q",
108
zName);
109
if( db_step(&q)==SQLITE_ROW ){
110
db_column_blob(&q, 1, pContent);
111
if( db_column_int(&q, 0)==1 ){
112
blob_uncompress(pContent, pContent);
113
}
114
rc = 2;
115
}
116
db_finalize(&q);
117
}
118
return rc;
119
}
120
121
/*
122
** Write unversioned content into the database.
123
*/
124
static void unversioned_write(
125
const char *zUVFile, /* Name of the unversioned file */
126
Blob *pContent, /* File content */
127
sqlite3_int64 mtime /* Modification time */
128
){
129
Stmt ins;
130
Blob compressed;
131
Blob hash;
132
133
db_prepare(&ins,
134
"REPLACE INTO unversioned(name,rcvid,mtime,hash,sz,encoding,content)"
135
" VALUES(:name,:rcvid,:mtime,:hash,:sz,:encoding,:content)"
136
);
137
hname_hash(pContent, 0, &hash);
138
blob_compress(pContent, &compressed);
139
db_bind_text(&ins, ":name", zUVFile);
140
db_bind_int(&ins, ":rcvid", g.rcvid);
141
db_bind_int64(&ins, ":mtime", mtime);
142
db_bind_text(&ins, ":hash", blob_str(&hash));
143
db_bind_int(&ins, ":sz", blob_size(pContent));
144
if( blob_size(&compressed) <= 0.8*blob_size(pContent) ){
145
db_bind_int(&ins, ":encoding", 1);
146
db_bind_blob(&ins, ":content", &compressed);
147
}else{
148
db_bind_int(&ins, ":encoding", 0);
149
db_bind_blob(&ins, ":content", pContent);
150
}
151
db_step(&ins);
152
admin_log("Wrote unversioned file \"%w\" with hash %!S",
153
zUVFile, blob_str(&hash));
154
blob_reset(&compressed);
155
blob_reset(&hash);
156
db_finalize(&ins);
157
db_unset("uv-hash", 0);
158
}
159
160
161
/*
162
** Check the status of unversioned file zName. "mtime" and "zHash" are the
163
** time of last change and hash of a copy of this file on a remote
164
** server. Return an integer status code as follows:
165
**
166
** 0: zName does not exist in the unversioned table.
167
** 1: zName exists and should be replaced by the mtime/zHash remote.
168
** 2: zName exists and is the same as zHash but has an older mtime
169
** 3: zName exists and is identical to mtime/zHash in all respects.
170
** 4: zName exists and is the same as zHash but has a newer mtime.
171
** 5: zName exists and should override the mtime/zHash remote.
172
*/
173
int unversioned_status(
174
const char *zName,
175
sqlite3_int64 mtime,
176
const char *zHash
177
){
178
int iStatus = 0;
179
Stmt q;
180
db_prepare(&q, "SELECT mtime, hash FROM unversioned WHERE name=%Q", zName);
181
if( db_step(&q)==SQLITE_ROW ){
182
const char *zLocalHash = db_column_text(&q, 1);
183
int hashCmp;
184
sqlite3_int64 iLocalMtime = db_column_int64(&q, 0);
185
int mtimeCmp = iLocalMtime<mtime ? -1 : (iLocalMtime==mtime ? 0 : +1);
186
if( zLocalHash==0 ) zLocalHash = "-";
187
hashCmp = strcmp(zLocalHash, zHash);
188
if( hashCmp==0 ){
189
iStatus = 3 + mtimeCmp;
190
}else if( mtimeCmp<0 || (mtimeCmp==0 && hashCmp<0) ){
191
iStatus = 1;
192
}else{
193
iStatus = 5;
194
}
195
}
196
db_finalize(&q);
197
return iStatus;
198
}
199
200
/*
201
** Extract command-line options for the "revert" and "sync" subcommands
202
*/
203
static int unversioned_sync_flags(unsigned syncFlags){
204
if( find_option("verbose","v",0)!=0 ){
205
syncFlags |= SYNC_UV_TRACE | SYNC_VERBOSE;
206
}
207
if( find_option("dry-run","n",0)!=0 ){
208
syncFlags |= SYNC_UV_DRYRUN | SYNC_UV_TRACE | SYNC_VERBOSE;
209
}
210
return syncFlags;
211
}
212
213
/*
214
** Return true if the zName contains any whitespace
215
*/
216
static int contains_whitespace(const char *zName){
217
while( zName[0] ){
218
if( fossil_isspace(zName[0]) ) return 1;
219
zName++;
220
}
221
return 0;
222
}
223
224
/*
225
** COMMAND: uv# abbrv-subcom
226
** COMMAND: unversioned abbrv-subcom
227
**
228
** Usage: %fossil unversioned SUBCOMMAND ARGS...
229
** or: %fossil uv SUBCOMMAND ARGS..
230
**
231
** Unversioned files (UV-files) are artifacts that are synced and are available
232
** for download but which do not preserve history. Only the most recent version
233
** of each UV-file is retained. Changes to an UV-file are permanent and cannot
234
** be undone, so use appropriate caution with this command.
235
**
236
** Subcommands:
237
**
238
** add FILE ... Add or update one or more unversioned files in
239
** the local repository so that they match FILEs
240
** on disk. Changes are not pushed to other
241
** repositories until the next sync.
242
**
243
** add FILE --as UVFILE Add or update a single file named FILE on disk
244
** and UVFILE in the repository unversioned file
245
** namespace. This variant of the 'add' command allows
246
** the name to be different in the repository versus
247
** what appears on disk, but it only allows adding
248
** a single file at a time.
249
**
250
** cat FILE ... Concatenate the content of FILEs to stdout.
251
**
252
** edit FILE Bring up FILE in a text editor for modification.
253
** Options:
254
** --editor NAME Name of the text editor to use
255
**
256
** export FILE OUTPUT Write the content of FILE into OUTPUT on disk
257
**
258
** list | ls Show all unversioned files held in the local
259
** repository.
260
**
261
** Options:
262
** --glob PATTERN Show only files that match
263
** --like PATTERN Show only files that match
264
** -l Show additional details for
265
** files that match. Implied
266
** when 'list' is used.
267
**
268
** revert ?URL? Restore the state of all unversioned files in the
269
** local repository to match the remote repository
270
** URL.
271
**
272
** Options:
273
** -v|--verbose Extra diagnostic output
274
** -n|--dry-run Show what would have happened
275
** --proxy PROXY Use the specified HTTP proxy
276
**
277
** remove|rm|delete FILE ...
278
** Remove unversioned files from the local repository.
279
** Changes are not pushed to other repositories until
280
** the next sync.
281
**
282
** Options:
283
** --glob PATTERN Remove files that match
284
** --like PATTERN Remove files that match
285
**
286
** sync ?URL? Synchronize the state of all unversioned files with
287
** the remote repository URL. The most recent version
288
** of each file is propagated to all repositories and
289
** all prior versions are permanently forgotten.
290
** The remote account requires the 'y' capability.
291
**
292
** Options:
293
** -v|--verbose Extra diagnostic output
294
** -n|--dry-run Show what would have happened
295
** --proxy PROXY Use the specified HTTP proxy
296
**
297
** touch FILE ... Update the TIMESTAMP on all of the listed files
298
**
299
** Options:
300
** --mtime TIMESTAMP Use TIMESTAMP instead of "now" for the "add",
301
** "edit", "remove", and "touch" subcommands.
302
** -R|--repository REPO Use REPO as the repository
303
*/
304
void unversioned_cmd(void){
305
const char *zCmd;
306
int nCmd;
307
const char *zMtime = find_option("mtime", 0, 1);
308
sqlite3_int64 mtime;
309
db_find_and_open_repository(0, 0);
310
unversioned_schema();
311
zCmd = g.argc>=3 ? g.argv[2] : "x";
312
nCmd = (int)strlen(zCmd);
313
if( zMtime==0 ){
314
mtime = time(0);
315
}else{
316
mtime = db_int(0, "SELECT strftime('%%s',%Q)", zMtime);
317
if( mtime<=0 ) fossil_fatal("bad timestamp: %Q", zMtime);
318
}
319
if( strncmp(zCmd, "add", nCmd)==0 ){
320
const char *zError = 0;
321
const char *zIn;
322
const char *zAs;
323
Blob file;
324
int i;
325
i64 mxSize = sqlite3_limit(g.db,SQLITE_LIMIT_LENGTH,-1) - 850;
326
/* Extra space for other fields ------^^^ */
327
/* of the UNVESIONED table row. */
328
329
zAs = find_option("as",0,1);
330
verify_all_options();
331
if( zAs && g.argc!=4 ) usage("add DISKFILE --as UVFILE");
332
db_begin_transaction();
333
content_rcvid_init("#!fossil unversioned add");
334
for(i=3; i<g.argc; i++){
335
zIn = zAs ? zAs : g.argv[i];
336
if( zIn[0]==0 ){
337
zError = "be empty string";
338
}else if( zIn[0]=='/' ){
339
zError = "be absolute";
340
}else if ( !file_is_simple_pathname(zIn,1) ){
341
zError = "contain complex paths";
342
}else if( contains_whitespace(zIn) ){
343
zError = "contain whitespace";
344
}else if( strlen(zIn)>500 ){
345
zError = "be more than 500 bytes long";
346
}
347
if( zError ){
348
fossil_fatal("unversioned filenames may not %s: %Q", zError, zIn);
349
}
350
if( file_size(g.argv[i], ExtFILE)>mxSize ){
351
fossil_fatal("file \"%s\" is too big; max size: %,lld bytes",
352
g.argv[i], mxSize);
353
}
354
blob_init(&file,0,0);
355
blob_read_from_file(&file, g.argv[i], ExtFILE);
356
unversioned_write(zIn, &file, mtime);
357
blob_reset(&file);
358
}
359
db_end_transaction(0);
360
}else if( strncmp(zCmd, "cat", nCmd)==0 ){
361
int i;
362
verify_all_options();
363
db_begin_transaction();
364
for(i=3; i<g.argc; i++){
365
Blob content;
366
if( unversioned_content(g.argv[i], &content)!=0 ){
367
blob_write_to_file(&content, "-");
368
}
369
blob_reset(&content);
370
}
371
db_end_transaction(0);
372
}else if( strncmp(zCmd, "edit", nCmd)==0 ){
373
const char *zEditor; /* Name of the text-editor command */
374
const char *zTFile; /* Temporary file */
375
const char *zUVFile; /* Name of the unversioned file */
376
char *zCmd; /* Command to run the text editor */
377
Blob content; /* Content of the unversioned file */
378
379
zEditor = fossil_text_editor();
380
if( zEditor==0 ){
381
fossil_fatal("no text editor - set the VISUAL env variable");
382
}
383
verify_all_options();
384
if( g.argc!=4) usage("edit UVFILE");
385
zUVFile = g.argv[3];
386
zTFile = fossil_temp_filename();
387
if( zTFile==0 ) fossil_fatal("cannot find a temporary filename");
388
db_begin_transaction();
389
content_rcvid_init("#!fossil unversioned edit");
390
if( unversioned_content(zUVFile, &content)==0 ){
391
fossil_fatal("no such uv-file: %Q", zUVFile);
392
}
393
if( looks_like_binary(&content) ){
394
fossil_fatal("cannot edit binary content");
395
}
396
#if defined(_WIN32) || defined(__CYGWIN__)
397
blob_add_cr(&content);
398
#endif
399
blob_write_to_file(&content, zTFile);
400
zCmd = mprintf("%s %$", zEditor, zTFile);
401
if( fossil_system(zCmd) ){
402
fossil_fatal("editor aborted: %Q", zCmd);
403
}
404
fossil_free(zCmd);
405
blob_reset(&content);
406
blob_read_from_file(&content, zTFile, ExtFILE);
407
#if defined(_WIN32) || defined(__CYGWIN__)
408
blob_to_lf_only(&content);
409
#endif
410
file_delete(zTFile);
411
if( zMtime==0 ) mtime = time(0);
412
unversioned_write(zUVFile, &content, mtime);
413
db_end_transaction(0);
414
blob_reset(&content);
415
}else if( strncmp(zCmd, "export", nCmd)==0 ){
416
Blob content;
417
verify_all_options();
418
if( g.argc!=5 ) usage("export UVFILE OUTPUT");
419
if( unversioned_content(g.argv[3], &content)==0 ){
420
fossil_fatal("no such uv-file: %Q", g.argv[3]);
421
}
422
blob_write_to_file(&content, g.argv[4]);
423
blob_reset(&content);
424
}else if( strncmp(zCmd, "hash", nCmd)==0 ){ /* undocumented */
425
/* Show the hash value used during uv sync */
426
int debugFlag = find_option("debug",0,0)!=0;
427
fossil_print("%s\n", unversioned_content_hash(debugFlag));
428
}else if( strncmp(zCmd, "list", nCmd)==0 || strncmp(zCmd, "ls", nCmd)==0 ){
429
Stmt q;
430
int allFlag = find_option("all","a",0)!=0;
431
int longFlag = find_option("l",0,0)!=0 || (nCmd>1 && zCmd[1]=='i');
432
char *zPattern = sqlite3_mprintf("true");
433
const char *zGlob;
434
zGlob = find_option("glob",0,1);
435
if( zGlob ){
436
sqlite3_free(zPattern);
437
zPattern = sqlite3_mprintf("(name GLOB %Q)", zGlob);
438
}
439
zGlob = find_option("like",0,1);
440
if( zGlob ){
441
sqlite3_free(zPattern);
442
zPattern = sqlite3_mprintf("(name LIKE %Q)", zGlob);
443
}
444
verify_all_options();
445
if( !longFlag ){
446
if( allFlag ){
447
db_prepare(&q, "SELECT name FROM unversioned WHERE %s ORDER BY name",
448
zPattern/*safe-for-%s*/);
449
}else{
450
db_prepare(&q, "SELECT name FROM unversioned"
451
" WHERE %s AND hash IS NOT NULL"
452
" ORDER BY name", zPattern/*safe-for-%s*/);
453
}
454
while( db_step(&q)==SQLITE_ROW ){
455
fossil_print("%s\n", db_column_text(&q,0));
456
}
457
}else{
458
db_prepare(&q,
459
"SELECT hash, datetime(mtime,'unixepoch'), sz, length(content), name"
460
" FROM unversioned WHERE %s"
461
" ORDER BY name;", zPattern/*safe-for-%s*/
462
);
463
while( db_step(&q)==SQLITE_ROW ){
464
const char *zHash = db_column_text(&q, 0);
465
const char *zNoContent = "";
466
if( zHash==0 ){
467
if( !allFlag ) continue;
468
zHash = "(deleted)";
469
}else if( db_column_type(&q,3)==SQLITE_NULL ){
470
zNoContent = " (no content)";
471
}
472
fossil_print("%12.12s %s %8d %8d %s%s\n",
473
zHash,
474
db_column_text(&q,1),
475
db_column_int(&q,2),
476
db_column_int(&q,3),
477
db_column_text(&q,4),
478
zNoContent
479
);
480
}
481
}
482
db_finalize(&q);
483
sqlite3_free(zPattern);
484
}else if( strncmp(zCmd, "revert", nCmd)==0 ){
485
unsigned syncFlags =
486
unversioned_sync_flags(SYNC_UNVERSIONED|SYNC_UV_REVERT);
487
g.argv[1] = "sync";
488
g.argv[2] = "--uv-noop";
489
sync_unversioned(syncFlags);
490
}else if( strncmp(zCmd, "remove", nCmd)==0 || strncmp(zCmd, "rm", nCmd)==0
491
|| strncmp(zCmd, "delete", nCmd)==0 ){
492
int i;
493
const char *zGlob;
494
db_begin_transaction();
495
while( (zGlob = find_option("glob",0,1))!=0 ){
496
db_multi_exec(
497
"UPDATE unversioned"
498
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name GLOB %Q",
499
mtime, zGlob
500
);
501
}
502
while( (zGlob = find_option("like",0,1))!=0 ){
503
db_multi_exec(
504
"UPDATE unversioned"
505
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name LIKE %Q",
506
mtime, zGlob
507
);
508
}
509
verify_all_options();
510
for(i=3; i<g.argc; i++){
511
db_multi_exec(
512
"UPDATE unversioned"
513
" SET hash=NULL, content=NULL, mtime=%lld, sz=0 WHERE name=%Q",
514
mtime, g.argv[i]
515
);
516
}
517
db_unset("uv-hash", 0);
518
db_end_transaction(0);
519
}else if( strncmp(zCmd,"sync",nCmd)==0 ){
520
unsigned syncFlags = unversioned_sync_flags(SYNC_UNVERSIONED);
521
g.argv[1] = "sync";
522
g.argv[2] = "--uv-noop";
523
sync_unversioned(syncFlags);
524
}else if( strncmp(zCmd, "touch", nCmd)==0 ){
525
int i;
526
verify_all_options();
527
db_begin_transaction();
528
for(i=3; i<g.argc; i++){
529
db_multi_exec(
530
"UPDATE unversioned SET mtime=%lld WHERE name=%Q",
531
mtime, g.argv[i]
532
);
533
}
534
db_unset("uv-hash", 0);
535
db_end_transaction(0);
536
}else{
537
usage("add|cat|edit|export|list|revert|remove|sync|touch");
538
}
539
}
540
541
/*
542
** Emit an HTML form for uploading a new unversioned file if
543
** the current user has WrUnver permissions, else this is
544
** a no-op.
545
**
546
** If this function detects that the form it emits has been submitted,
547
** it will add the uploaded file to the unversioned file list before
548
** returning.
549
**
550
** Intended only for use by /uvlist, and its form's action is that
551
** page.
552
*/
553
static void uvlist_upload(void){
554
const char * aContent;
555
if( !g.perm.WrUnver ) return;
556
aContent = P("f");
557
if( aContent!=0 ){
558
const char * const zName = P("f:filename");
559
int const nContent = atoi(PD("f:bytes","0"));
560
const char * zError = 0;
561
Blob content;
562
if( zName[0]==0 ){
563
zError = "be an empty string";
564
}else if( contains_whitespace(zName) ){
565
zError = "contain spaces";
566
}
567
if( zError ){
568
fossil_fatal("Unversioned filenames may not %s: %h",
569
zError, zName);
570
}
571
unversioned_schema();
572
db_begin_transaction();
573
content_rcvid_init("#!fossil /uvlist upload");
574
blob_init(&content, aContent, nContent);
575
unversioned_write(zName, &content, time(0));
576
blob_reset(&content);
577
db_end_transaction(0);
578
CX("<div>Added: %h</div>", zName);
579
}
580
form_begin("enctype='multipart/form-data'", "%R/uvlist");
581
@ <label for='uvupload'>Upload unversioned file:</label>
582
@ <input type='file' id='uvupload' name='f'/>
583
@ <input type='submit' id='uvsubmit' value='Upload' disabled='disabled'/>
584
@ </form>
585
@ <script nonce='%h(style_nonce())'>;/* unversioned.c:%d(__LINE__) */
586
@ var upl = document.getElementById('uvupload');
587
@ var sbm = document.getElementById('uvsubmit');
588
@ upl.onchange = function(){
589
@ if (!upl.value) sbm.setAttribute('disabled', 'disabled');
590
@ else sbm.removeAttribute('disabled');
591
@ }
592
@ </script>
593
}
594
595
/*
596
** WEBPAGE: uvlist
597
**
598
** Display a list of all unversioned files in the repository.
599
** Query parameters:
600
**
601
** byage=1 Order the initial display be decreasing age
602
** showdel=0 Show deleted files
603
*/
604
void uvlist_page(void){
605
Stmt q;
606
sqlite3_int64 iNow;
607
sqlite3_int64 iTotalSz = 0;
608
int cnt = 0;
609
int n = 0;
610
const char *zOrderBy = "name";
611
int showDel = 0;
612
char zSzName[100];
613
614
login_check_credentials();
615
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
616
cgi_check_for_malice();
617
etag_check(ETAG_DATA,0);
618
style_header("Unversioned Files");
619
uvlist_upload();
620
if( !db_table_exists("repository","unversioned") ){
621
@ No unversioned files on this server
622
style_finish_page();
623
return;
624
}
625
if( PB("byage") ) zOrderBy = "mtime DESC";
626
if( PB("showdel") ) showDel = 1;
627
db_prepare(&q,
628
"SELECT"
629
" name,"
630
" mtime,"
631
" hash,"
632
" sz,"
633
" (SELECT login FROM rcvfrom, user"
634
" WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid),"
635
" rcvid"
636
" FROM unversioned %s ORDER BY %s",
637
showDel ? "" : "WHERE hash IS NOT NULL" /*safe-for-%s*/,
638
zOrderBy/*safe-for-%s*/
639
);
640
iNow = db_int64(0, "SELECT strftime('%%s','now');");
641
while( db_step(&q)==SQLITE_ROW ){
642
const char *zName = db_column_text(&q, 0);
643
sqlite3_int64 mtime = db_column_int(&q, 1);
644
const char *zHash = db_column_text(&q, 2);
645
int isDeleted = zHash==0;
646
const char *zAlgo;
647
int fullSize = db_column_int(&q, 3);
648
char *zAge = human_readable_age((iNow - mtime)/86400.0);
649
const char *zLogin = db_column_text(&q, 4);
650
int rcvid = db_column_int(&q,5);
651
if( isDeleted ) zAlgo = "deleted";
652
else zAlgo = hname_alg(strlen(zHash));
653
if( zLogin==0 ) zLogin = "";
654
if( (n++)==0 ){
655
style_table_sorter();
656
@ <div class="uvlist">
657
@ <table cellpadding="2" cellspacing="0" border="1" class='sortable' \
658
@ data-column-types='tkKttn' data-init-sort='1'>
659
@ <thead><tr>
660
@ <th> Name
661
@ <th> Age
662
@ <th> Size
663
@ <th> User
664
@ <th> Hash
665
@ <th> Algo
666
if( g.perm.Admin ){
667
@ <th> rcvid
668
}
669
@ </tr></thead>
670
@ <tbody>
671
}
672
@ <tr>
673
if( isDeleted ){
674
sqlite3_snprintf(sizeof(zSzName), zSzName, "<i>Deleted</i>");
675
zHash = "";
676
fullSize = 0;
677
@ <td> %h(zName) </td>
678
}else{
679
approxSizeName(sizeof(zSzName), zSzName, fullSize);
680
iTotalSz += fullSize;
681
cnt++;
682
@ <td> <a href='%R/uv/%T(zName)'>%h(zName)</a> </td>
683
}
684
@ <td data-sortkey='%016llx(-mtime)'> %s(zAge) </td>
685
@ <td data-sortkey='%08x(fullSize)'> %s(zSzName) </td>
686
@ <td> %h(zLogin) </td>
687
@ <td><code> %h(zHash) </code></td>
688
@ <td> %s(zAlgo) </td>
689
if( g.perm.Admin ){
690
if( rcvid ){
691
@ <td> <a href="%R/rcvfrom?rcvid=%d(rcvid)">%d(rcvid)</a>
692
}else{
693
@ <td>
694
}
695
}
696
@ </tr>
697
fossil_free(zAge);
698
}
699
db_finalize(&q);
700
if( n ){
701
approxSizeName(sizeof(zSzName), zSzName, iTotalSz);
702
@ </tbody>
703
@ <tfoot><tr><td><b>Total for %d(cnt) files</b><td><td>%s(zSzName)
704
@ <td><td>
705
if( g.perm.Admin ){
706
@ <td>
707
}
708
@ <td>
709
@ </tfoot>
710
@ </table></div>
711
}else{
712
@ No unversioned files on this server.
713
}
714
style_finish_page();
715
}
716
717
/*
718
** WEBPAGE: juvlist
719
**
720
** Return a complete list of unversioned files as JSON. The JSON
721
** looks like this:
722
**
723
** [{"name":NAME,
724
** "mtime":MTIME,
725
** "hash":HASH,
726
** "size":SIZE,
727
** "user":USER}]
728
*/
729
void uvlist_json_page(void){
730
Stmt q;
731
char *zSep = "[";
732
Blob json;
733
734
login_check_credentials();
735
if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
736
cgi_check_for_malice();
737
cgi_set_content_type("application/json");
738
etag_check(ETAG_DATA,0);
739
if( !db_table_exists("repository","unversioned") ){
740
blob_init(&json, "[]", -1);
741
cgi_set_content(&json);
742
return;
743
}
744
blob_init(&json, 0, 0);
745
db_prepare(&q,
746
"SELECT"
747
" name,"
748
" mtime,"
749
" hash,"
750
" sz,"
751
" (SELECT login FROM rcvfrom, user"
752
" WHERE user.uid=rcvfrom.uid AND rcvfrom.rcvid=unversioned.rcvid)"
753
" FROM unversioned WHERE hash IS NOT NULL"
754
);
755
while( db_step(&q)==SQLITE_ROW ){
756
const char *zName = db_column_text(&q, 0);
757
sqlite3_int64 mtime = db_column_int(&q, 1);
758
const char *zHash = db_column_text(&q, 2);
759
int fullSize = db_column_int(&q, 3);
760
const char *zLogin = db_column_text(&q, 4);
761
if( zLogin==0 ) zLogin = "";
762
blob_appendf(&json, "%s{\"name\":\"%j\",\n", zSep, zName);
763
zSep = ",\n ";
764
blob_appendf(&json, " \"mtime\":%lld,\n", mtime);
765
blob_appendf(&json, " \"hash\":\"%j\",\n", zHash);
766
blob_appendf(&json, " \"size\":%d,\n", fullSize);
767
blob_appendf(&json, " \"user\":\"%j\"}", zLogin);
768
}
769
db_finalize(&q);
770
blob_appendf(&json,"]\n");
771
cgi_set_content(&json);
772
}
773

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button