Fossil SCM

merge trunk

jan.nijtmans 2014-01-02 15:17 UTC timeline-utc merge
Commit d5fd6cc1a5662e2779c3b6525dfc0bafd6e2b3d2
+332 -24
--- src/browse.c
+++ src/browse.c
@@ -71,23 +71,29 @@
7171
** There is no hyperlink on the file element of the path.
7272
**
7373
** The computed string is appended to the pOut blob. pOut should
7474
** have already been initialized.
7575
*/
76
-void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
76
+void hyperlinked_path(
77
+ const char *zPath, /* Path to render */
78
+ Blob *pOut, /* Write into this blob */
79
+ const char *zCI, /* check-in name, or NULL */
80
+ const char *zURI, /* "dir" or "tree" */
81
+ const char *zREx /* Extra query parameters */
82
+){
7783
int i, j;
7884
char *zSep = "";
7985
8086
for(i=0; zPath[i]; i=j){
8187
for(j=i; zPath[j] && zPath[j]!='/'; j++){}
8288
if( zPath[j] && g.perm.Hyperlink ){
8389
if( zCI ){
84
- char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath);
90
+ char *zLink = href("%R/%s?ci=%S&name=%#T%s", zURI, zCI, j, zPath,zREx);
8591
blob_appendf(pOut, "%s%z%#h</a>",
8692
zSep, zLink, j-i, &zPath[i]);
8793
}else{
88
- char *zLink = href("%R/dir?name=%#T", j, zPath);
94
+ char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
8995
blob_appendf(pOut, "%s%z%#h</a>",
9096
zSep, zLink, j-i, &zPath[i]);
9197
}
9298
}else{
9399
blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
@@ -101,11 +107,11 @@
101107
/*
102108
** WEBPAGE: dir
103109
**
104110
** Query parameters:
105111
**
106
-** name=PATH Directory to display. Required.
112
+** name=PATH Directory to display. Optional. Top-level if missing
107113
** ci=LABEL Show only files in this check-in. Optional.
108114
*/
109115
void page_dir(void){
110116
char *zD = fossil_strdup(P("name"));
111117
int nD = zD ? strlen(zD)+1 : 0;
@@ -118,18 +124,22 @@
118124
int rid = 0;
119125
char *zUuid = 0;
120126
Blob dirname;
121127
Manifest *pM = 0;
122128
const char *zSubdirLink;
123
- int linkTrunk = 1, linkTip = 1;
129
+ int linkTrunk = 1;
130
+ int linkTip = 1;
131
+ HQuery sURI;
124132
133
+ if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
125134
login_check_credentials();
126135
if( !g.perm.Read ){ login_needed(); return; }
127136
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
128137
style_header("File List");
129138
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
130139
pathelementFunc, 0, 0);
140
+ url_initialize(&sURI, "dir");
131141
132142
/* If the name= parameter is an empty string, make it a NULL pointer */
133143
if( zD && strlen(zD)==0 ){ zD = 0; }
134144
135145
/* If a specific check-in is requested, fetch and parse it. If the
@@ -141,58 +151,57 @@
141151
if( pM ){
142152
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
143153
linkTrunk = trunkRid && rid != trunkRid;
144154
linkTip = rid != symbolic_name_to_rid("tip", "ci");
145155
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
156
+ url_add_parameter(&sURI, "ci", zCI);
146157
}else{
147158
zCI = 0;
148159
}
149160
}
150161
151162
/* Compute the title of the page */
152163
blob_zero(&dirname);
153164
if( zD ){
165
+ url_add_parameter(&sURI, "name", zD);
154166
blob_append(&dirname, "in directory ", -1);
155
- hyperlinked_path(zD, &dirname, zCI);
167
+ hyperlinked_path(zD, &dirname, zCI, "dir", "");
156168
zPrefix = mprintf("%s/", zD);
157
- if( linkTrunk ){
158
- style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk",
159
- zD);
160
- }
161
- if ( linkTip ){
162
- style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD);
163
- }
169
+ style_submenu_element("Top-Level", "Top-Level", "%s",
170
+ url_render(&sURI, "name", 0, 0, 0));
164171
}else{
165172
blob_append(&dirname, "in the top-level directory", -1);
166173
zPrefix = "";
167
- if( linkTrunk ){
168
- style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk");
169
- }
170
- if ( linkTip ){
171
- style_submenu_element("Tip", "Tip", "%R/dir?ci=tip");
172
- }
174
+ }
175
+ if( linkTrunk ){
176
+ style_submenu_element("Trunk", "Trunk", "%s",
177
+ url_render(&sURI, "ci", "trunk", 0, 0));
178
+ }
179
+ if( linkTip ){
180
+ style_submenu_element("Tip", "Tip", "%s",
181
+ url_render(&sURI, "ci", "tip", 0, 0));
173182
}
174183
if( zCI ){
175184
char zShort[20];
176185
memcpy(zShort, zUuid, 10);
177186
zShort[10] = 0;
178187
@ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
179188
@ %s(blob_str(&dirname))</h2>
180189
zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
181
- if( zD ){
182
- style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid);
183
- style_submenu_element("All", "All", "%R/dir?name=%t", zD);
184
- }else{
185
- style_submenu_element("All", "All", "%R/dir");
190
+ if( nD==0 ){
186191
style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
187192
zUuid);
188193
}
189194
}else{
190195
@ <h2>The union of all files from all check-ins
191196
@ %s(blob_str(&dirname))</h2>
192197
zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
193198
}
199
+ style_submenu_element("All", "All", "%s",
200
+ url_render(&sURI, "ci", 0, 0, 0));
201
+ style_submenu_element("Tree-View", "Tree-View", "%s",
202
+ url_render(&sURI, "type", "tree", 0, 0));
194203
195204
/* Compute the temporary table "localfiles" containing the names
196205
** of all files and subdirectories in the zD[] directory.
197206
**
198207
** Subdirectory names begin with "/". This causes them to sort
@@ -287,10 +296,309 @@
287296
db_finalize(&q);
288297
manifest_destroy(pM);
289298
@ </ul></td></tr></table>
290299
style_footer();
291300
}
301
+
302
+/*
303
+** Objects used by the "tree" webpage.
304
+*/
305
+typedef struct FileTreeNode FileTreeNode;
306
+typedef struct FileTree FileTree;
307
+
308
+/*
309
+** A single line of the file hierarchy
310
+*/
311
+struct FileTreeNode {
312
+ FileTreeNode *pNext; /* Next line in sequence */
313
+ FileTreeNode *pPrev; /* Previous line */
314
+ FileTreeNode *pParent; /* Directory containing this line */
315
+ char *zName; /* Name of this entry. The "tail" */
316
+ char *zFullName; /* Full pathname of this entry */
317
+ char *zUuid; /* SHA1 hash of this file. May be NULL. */
318
+ unsigned nFullName; /* Length of zFullName */
319
+ unsigned iLevel; /* Levels of parent directories */
320
+ u8 isDir; /* True if there are children */
321
+ u8 isLast; /* True if this is the last child of its parent */
322
+};
323
+
324
+/*
325
+** A complete file hierarchy
326
+*/
327
+struct FileTree {
328
+ FileTreeNode *pFirst; /* First line of the list */
329
+ FileTreeNode *pLast; /* Last line of the list */
330
+};
331
+
332
+/*
333
+** Add one or more new FileTreeNodes to the FileTree object so that the
334
+** leaf object zPathname is at the end of the node list
335
+*/
336
+static void tree_add_node(
337
+ FileTree *pTree, /* Tree into which nodes are added */
338
+ const char *zPath, /* The full pathname of file to add */
339
+ const char *zUuid /* UUID of the file. Might be NULL. */
340
+){
341
+ int i;
342
+ FileTreeNode *pParent;
343
+ FileTreeNode *pChild;
344
+
345
+ pChild = pTree->pLast;
346
+ pParent = pChild ? pChild->pParent : 0;
347
+ while( pParent!=0 &&
348
+ ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0
349
+ || zPath[pParent->nFullName]!='/' )
350
+ ){
351
+ pChild = pParent;
352
+ pParent = pChild->pParent;
353
+ }
354
+ i = pParent ? pParent->nFullName+1 : 0;
355
+ if( pChild ) pChild->isLast = 0;
356
+ while( zPath[i] ){
357
+ FileTreeNode *pNew;
358
+ int iStart = i;
359
+ int nByte;
360
+ while( zPath[i] && zPath[i]!='/' ){ i++; }
361
+ nByte = sizeof(*pNew) + i + 1;
362
+ if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1;
363
+ pNew = fossil_malloc( nByte );
364
+ pNew->zFullName = (char*)&pNew[1];
365
+ memcpy(pNew->zFullName, zPath, i);
366
+ pNew->zFullName[i] = 0;
367
+ pNew->nFullName = i;
368
+ if( zUuid!=0 && zPath[i]==0 ){
369
+ pNew->zUuid = pNew->zFullName + i + 1;
370
+ memcpy(pNew->zUuid, zUuid, UUID_SIZE+1);
371
+ }else{
372
+ pNew->zUuid = 0;
373
+ }
374
+ pNew->zName = pNew->zFullName + iStart;
375
+ if( pTree->pLast ){
376
+ pTree->pLast->pNext = pNew;
377
+ }else{
378
+ pTree->pFirst = pNew;
379
+ }
380
+ pNew->pPrev = pTree->pLast;
381
+ pNew->pNext = 0;
382
+ pNew->pParent = pParent;
383
+ pTree->pLast = pNew;
384
+ pNew->iLevel = pParent ? pParent->iLevel+1 : 0;
385
+ pNew->isDir = zPath[i]=='/';
386
+ pNew->isLast = 1;
387
+ while( zPath[i]=='/' ){ i++; }
388
+ pParent = pNew;
389
+ }
390
+}
391
+
392
+/*
393
+** Render parent lines for pNode
394
+*/
395
+static void tree_indentation(FileTreeNode *p){
396
+ if( p==0 ) return;
397
+ tree_indentation(p->pParent);
398
+ if( p->isLast ){
399
+ cgi_append_content(" ", 4);
400
+ }else{
401
+ cgi_append_content("&#x2502; ", 11);
402
+ }
403
+}
404
+
405
+
406
+/*
407
+** WEBPAGE: tree
408
+**
409
+** Query parameters:
410
+**
411
+** name=PATH Directory to display. Optional
412
+** ci=LABEL Show only files in this check-in. Optional.
413
+** re=REGEXP Show only files matching REGEXP. Optional.
414
+*/
415
+void page_tree(void){
416
+ char *zD = fossil_strdup(P("name"));
417
+ int nD = zD ? strlen(zD)+1 : 0;
418
+ const char *zCI = P("ci");
419
+ int rid = 0;
420
+ char *zUuid = 0;
421
+ Blob dirname;
422
+ Manifest *pM = 0;
423
+ int nFile = 0; /* Number of files */
424
+ int linkTrunk = 1; /* include link to "trunk" */
425
+ int linkTip = 1; /* include link to "tip" */
426
+ const char *zRE; /* the value for the re=REGEXP query parameter */
427
+ char *zPrefix; /* Prefix on all filenames */
428
+ char *zREx = ""; /* Extra parameters for path hyperlinks */
429
+ ReCompiled *pRE = 0; /* Compiled regular expression */
430
+ FileTreeNode *p; /* One line of the tree */
431
+ FileTree sTree; /* The complete tree of files */
432
+ HQuery sURI; /* Hyperlink */
433
+
434
+ if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
435
+ memset(&sTree, 0, sizeof(sTree));
436
+ login_check_credentials();
437
+ if( !g.perm.Read ){ login_needed(); return; }
438
+ while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
439
+ style_header("File List");
440
+ sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
441
+ pathelementFunc, 0, 0);
442
+ url_initialize(&sURI, "tree");
443
+
444
+ /* If a regular expression is specified, compile it */
445
+ zRE = P("re");
446
+ if( zRE ){
447
+ re_compile(&pRE, zRE, 0);
448
+ url_add_parameter(&sURI, "re", zRE);
449
+ zREx = mprintf("&re=%T", zRE);
450
+ }
451
+
452
+ /* If the name= parameter is an empty string, make it a NULL pointer */
453
+ if( zD && strlen(zD)==0 ){ zD = 0; }
454
+
455
+ /* If a specific check-in is requested, fetch and parse it. If the
456
+ ** specific check-in does not exist, clear zCI. zCI==0 will cause all
457
+ ** files from all check-ins to be displayed.
458
+ */
459
+ if( zCI ){
460
+ pM = manifest_get_by_name(zCI, &rid);
461
+ if( pM ){
462
+ int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
463
+ linkTrunk = trunkRid && rid != trunkRid;
464
+ linkTip = rid != symbolic_name_to_rid("tip", "ci");
465
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
466
+ url_add_parameter(&sURI, "ci", zCI);
467
+ }else{
468
+ zCI = 0;
469
+ }
470
+ }
471
+
472
+ /* Compute the title of the page */
473
+ blob_zero(&dirname);
474
+ if( zD ){
475
+ url_add_parameter(&sURI, "name", zD);
476
+ blob_append(&dirname, "within directory ", -1);
477
+ hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
478
+ if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
479
+ zPrefix = mprintf("%T/", zD);
480
+ style_submenu_element("Top-Level", "Top-Level", "%s",
481
+ url_render(&sURI, "name", 0, 0, 0));
482
+ }else{
483
+ if( zRE ){
484
+ blob_appendf(&dirname, "matching \"%s\"", zRE);
485
+ }
486
+ zPrefix = "";
487
+ }
488
+ if( zCI ){
489
+ style_submenu_element("All", "All", "%s",
490
+ url_render(&sURI, "ci", 0, 0, 0));
491
+ }
492
+ if( linkTrunk ){
493
+ style_submenu_element("Trunk", "Trunk", "%s",
494
+ url_render(&sURI, "ci", "trunk", 0, 0));
495
+ }
496
+ if ( linkTip ){
497
+ style_submenu_element("Tip", "Tip", "%s",
498
+ url_render(&sURI, "ci", "tip", 0, 0));
499
+ }
500
+ style_submenu_element("Flat-View", "Flat-View", "%s",
501
+ url_render(&sURI, "type", "flat", 0, 0));
502
+ /* Compute the file hierarchy.
503
+ */
504
+ if( zCI ){
505
+ Stmt ins, q;
506
+ ManifestFile *pFile;
507
+
508
+ db_multi_exec(
509
+ "CREATE TEMP TABLE filelist("
510
+ " x TEXT PRIMARY KEY COLLATE nocase,"
511
+ " uuid TEXT"
512
+ ") WITHOUT ROWID;"
513
+ );
514
+ db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)");
515
+ manifest_file_rewind(pM);
516
+ while( (pFile = manifest_file_next(pM,0))!=0 ){
517
+ if( nD>0
518
+ && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
519
+ || pFile->zName[nD-1]!='/')
520
+ ){
521
+ continue;
522
+ }
523
+ if( pRE && re_match(pRE, (const u8*)pFile->zName, -1)==0 ) continue;
524
+ db_bind_text(&ins, ":f", &pFile->zName[nD]);
525
+ db_bind_text(&ins, ":u", pFile->zUuid);
526
+ db_step(&ins);
527
+ db_reset(&ins);
528
+ }
529
+ db_finalize(&ins);
530
+ db_prepare(&q, "SELECT x, uuid FROM filelist ORDER BY x");
531
+ while( db_step(&q)==SQLITE_ROW ){
532
+ tree_add_node(&sTree, db_column_text(&q,0), db_column_text(&q,1));
533
+ nFile++;
534
+ }
535
+ db_finalize(&q);
536
+ }else{
537
+ Stmt q;
538
+ db_prepare(&q, "SELECT name FROM filename ORDER BY name COLLATE nocase");
539
+ while( db_step(&q)==SQLITE_ROW ){
540
+ const char *z = db_column_text(&q, 0);
541
+ if( nD>0 && (fossil_strncmp(z, zD, nD-1)!=0 || z[nD-1]!='/') ){
542
+ continue;
543
+ }
544
+ if( pRE && re_match(pRE, (const u8*)z, -1)==0 ) continue;
545
+ tree_add_node(&sTree, z+nD, 0);
546
+ nFile++;
547
+ }
548
+ db_finalize(&q);
549
+ }
550
+
551
+ if( zCI ){
552
+ @ <h2>%d(nFile) files of
553
+ @ check-in [%z(href("vinfo?name=%T",zUuid))%S(zUuid)</a>]
554
+ @ %s(blob_str(&dirname))</h2>
555
+ }else{
556
+ int n = db_int(0, "SELECT count(*) FROM plink");
557
+ @ <h2>%d(nFile) files from all %d(n) check-ins
558
+ @ %s(blob_str(&dirname))</h2>
559
+ }
560
+
561
+
562
+ /* Generate a multi-column table listing the contents of zD[]
563
+ ** directory.
564
+ */
565
+ @ <pre>
566
+ if( nD ){
567
+ cgi_printf("%.*h\n", nD, zD);
568
+ }else{
569
+ @ .
570
+ }
571
+ for(p=sTree.pFirst; p; p=p->pNext){
572
+ tree_indentation(p->pParent);
573
+ if( p->isLast ){
574
+ cgi_append_content("&#x2514;&#x2500;&#x2500; ", 25);
575
+ }else{
576
+ cgi_append_content("&#x251c;&#x2500;&#x2500; ", 25);
577
+ }
578
+ if( p->isDir ){
579
+ char *zName = mprintf("%s%T", zPrefix, p->zFullName);
580
+ char *zLink = href("%s", url_render(&sURI, "name", zName, 0, 0));
581
+ fossil_free(zName);
582
+ @ %z(zLink)%h(p->zName)</a>
583
+ }else{
584
+ char *zLink;
585
+ if( zCI ){
586
+ zLink = href("%R/artifact/%s",p->zUuid);
587
+ }else{
588
+ zLink = href("%R/finfo?name=%s%T",zPrefix,p->zFullName);
589
+ }
590
+ @ %z(zLink)%h(p->zName)</a>
591
+ }
592
+ }
593
+ @ </pre>
594
+ style_footer();
595
+
596
+ /* We could free memory used by sTree here if we needed to. But
597
+ ** the process is about to exit, so doing so would not really accomplish
598
+ ** anything useful. */
599
+}
292600
293601
/*
294602
** Return a CSS class name based on the given filename's extension.
295603
** Result must be freed by the caller.
296604
**/
297605
--- src/browse.c
+++ src/browse.c
@@ -71,23 +71,29 @@
71 ** There is no hyperlink on the file element of the path.
72 **
73 ** The computed string is appended to the pOut blob. pOut should
74 ** have already been initialized.
75 */
76 void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
 
 
 
 
 
 
77 int i, j;
78 char *zSep = "";
79
80 for(i=0; zPath[i]; i=j){
81 for(j=i; zPath[j] && zPath[j]!='/'; j++){}
82 if( zPath[j] && g.perm.Hyperlink ){
83 if( zCI ){
84 char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath);
85 blob_appendf(pOut, "%s%z%#h</a>",
86 zSep, zLink, j-i, &zPath[i]);
87 }else{
88 char *zLink = href("%R/dir?name=%#T", j, zPath);
89 blob_appendf(pOut, "%s%z%#h</a>",
90 zSep, zLink, j-i, &zPath[i]);
91 }
92 }else{
93 blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
@@ -101,11 +107,11 @@
101 /*
102 ** WEBPAGE: dir
103 **
104 ** Query parameters:
105 **
106 ** name=PATH Directory to display. Required.
107 ** ci=LABEL Show only files in this check-in. Optional.
108 */
109 void page_dir(void){
110 char *zD = fossil_strdup(P("name"));
111 int nD = zD ? strlen(zD)+1 : 0;
@@ -118,18 +124,22 @@
118 int rid = 0;
119 char *zUuid = 0;
120 Blob dirname;
121 Manifest *pM = 0;
122 const char *zSubdirLink;
123 int linkTrunk = 1, linkTip = 1;
 
 
124
 
125 login_check_credentials();
126 if( !g.perm.Read ){ login_needed(); return; }
127 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
128 style_header("File List");
129 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
130 pathelementFunc, 0, 0);
 
131
132 /* If the name= parameter is an empty string, make it a NULL pointer */
133 if( zD && strlen(zD)==0 ){ zD = 0; }
134
135 /* If a specific check-in is requested, fetch and parse it. If the
@@ -141,58 +151,57 @@
141 if( pM ){
142 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
143 linkTrunk = trunkRid && rid != trunkRid;
144 linkTip = rid != symbolic_name_to_rid("tip", "ci");
145 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
 
146 }else{
147 zCI = 0;
148 }
149 }
150
151 /* Compute the title of the page */
152 blob_zero(&dirname);
153 if( zD ){
 
154 blob_append(&dirname, "in directory ", -1);
155 hyperlinked_path(zD, &dirname, zCI);
156 zPrefix = mprintf("%s/", zD);
157 if( linkTrunk ){
158 style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk",
159 zD);
160 }
161 if ( linkTip ){
162 style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD);
163 }
164 }else{
165 blob_append(&dirname, "in the top-level directory", -1);
166 zPrefix = "";
167 if( linkTrunk ){
168 style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk");
169 }
170 if ( linkTip ){
171 style_submenu_element("Tip", "Tip", "%R/dir?ci=tip");
172 }
 
 
173 }
174 if( zCI ){
175 char zShort[20];
176 memcpy(zShort, zUuid, 10);
177 zShort[10] = 0;
178 @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
179 @ %s(blob_str(&dirname))</h2>
180 zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
181 if( zD ){
182 style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid);
183 style_submenu_element("All", "All", "%R/dir?name=%t", zD);
184 }else{
185 style_submenu_element("All", "All", "%R/dir");
186 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
187 zUuid);
188 }
189 }else{
190 @ <h2>The union of all files from all check-ins
191 @ %s(blob_str(&dirname))</h2>
192 zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
193 }
 
 
 
 
194
195 /* Compute the temporary table "localfiles" containing the names
196 ** of all files and subdirectories in the zD[] directory.
197 **
198 ** Subdirectory names begin with "/". This causes them to sort
@@ -287,10 +296,309 @@
287 db_finalize(&q);
288 manifest_destroy(pM);
289 @ </ul></td></tr></table>
290 style_footer();
291 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
293 /*
294 ** Return a CSS class name based on the given filename's extension.
295 ** Result must be freed by the caller.
296 **/
297
--- src/browse.c
+++ src/browse.c
@@ -71,23 +71,29 @@
71 ** There is no hyperlink on the file element of the path.
72 **
73 ** The computed string is appended to the pOut blob. pOut should
74 ** have already been initialized.
75 */
76 void hyperlinked_path(
77 const char *zPath, /* Path to render */
78 Blob *pOut, /* Write into this blob */
79 const char *zCI, /* check-in name, or NULL */
80 const char *zURI, /* "dir" or "tree" */
81 const char *zREx /* Extra query parameters */
82 ){
83 int i, j;
84 char *zSep = "";
85
86 for(i=0; zPath[i]; i=j){
87 for(j=i; zPath[j] && zPath[j]!='/'; j++){}
88 if( zPath[j] && g.perm.Hyperlink ){
89 if( zCI ){
90 char *zLink = href("%R/%s?ci=%S&name=%#T%s", zURI, zCI, j, zPath,zREx);
91 blob_appendf(pOut, "%s%z%#h</a>",
92 zSep, zLink, j-i, &zPath[i]);
93 }else{
94 char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
95 blob_appendf(pOut, "%s%z%#h</a>",
96 zSep, zLink, j-i, &zPath[i]);
97 }
98 }else{
99 blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
@@ -101,11 +107,11 @@
107 /*
108 ** WEBPAGE: dir
109 **
110 ** Query parameters:
111 **
112 ** name=PATH Directory to display. Optional. Top-level if missing
113 ** ci=LABEL Show only files in this check-in. Optional.
114 */
115 void page_dir(void){
116 char *zD = fossil_strdup(P("name"));
117 int nD = zD ? strlen(zD)+1 : 0;
@@ -118,18 +124,22 @@
124 int rid = 0;
125 char *zUuid = 0;
126 Blob dirname;
127 Manifest *pM = 0;
128 const char *zSubdirLink;
129 int linkTrunk = 1;
130 int linkTip = 1;
131 HQuery sURI;
132
133 if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
134 login_check_credentials();
135 if( !g.perm.Read ){ login_needed(); return; }
136 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
137 style_header("File List");
138 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
139 pathelementFunc, 0, 0);
140 url_initialize(&sURI, "dir");
141
142 /* If the name= parameter is an empty string, make it a NULL pointer */
143 if( zD && strlen(zD)==0 ){ zD = 0; }
144
145 /* If a specific check-in is requested, fetch and parse it. If the
@@ -141,58 +151,57 @@
151 if( pM ){
152 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
153 linkTrunk = trunkRid && rid != trunkRid;
154 linkTip = rid != symbolic_name_to_rid("tip", "ci");
155 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
156 url_add_parameter(&sURI, "ci", zCI);
157 }else{
158 zCI = 0;
159 }
160 }
161
162 /* Compute the title of the page */
163 blob_zero(&dirname);
164 if( zD ){
165 url_add_parameter(&sURI, "name", zD);
166 blob_append(&dirname, "in directory ", -1);
167 hyperlinked_path(zD, &dirname, zCI, "dir", "");
168 zPrefix = mprintf("%s/", zD);
169 style_submenu_element("Top-Level", "Top-Level", "%s",
170 url_render(&sURI, "name", 0, 0, 0));
 
 
 
 
 
171 }else{
172 blob_append(&dirname, "in the top-level directory", -1);
173 zPrefix = "";
174 }
175 if( linkTrunk ){
176 style_submenu_element("Trunk", "Trunk", "%s",
177 url_render(&sURI, "ci", "trunk", 0, 0));
178 }
179 if( linkTip ){
180 style_submenu_element("Tip", "Tip", "%s",
181 url_render(&sURI, "ci", "tip", 0, 0));
182 }
183 if( zCI ){
184 char zShort[20];
185 memcpy(zShort, zUuid, 10);
186 zShort[10] = 0;
187 @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
188 @ %s(blob_str(&dirname))</h2>
189 zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
190 if( nD==0 ){
 
 
 
 
191 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
192 zUuid);
193 }
194 }else{
195 @ <h2>The union of all files from all check-ins
196 @ %s(blob_str(&dirname))</h2>
197 zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
198 }
199 style_submenu_element("All", "All", "%s",
200 url_render(&sURI, "ci", 0, 0, 0));
201 style_submenu_element("Tree-View", "Tree-View", "%s",
202 url_render(&sURI, "type", "tree", 0, 0));
203
204 /* Compute the temporary table "localfiles" containing the names
205 ** of all files and subdirectories in the zD[] directory.
206 **
207 ** Subdirectory names begin with "/". This causes them to sort
@@ -287,10 +296,309 @@
296 db_finalize(&q);
297 manifest_destroy(pM);
298 @ </ul></td></tr></table>
299 style_footer();
300 }
301
302 /*
303 ** Objects used by the "tree" webpage.
304 */
305 typedef struct FileTreeNode FileTreeNode;
306 typedef struct FileTree FileTree;
307
308 /*
309 ** A single line of the file hierarchy
310 */
311 struct FileTreeNode {
312 FileTreeNode *pNext; /* Next line in sequence */
313 FileTreeNode *pPrev; /* Previous line */
314 FileTreeNode *pParent; /* Directory containing this line */
315 char *zName; /* Name of this entry. The "tail" */
316 char *zFullName; /* Full pathname of this entry */
317 char *zUuid; /* SHA1 hash of this file. May be NULL. */
318 unsigned nFullName; /* Length of zFullName */
319 unsigned iLevel; /* Levels of parent directories */
320 u8 isDir; /* True if there are children */
321 u8 isLast; /* True if this is the last child of its parent */
322 };
323
324 /*
325 ** A complete file hierarchy
326 */
327 struct FileTree {
328 FileTreeNode *pFirst; /* First line of the list */
329 FileTreeNode *pLast; /* Last line of the list */
330 };
331
332 /*
333 ** Add one or more new FileTreeNodes to the FileTree object so that the
334 ** leaf object zPathname is at the end of the node list
335 */
336 static void tree_add_node(
337 FileTree *pTree, /* Tree into which nodes are added */
338 const char *zPath, /* The full pathname of file to add */
339 const char *zUuid /* UUID of the file. Might be NULL. */
340 ){
341 int i;
342 FileTreeNode *pParent;
343 FileTreeNode *pChild;
344
345 pChild = pTree->pLast;
346 pParent = pChild ? pChild->pParent : 0;
347 while( pParent!=0 &&
348 ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0
349 || zPath[pParent->nFullName]!='/' )
350 ){
351 pChild = pParent;
352 pParent = pChild->pParent;
353 }
354 i = pParent ? pParent->nFullName+1 : 0;
355 if( pChild ) pChild->isLast = 0;
356 while( zPath[i] ){
357 FileTreeNode *pNew;
358 int iStart = i;
359 int nByte;
360 while( zPath[i] && zPath[i]!='/' ){ i++; }
361 nByte = sizeof(*pNew) + i + 1;
362 if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1;
363 pNew = fossil_malloc( nByte );
364 pNew->zFullName = (char*)&pNew[1];
365 memcpy(pNew->zFullName, zPath, i);
366 pNew->zFullName[i] = 0;
367 pNew->nFullName = i;
368 if( zUuid!=0 && zPath[i]==0 ){
369 pNew->zUuid = pNew->zFullName + i + 1;
370 memcpy(pNew->zUuid, zUuid, UUID_SIZE+1);
371 }else{
372 pNew->zUuid = 0;
373 }
374 pNew->zName = pNew->zFullName + iStart;
375 if( pTree->pLast ){
376 pTree->pLast->pNext = pNew;
377 }else{
378 pTree->pFirst = pNew;
379 }
380 pNew->pPrev = pTree->pLast;
381 pNew->pNext = 0;
382 pNew->pParent = pParent;
383 pTree->pLast = pNew;
384 pNew->iLevel = pParent ? pParent->iLevel+1 : 0;
385 pNew->isDir = zPath[i]=='/';
386 pNew->isLast = 1;
387 while( zPath[i]=='/' ){ i++; }
388 pParent = pNew;
389 }
390 }
391
392 /*
393 ** Render parent lines for pNode
394 */
395 static void tree_indentation(FileTreeNode *p){
396 if( p==0 ) return;
397 tree_indentation(p->pParent);
398 if( p->isLast ){
399 cgi_append_content(" ", 4);
400 }else{
401 cgi_append_content("&#x2502; ", 11);
402 }
403 }
404
405
406 /*
407 ** WEBPAGE: tree
408 **
409 ** Query parameters:
410 **
411 ** name=PATH Directory to display. Optional
412 ** ci=LABEL Show only files in this check-in. Optional.
413 ** re=REGEXP Show only files matching REGEXP. Optional.
414 */
415 void page_tree(void){
416 char *zD = fossil_strdup(P("name"));
417 int nD = zD ? strlen(zD)+1 : 0;
418 const char *zCI = P("ci");
419 int rid = 0;
420 char *zUuid = 0;
421 Blob dirname;
422 Manifest *pM = 0;
423 int nFile = 0; /* Number of files */
424 int linkTrunk = 1; /* include link to "trunk" */
425 int linkTip = 1; /* include link to "tip" */
426 const char *zRE; /* the value for the re=REGEXP query parameter */
427 char *zPrefix; /* Prefix on all filenames */
428 char *zREx = ""; /* Extra parameters for path hyperlinks */
429 ReCompiled *pRE = 0; /* Compiled regular expression */
430 FileTreeNode *p; /* One line of the tree */
431 FileTree sTree; /* The complete tree of files */
432 HQuery sURI; /* Hyperlink */
433
434 if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
435 memset(&sTree, 0, sizeof(sTree));
436 login_check_credentials();
437 if( !g.perm.Read ){ login_needed(); return; }
438 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
439 style_header("File List");
440 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
441 pathelementFunc, 0, 0);
442 url_initialize(&sURI, "tree");
443
444 /* If a regular expression is specified, compile it */
445 zRE = P("re");
446 if( zRE ){
447 re_compile(&pRE, zRE, 0);
448 url_add_parameter(&sURI, "re", zRE);
449 zREx = mprintf("&re=%T", zRE);
450 }
451
452 /* If the name= parameter is an empty string, make it a NULL pointer */
453 if( zD && strlen(zD)==0 ){ zD = 0; }
454
455 /* If a specific check-in is requested, fetch and parse it. If the
456 ** specific check-in does not exist, clear zCI. zCI==0 will cause all
457 ** files from all check-ins to be displayed.
458 */
459 if( zCI ){
460 pM = manifest_get_by_name(zCI, &rid);
461 if( pM ){
462 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
463 linkTrunk = trunkRid && rid != trunkRid;
464 linkTip = rid != symbolic_name_to_rid("tip", "ci");
465 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
466 url_add_parameter(&sURI, "ci", zCI);
467 }else{
468 zCI = 0;
469 }
470 }
471
472 /* Compute the title of the page */
473 blob_zero(&dirname);
474 if( zD ){
475 url_add_parameter(&sURI, "name", zD);
476 blob_append(&dirname, "within directory ", -1);
477 hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
478 if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
479 zPrefix = mprintf("%T/", zD);
480 style_submenu_element("Top-Level", "Top-Level", "%s",
481 url_render(&sURI, "name", 0, 0, 0));
482 }else{
483 if( zRE ){
484 blob_appendf(&dirname, "matching \"%s\"", zRE);
485 }
486 zPrefix = "";
487 }
488 if( zCI ){
489 style_submenu_element("All", "All", "%s",
490 url_render(&sURI, "ci", 0, 0, 0));
491 }
492 if( linkTrunk ){
493 style_submenu_element("Trunk", "Trunk", "%s",
494 url_render(&sURI, "ci", "trunk", 0, 0));
495 }
496 if ( linkTip ){
497 style_submenu_element("Tip", "Tip", "%s",
498 url_render(&sURI, "ci", "tip", 0, 0));
499 }
500 style_submenu_element("Flat-View", "Flat-View", "%s",
501 url_render(&sURI, "type", "flat", 0, 0));
502 /* Compute the file hierarchy.
503 */
504 if( zCI ){
505 Stmt ins, q;
506 ManifestFile *pFile;
507
508 db_multi_exec(
509 "CREATE TEMP TABLE filelist("
510 " x TEXT PRIMARY KEY COLLATE nocase,"
511 " uuid TEXT"
512 ") WITHOUT ROWID;"
513 );
514 db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)");
515 manifest_file_rewind(pM);
516 while( (pFile = manifest_file_next(pM,0))!=0 ){
517 if( nD>0
518 && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
519 || pFile->zName[nD-1]!='/')
520 ){
521 continue;
522 }
523 if( pRE && re_match(pRE, (const u8*)pFile->zName, -1)==0 ) continue;
524 db_bind_text(&ins, ":f", &pFile->zName[nD]);
525 db_bind_text(&ins, ":u", pFile->zUuid);
526 db_step(&ins);
527 db_reset(&ins);
528 }
529 db_finalize(&ins);
530 db_prepare(&q, "SELECT x, uuid FROM filelist ORDER BY x");
531 while( db_step(&q)==SQLITE_ROW ){
532 tree_add_node(&sTree, db_column_text(&q,0), db_column_text(&q,1));
533 nFile++;
534 }
535 db_finalize(&q);
536 }else{
537 Stmt q;
538 db_prepare(&q, "SELECT name FROM filename ORDER BY name COLLATE nocase");
539 while( db_step(&q)==SQLITE_ROW ){
540 const char *z = db_column_text(&q, 0);
541 if( nD>0 && (fossil_strncmp(z, zD, nD-1)!=0 || z[nD-1]!='/') ){
542 continue;
543 }
544 if( pRE && re_match(pRE, (const u8*)z, -1)==0 ) continue;
545 tree_add_node(&sTree, z+nD, 0);
546 nFile++;
547 }
548 db_finalize(&q);
549 }
550
551 if( zCI ){
552 @ <h2>%d(nFile) files of
553 @ check-in [%z(href("vinfo?name=%T",zUuid))%S(zUuid)</a>]
554 @ %s(blob_str(&dirname))</h2>
555 }else{
556 int n = db_int(0, "SELECT count(*) FROM plink");
557 @ <h2>%d(nFile) files from all %d(n) check-ins
558 @ %s(blob_str(&dirname))</h2>
559 }
560
561
562 /* Generate a multi-column table listing the contents of zD[]
563 ** directory.
564 */
565 @ <pre>
566 if( nD ){
567 cgi_printf("%.*h\n", nD, zD);
568 }else{
569 @ .
570 }
571 for(p=sTree.pFirst; p; p=p->pNext){
572 tree_indentation(p->pParent);
573 if( p->isLast ){
574 cgi_append_content("&#x2514;&#x2500;&#x2500; ", 25);
575 }else{
576 cgi_append_content("&#x251c;&#x2500;&#x2500; ", 25);
577 }
578 if( p->isDir ){
579 char *zName = mprintf("%s%T", zPrefix, p->zFullName);
580 char *zLink = href("%s", url_render(&sURI, "name", zName, 0, 0));
581 fossil_free(zName);
582 @ %z(zLink)%h(p->zName)</a>
583 }else{
584 char *zLink;
585 if( zCI ){
586 zLink = href("%R/artifact/%s",p->zUuid);
587 }else{
588 zLink = href("%R/finfo?name=%s%T",zPrefix,p->zFullName);
589 }
590 @ %z(zLink)%h(p->zName)</a>
591 }
592 }
593 @ </pre>
594 style_footer();
595
596 /* We could free memory used by sTree here if we needed to. But
597 ** the process is about to exit, so doing so would not really accomplish
598 ** anything useful. */
599 }
600
601 /*
602 ** Return a CSS class name based on the given filename's extension.
603 ** Result must be freed by the caller.
604 **/
605
+332 -24
--- src/browse.c
+++ src/browse.c
@@ -71,23 +71,29 @@
7171
** There is no hyperlink on the file element of the path.
7272
**
7373
** The computed string is appended to the pOut blob. pOut should
7474
** have already been initialized.
7575
*/
76
-void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
76
+void hyperlinked_path(
77
+ const char *zPath, /* Path to render */
78
+ Blob *pOut, /* Write into this blob */
79
+ const char *zCI, /* check-in name, or NULL */
80
+ const char *zURI, /* "dir" or "tree" */
81
+ const char *zREx /* Extra query parameters */
82
+){
7783
int i, j;
7884
char *zSep = "";
7985
8086
for(i=0; zPath[i]; i=j){
8187
for(j=i; zPath[j] && zPath[j]!='/'; j++){}
8288
if( zPath[j] && g.perm.Hyperlink ){
8389
if( zCI ){
84
- char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath);
90
+ char *zLink = href("%R/%s?ci=%S&name=%#T%s", zURI, zCI, j, zPath,zREx);
8591
blob_appendf(pOut, "%s%z%#h</a>",
8692
zSep, zLink, j-i, &zPath[i]);
8793
}else{
88
- char *zLink = href("%R/dir?name=%#T", j, zPath);
94
+ char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
8995
blob_appendf(pOut, "%s%z%#h</a>",
9096
zSep, zLink, j-i, &zPath[i]);
9197
}
9298
}else{
9399
blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
@@ -101,11 +107,11 @@
101107
/*
102108
** WEBPAGE: dir
103109
**
104110
** Query parameters:
105111
**
106
-** name=PATH Directory to display. Required.
112
+** name=PATH Directory to display. Optional. Top-level if missing
107113
** ci=LABEL Show only files in this check-in. Optional.
108114
*/
109115
void page_dir(void){
110116
char *zD = fossil_strdup(P("name"));
111117
int nD = zD ? strlen(zD)+1 : 0;
@@ -118,18 +124,22 @@
118124
int rid = 0;
119125
char *zUuid = 0;
120126
Blob dirname;
121127
Manifest *pM = 0;
122128
const char *zSubdirLink;
123
- int linkTrunk = 1, linkTip = 1;
129
+ int linkTrunk = 1;
130
+ int linkTip = 1;
131
+ HQuery sURI;
124132
133
+ if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
125134
login_check_credentials();
126135
if( !g.perm.Read ){ login_needed(); return; }
127136
while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
128137
style_header("File List");
129138
sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
130139
pathelementFunc, 0, 0);
140
+ url_initialize(&sURI, "dir");
131141
132142
/* If the name= parameter is an empty string, make it a NULL pointer */
133143
if( zD && strlen(zD)==0 ){ zD = 0; }
134144
135145
/* If a specific check-in is requested, fetch and parse it. If the
@@ -141,58 +151,57 @@
141151
if( pM ){
142152
int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
143153
linkTrunk = trunkRid && rid != trunkRid;
144154
linkTip = rid != symbolic_name_to_rid("tip", "ci");
145155
zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
156
+ url_add_parameter(&sURI, "ci", zCI);
146157
}else{
147158
zCI = 0;
148159
}
149160
}
150161
151162
/* Compute the title of the page */
152163
blob_zero(&dirname);
153164
if( zD ){
165
+ url_add_parameter(&sURI, "name", zD);
154166
blob_append(&dirname, "in directory ", -1);
155
- hyperlinked_path(zD, &dirname, zCI);
167
+ hyperlinked_path(zD, &dirname, zCI, "dir", "");
156168
zPrefix = mprintf("%s/", zD);
157
- if( linkTrunk ){
158
- style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk",
159
- zD);
160
- }
161
- if ( linkTip ){
162
- style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD);
163
- }
169
+ style_submenu_element("Top-Level", "Top-Level", "%s",
170
+ url_render(&sURI, "name", 0, 0, 0));
164171
}else{
165172
blob_append(&dirname, "in the top-level directory", -1);
166173
zPrefix = "";
167
- if( linkTrunk ){
168
- style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk");
169
- }
170
- if ( linkTip ){
171
- style_submenu_element("Tip", "Tip", "%R/dir?ci=tip");
172
- }
174
+ }
175
+ if( linkTrunk ){
176
+ style_submenu_element("Trunk", "Trunk", "%s",
177
+ url_render(&sURI, "ci", "trunk", 0, 0));
178
+ }
179
+ if( linkTip ){
180
+ style_submenu_element("Tip", "Tip", "%s",
181
+ url_render(&sURI, "ci", "tip", 0, 0));
173182
}
174183
if( zCI ){
175184
char zShort[20];
176185
memcpy(zShort, zUuid, 10);
177186
zShort[10] = 0;
178187
@ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
179188
@ %s(blob_str(&dirname))</h2>
180189
zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
181
- if( zD ){
182
- style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid);
183
- style_submenu_element("All", "All", "%R/dir?name=%t", zD);
184
- }else{
185
- style_submenu_element("All", "All", "%R/dir");
190
+ if( nD==0 ){
186191
style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
187192
zUuid);
188193
}
189194
}else{
190195
@ <h2>The union of all files from all check-ins
191196
@ %s(blob_str(&dirname))</h2>
192197
zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
193198
}
199
+ style_submenu_element("All", "All", "%s",
200
+ url_render(&sURI, "ci", 0, 0, 0));
201
+ style_submenu_element("Tree-View", "Tree-View", "%s",
202
+ url_render(&sURI, "type", "tree", 0, 0));
194203
195204
/* Compute the temporary table "localfiles" containing the names
196205
** of all files and subdirectories in the zD[] directory.
197206
**
198207
** Subdirectory names begin with "/". This causes them to sort
@@ -287,10 +296,309 @@
287296
db_finalize(&q);
288297
manifest_destroy(pM);
289298
@ </ul></td></tr></table>
290299
style_footer();
291300
}
301
+
302
+/*
303
+** Objects used by the "tree" webpage.
304
+*/
305
+typedef struct FileTreeNode FileTreeNode;
306
+typedef struct FileTree FileTree;
307
+
308
+/*
309
+** A single line of the file hierarchy
310
+*/
311
+struct FileTreeNode {
312
+ FileTreeNode *pNext; /* Next line in sequence */
313
+ FileTreeNode *pPrev; /* Previous line */
314
+ FileTreeNode *pParent; /* Directory containing this line */
315
+ char *zName; /* Name of this entry. The "tail" */
316
+ char *zFullName; /* Full pathname of this entry */
317
+ char *zUuid; /* SHA1 hash of this file. May be NULL. */
318
+ unsigned nFullName; /* Length of zFullName */
319
+ unsigned iLevel; /* Levels of parent directories */
320
+ u8 isDir; /* True if there are children */
321
+ u8 isLast; /* True if this is the last child of its parent */
322
+};
323
+
324
+/*
325
+** A complete file hierarchy
326
+*/
327
+struct FileTree {
328
+ FileTreeNode *pFirst; /* First line of the list */
329
+ FileTreeNode *pLast; /* Last line of the list */
330
+};
331
+
332
+/*
333
+** Add one or more new FileTreeNodes to the FileTree object so that the
334
+** leaf object zPathname is at the end of the node list
335
+*/
336
+static void tree_add_node(
337
+ FileTree *pTree, /* Tree into which nodes are added */
338
+ const char *zPath, /* The full pathname of file to add */
339
+ const char *zUuid /* UUID of the file. Might be NULL. */
340
+){
341
+ int i;
342
+ FileTreeNode *pParent;
343
+ FileTreeNode *pChild;
344
+
345
+ pChild = pTree->pLast;
346
+ pParent = pChild ? pChild->pParent : 0;
347
+ while( pParent!=0 &&
348
+ ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0
349
+ || zPath[pParent->nFullName]!='/' )
350
+ ){
351
+ pChild = pParent;
352
+ pParent = pChild->pParent;
353
+ }
354
+ i = pParent ? pParent->nFullName+1 : 0;
355
+ if( pChild ) pChild->isLast = 0;
356
+ while( zPath[i] ){
357
+ FileTreeNode *pNew;
358
+ int iStart = i;
359
+ int nByte;
360
+ while( zPath[i] && zPath[i]!='/' ){ i++; }
361
+ nByte = sizeof(*pNew) + i + 1;
362
+ if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1;
363
+ pNew = fossil_malloc( nByte );
364
+ pNew->zFullName = (char*)&pNew[1];
365
+ memcpy(pNew->zFullName, zPath, i);
366
+ pNew->zFullName[i] = 0;
367
+ pNew->nFullName = i;
368
+ if( zUuid!=0 && zPath[i]==0 ){
369
+ pNew->zUuid = pNew->zFullName + i + 1;
370
+ memcpy(pNew->zUuid, zUuid, UUID_SIZE+1);
371
+ }else{
372
+ pNew->zUuid = 0;
373
+ }
374
+ pNew->zName = pNew->zFullName + iStart;
375
+ if( pTree->pLast ){
376
+ pTree->pLast->pNext = pNew;
377
+ }else{
378
+ pTree->pFirst = pNew;
379
+ }
380
+ pNew->pPrev = pTree->pLast;
381
+ pNew->pNext = 0;
382
+ pNew->pParent = pParent;
383
+ pTree->pLast = pNew;
384
+ pNew->iLevel = pParent ? pParent->iLevel+1 : 0;
385
+ pNew->isDir = zPath[i]=='/';
386
+ pNew->isLast = 1;
387
+ while( zPath[i]=='/' ){ i++; }
388
+ pParent = pNew;
389
+ }
390
+}
391
+
392
+/*
393
+** Render parent lines for pNode
394
+*/
395
+static void tree_indentation(FileTreeNode *p){
396
+ if( p==0 ) return;
397
+ tree_indentation(p->pParent);
398
+ if( p->isLast ){
399
+ cgi_append_content(" ", 4);
400
+ }else{
401
+ cgi_append_content("&#x2502; ", 11);
402
+ }
403
+}
404
+
405
+
406
+/*
407
+** WEBPAGE: tree
408
+**
409
+** Query parameters:
410
+**
411
+** name=PATH Directory to display. Optional
412
+** ci=LABEL Show only files in this check-in. Optional.
413
+** re=REGEXP Show only files matching REGEXP. Optional.
414
+*/
415
+void page_tree(void){
416
+ char *zD = fossil_strdup(P("name"));
417
+ int nD = zD ? strlen(zD)+1 : 0;
418
+ const char *zCI = P("ci");
419
+ int rid = 0;
420
+ char *zUuid = 0;
421
+ Blob dirname;
422
+ Manifest *pM = 0;
423
+ int nFile = 0; /* Number of files */
424
+ int linkTrunk = 1; /* include link to "trunk" */
425
+ int linkTip = 1; /* include link to "tip" */
426
+ const char *zRE; /* the value for the re=REGEXP query parameter */
427
+ char *zPrefix; /* Prefix on all filenames */
428
+ char *zREx = ""; /* Extra parameters for path hyperlinks */
429
+ ReCompiled *pRE = 0; /* Compiled regular expression */
430
+ FileTreeNode *p; /* One line of the tree */
431
+ FileTree sTree; /* The complete tree of files */
432
+ HQuery sURI; /* Hyperlink */
433
+
434
+ if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
435
+ memset(&sTree, 0, sizeof(sTree));
436
+ login_check_credentials();
437
+ if( !g.perm.Read ){ login_needed(); return; }
438
+ while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
439
+ style_header("File List");
440
+ sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
441
+ pathelementFunc, 0, 0);
442
+ url_initialize(&sURI, "tree");
443
+
444
+ /* If a regular expression is specified, compile it */
445
+ zRE = P("re");
446
+ if( zRE ){
447
+ re_compile(&pRE, zRE, 0);
448
+ url_add_parameter(&sURI, "re", zRE);
449
+ zREx = mprintf("&re=%T", zRE);
450
+ }
451
+
452
+ /* If the name= parameter is an empty string, make it a NULL pointer */
453
+ if( zD && strlen(zD)==0 ){ zD = 0; }
454
+
455
+ /* If a specific check-in is requested, fetch and parse it. If the
456
+ ** specific check-in does not exist, clear zCI. zCI==0 will cause all
457
+ ** files from all check-ins to be displayed.
458
+ */
459
+ if( zCI ){
460
+ pM = manifest_get_by_name(zCI, &rid);
461
+ if( pM ){
462
+ int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
463
+ linkTrunk = trunkRid && rid != trunkRid;
464
+ linkTip = rid != symbolic_name_to_rid("tip", "ci");
465
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
466
+ url_add_parameter(&sURI, "ci", zCI);
467
+ }else{
468
+ zCI = 0;
469
+ }
470
+ }
471
+
472
+ /* Compute the title of the page */
473
+ blob_zero(&dirname);
474
+ if( zD ){
475
+ url_add_parameter(&sURI, "name", zD);
476
+ blob_append(&dirname, "within directory ", -1);
477
+ hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
478
+ if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
479
+ zPrefix = mprintf("%T/", zD);
480
+ style_submenu_element("Top-Level", "Top-Level", "%s",
481
+ url_render(&sURI, "name", 0, 0, 0));
482
+ }else{
483
+ if( zRE ){
484
+ blob_appendf(&dirname, "matching \"%s\"", zRE);
485
+ }
486
+ zPrefix = "";
487
+ }
488
+ if( zCI ){
489
+ style_submenu_element("All", "All", "%s",
490
+ url_render(&sURI, "ci", 0, 0, 0));
491
+ }
492
+ if( linkTrunk ){
493
+ style_submenu_element("Trunk", "Trunk", "%s",
494
+ url_render(&sURI, "ci", "trunk", 0, 0));
495
+ }
496
+ if ( linkTip ){
497
+ style_submenu_element("Tip", "Tip", "%s",
498
+ url_render(&sURI, "ci", "tip", 0, 0));
499
+ }
500
+ style_submenu_element("Flat-View", "Flat-View", "%s",
501
+ url_render(&sURI, "type", "flat", 0, 0));
502
+ /* Compute the file hierarchy.
503
+ */
504
+ if( zCI ){
505
+ Stmt ins, q;
506
+ ManifestFile *pFile;
507
+
508
+ db_multi_exec(
509
+ "CREATE TEMP TABLE filelist("
510
+ " x TEXT PRIMARY KEY COLLATE nocase,"
511
+ " uuid TEXT"
512
+ ") WITHOUT ROWID;"
513
+ );
514
+ db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)");
515
+ manifest_file_rewind(pM);
516
+ while( (pFile = manifest_file_next(pM,0))!=0 ){
517
+ if( nD>0
518
+ && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
519
+ || pFile->zName[nD-1]!='/')
520
+ ){
521
+ continue;
522
+ }
523
+ if( pRE && re_match(pRE, (const u8*)pFile->zName, -1)==0 ) continue;
524
+ db_bind_text(&ins, ":f", &pFile->zName[nD]);
525
+ db_bind_text(&ins, ":u", pFile->zUuid);
526
+ db_step(&ins);
527
+ db_reset(&ins);
528
+ }
529
+ db_finalize(&ins);
530
+ db_prepare(&q, "SELECT x, uuid FROM filelist ORDER BY x");
531
+ while( db_step(&q)==SQLITE_ROW ){
532
+ tree_add_node(&sTree, db_column_text(&q,0), db_column_text(&q,1));
533
+ nFile++;
534
+ }
535
+ db_finalize(&q);
536
+ }else{
537
+ Stmt q;
538
+ db_prepare(&q, "SELECT name FROM filename ORDER BY name COLLATE nocase");
539
+ while( db_step(&q)==SQLITE_ROW ){
540
+ const char *z = db_column_text(&q, 0);
541
+ if( nD>0 && (fossil_strncmp(z, zD, nD-1)!=0 || z[nD-1]!='/') ){
542
+ continue;
543
+ }
544
+ if( pRE && re_match(pRE, (const u8*)z, -1)==0 ) continue;
545
+ tree_add_node(&sTree, z+nD, 0);
546
+ nFile++;
547
+ }
548
+ db_finalize(&q);
549
+ }
550
+
551
+ if( zCI ){
552
+ @ <h2>%d(nFile) files of
553
+ @ check-in [%z(href("vinfo?name=%T",zUuid))%S(zUuid)</a>]
554
+ @ %s(blob_str(&dirname))</h2>
555
+ }else{
556
+ int n = db_int(0, "SELECT count(*) FROM plink");
557
+ @ <h2>%d(nFile) files from all %d(n) check-ins
558
+ @ %s(blob_str(&dirname))</h2>
559
+ }
560
+
561
+
562
+ /* Generate a multi-column table listing the contents of zD[]
563
+ ** directory.
564
+ */
565
+ @ <pre>
566
+ if( nD ){
567
+ cgi_printf("%.*h\n", nD, zD);
568
+ }else{
569
+ @ .
570
+ }
571
+ for(p=sTree.pFirst; p; p=p->pNext){
572
+ tree_indentation(p->pParent);
573
+ if( p->isLast ){
574
+ cgi_append_content("&#x2514;&#x2500;&#x2500; ", 25);
575
+ }else{
576
+ cgi_append_content("&#x251c;&#x2500;&#x2500; ", 25);
577
+ }
578
+ if( p->isDir ){
579
+ char *zName = mprintf("%s%T", zPrefix, p->zFullName);
580
+ char *zLink = href("%s", url_render(&sURI, "name", zName, 0, 0));
581
+ fossil_free(zName);
582
+ @ %z(zLink)%h(p->zName)</a>
583
+ }else{
584
+ char *zLink;
585
+ if( zCI ){
586
+ zLink = href("%R/artifact/%s",p->zUuid);
587
+ }else{
588
+ zLink = href("%R/finfo?name=%s%T",zPrefix,p->zFullName);
589
+ }
590
+ @ %z(zLink)%h(p->zName)</a>
591
+ }
592
+ }
593
+ @ </pre>
594
+ style_footer();
595
+
596
+ /* We could free memory used by sTree here if we needed to. But
597
+ ** the process is about to exit, so doing so would not really accomplish
598
+ ** anything useful. */
599
+}
292600
293601
/*
294602
** Return a CSS class name based on the given filename's extension.
295603
** Result must be freed by the caller.
296604
**/
297605
--- src/browse.c
+++ src/browse.c
@@ -71,23 +71,29 @@
71 ** There is no hyperlink on the file element of the path.
72 **
73 ** The computed string is appended to the pOut blob. pOut should
74 ** have already been initialized.
75 */
76 void hyperlinked_path(const char *zPath, Blob *pOut, const char *zCI){
 
 
 
 
 
 
77 int i, j;
78 char *zSep = "";
79
80 for(i=0; zPath[i]; i=j){
81 for(j=i; zPath[j] && zPath[j]!='/'; j++){}
82 if( zPath[j] && g.perm.Hyperlink ){
83 if( zCI ){
84 char *zLink = href("%R/dir?ci=%S&name=%#T", zCI, j, zPath);
85 blob_appendf(pOut, "%s%z%#h</a>",
86 zSep, zLink, j-i, &zPath[i]);
87 }else{
88 char *zLink = href("%R/dir?name=%#T", j, zPath);
89 blob_appendf(pOut, "%s%z%#h</a>",
90 zSep, zLink, j-i, &zPath[i]);
91 }
92 }else{
93 blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
@@ -101,11 +107,11 @@
101 /*
102 ** WEBPAGE: dir
103 **
104 ** Query parameters:
105 **
106 ** name=PATH Directory to display. Required.
107 ** ci=LABEL Show only files in this check-in. Optional.
108 */
109 void page_dir(void){
110 char *zD = fossil_strdup(P("name"));
111 int nD = zD ? strlen(zD)+1 : 0;
@@ -118,18 +124,22 @@
118 int rid = 0;
119 char *zUuid = 0;
120 Blob dirname;
121 Manifest *pM = 0;
122 const char *zSubdirLink;
123 int linkTrunk = 1, linkTip = 1;
 
 
124
 
125 login_check_credentials();
126 if( !g.perm.Read ){ login_needed(); return; }
127 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
128 style_header("File List");
129 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
130 pathelementFunc, 0, 0);
 
131
132 /* If the name= parameter is an empty string, make it a NULL pointer */
133 if( zD && strlen(zD)==0 ){ zD = 0; }
134
135 /* If a specific check-in is requested, fetch and parse it. If the
@@ -141,58 +151,57 @@
141 if( pM ){
142 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
143 linkTrunk = trunkRid && rid != trunkRid;
144 linkTip = rid != symbolic_name_to_rid("tip", "ci");
145 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
 
146 }else{
147 zCI = 0;
148 }
149 }
150
151 /* Compute the title of the page */
152 blob_zero(&dirname);
153 if( zD ){
 
154 blob_append(&dirname, "in directory ", -1);
155 hyperlinked_path(zD, &dirname, zCI);
156 zPrefix = mprintf("%s/", zD);
157 if( linkTrunk ){
158 style_submenu_element("Trunk", "Trunk", "%R/dir?name=%t&ci=trunk",
159 zD);
160 }
161 if ( linkTip ){
162 style_submenu_element("Tip", "Tip", "%R/dir?name=%t&ci=tip", zD);
163 }
164 }else{
165 blob_append(&dirname, "in the top-level directory", -1);
166 zPrefix = "";
167 if( linkTrunk ){
168 style_submenu_element("Trunk", "Trunk", "%R/dir?ci=trunk");
169 }
170 if ( linkTip ){
171 style_submenu_element("Tip", "Tip", "%R/dir?ci=tip");
172 }
 
 
173 }
174 if( zCI ){
175 char zShort[20];
176 memcpy(zShort, zUuid, 10);
177 zShort[10] = 0;
178 @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
179 @ %s(blob_str(&dirname))</h2>
180 zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
181 if( zD ){
182 style_submenu_element("Top", "Top", "%R/dir?ci=%S", zUuid);
183 style_submenu_element("All", "All", "%R/dir?name=%t", zD);
184 }else{
185 style_submenu_element("All", "All", "%R/dir");
186 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
187 zUuid);
188 }
189 }else{
190 @ <h2>The union of all files from all check-ins
191 @ %s(blob_str(&dirname))</h2>
192 zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
193 }
 
 
 
 
194
195 /* Compute the temporary table "localfiles" containing the names
196 ** of all files and subdirectories in the zD[] directory.
197 **
198 ** Subdirectory names begin with "/". This causes them to sort
@@ -287,10 +296,309 @@
287 db_finalize(&q);
288 manifest_destroy(pM);
289 @ </ul></td></tr></table>
290 style_footer();
291 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
293 /*
294 ** Return a CSS class name based on the given filename's extension.
295 ** Result must be freed by the caller.
296 **/
297
--- src/browse.c
+++ src/browse.c
@@ -71,23 +71,29 @@
71 ** There is no hyperlink on the file element of the path.
72 **
73 ** The computed string is appended to the pOut blob. pOut should
74 ** have already been initialized.
75 */
76 void hyperlinked_path(
77 const char *zPath, /* Path to render */
78 Blob *pOut, /* Write into this blob */
79 const char *zCI, /* check-in name, or NULL */
80 const char *zURI, /* "dir" or "tree" */
81 const char *zREx /* Extra query parameters */
82 ){
83 int i, j;
84 char *zSep = "";
85
86 for(i=0; zPath[i]; i=j){
87 for(j=i; zPath[j] && zPath[j]!='/'; j++){}
88 if( zPath[j] && g.perm.Hyperlink ){
89 if( zCI ){
90 char *zLink = href("%R/%s?ci=%S&name=%#T%s", zURI, zCI, j, zPath,zREx);
91 blob_appendf(pOut, "%s%z%#h</a>",
92 zSep, zLink, j-i, &zPath[i]);
93 }else{
94 char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
95 blob_appendf(pOut, "%s%z%#h</a>",
96 zSep, zLink, j-i, &zPath[i]);
97 }
98 }else{
99 blob_appendf(pOut, "%s%#h", zSep, j-i, &zPath[i]);
@@ -101,11 +107,11 @@
107 /*
108 ** WEBPAGE: dir
109 **
110 ** Query parameters:
111 **
112 ** name=PATH Directory to display. Optional. Top-level if missing
113 ** ci=LABEL Show only files in this check-in. Optional.
114 */
115 void page_dir(void){
116 char *zD = fossil_strdup(P("name"));
117 int nD = zD ? strlen(zD)+1 : 0;
@@ -118,18 +124,22 @@
124 int rid = 0;
125 char *zUuid = 0;
126 Blob dirname;
127 Manifest *pM = 0;
128 const char *zSubdirLink;
129 int linkTrunk = 1;
130 int linkTip = 1;
131 HQuery sURI;
132
133 if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
134 login_check_credentials();
135 if( !g.perm.Read ){ login_needed(); return; }
136 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
137 style_header("File List");
138 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
139 pathelementFunc, 0, 0);
140 url_initialize(&sURI, "dir");
141
142 /* If the name= parameter is an empty string, make it a NULL pointer */
143 if( zD && strlen(zD)==0 ){ zD = 0; }
144
145 /* If a specific check-in is requested, fetch and parse it. If the
@@ -141,58 +151,57 @@
151 if( pM ){
152 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
153 linkTrunk = trunkRid && rid != trunkRid;
154 linkTip = rid != symbolic_name_to_rid("tip", "ci");
155 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
156 url_add_parameter(&sURI, "ci", zCI);
157 }else{
158 zCI = 0;
159 }
160 }
161
162 /* Compute the title of the page */
163 blob_zero(&dirname);
164 if( zD ){
165 url_add_parameter(&sURI, "name", zD);
166 blob_append(&dirname, "in directory ", -1);
167 hyperlinked_path(zD, &dirname, zCI, "dir", "");
168 zPrefix = mprintf("%s/", zD);
169 style_submenu_element("Top-Level", "Top-Level", "%s",
170 url_render(&sURI, "name", 0, 0, 0));
 
 
 
 
 
171 }else{
172 blob_append(&dirname, "in the top-level directory", -1);
173 zPrefix = "";
174 }
175 if( linkTrunk ){
176 style_submenu_element("Trunk", "Trunk", "%s",
177 url_render(&sURI, "ci", "trunk", 0, 0));
178 }
179 if( linkTip ){
180 style_submenu_element("Tip", "Tip", "%s",
181 url_render(&sURI, "ci", "tip", 0, 0));
182 }
183 if( zCI ){
184 char zShort[20];
185 memcpy(zShort, zUuid, 10);
186 zShort[10] = 0;
187 @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
188 @ %s(blob_str(&dirname))</h2>
189 zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
190 if( nD==0 ){
 
 
 
 
191 style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
192 zUuid);
193 }
194 }else{
195 @ <h2>The union of all files from all check-ins
196 @ %s(blob_str(&dirname))</h2>
197 zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
198 }
199 style_submenu_element("All", "All", "%s",
200 url_render(&sURI, "ci", 0, 0, 0));
201 style_submenu_element("Tree-View", "Tree-View", "%s",
202 url_render(&sURI, "type", "tree", 0, 0));
203
204 /* Compute the temporary table "localfiles" containing the names
205 ** of all files and subdirectories in the zD[] directory.
206 **
207 ** Subdirectory names begin with "/". This causes them to sort
@@ -287,10 +296,309 @@
296 db_finalize(&q);
297 manifest_destroy(pM);
298 @ </ul></td></tr></table>
299 style_footer();
300 }
301
302 /*
303 ** Objects used by the "tree" webpage.
304 */
305 typedef struct FileTreeNode FileTreeNode;
306 typedef struct FileTree FileTree;
307
308 /*
309 ** A single line of the file hierarchy
310 */
311 struct FileTreeNode {
312 FileTreeNode *pNext; /* Next line in sequence */
313 FileTreeNode *pPrev; /* Previous line */
314 FileTreeNode *pParent; /* Directory containing this line */
315 char *zName; /* Name of this entry. The "tail" */
316 char *zFullName; /* Full pathname of this entry */
317 char *zUuid; /* SHA1 hash of this file. May be NULL. */
318 unsigned nFullName; /* Length of zFullName */
319 unsigned iLevel; /* Levels of parent directories */
320 u8 isDir; /* True if there are children */
321 u8 isLast; /* True if this is the last child of its parent */
322 };
323
324 /*
325 ** A complete file hierarchy
326 */
327 struct FileTree {
328 FileTreeNode *pFirst; /* First line of the list */
329 FileTreeNode *pLast; /* Last line of the list */
330 };
331
332 /*
333 ** Add one or more new FileTreeNodes to the FileTree object so that the
334 ** leaf object zPathname is at the end of the node list
335 */
336 static void tree_add_node(
337 FileTree *pTree, /* Tree into which nodes are added */
338 const char *zPath, /* The full pathname of file to add */
339 const char *zUuid /* UUID of the file. Might be NULL. */
340 ){
341 int i;
342 FileTreeNode *pParent;
343 FileTreeNode *pChild;
344
345 pChild = pTree->pLast;
346 pParent = pChild ? pChild->pParent : 0;
347 while( pParent!=0 &&
348 ( strncmp(pParent->zFullName, zPath, pParent->nFullName)!=0
349 || zPath[pParent->nFullName]!='/' )
350 ){
351 pChild = pParent;
352 pParent = pChild->pParent;
353 }
354 i = pParent ? pParent->nFullName+1 : 0;
355 if( pChild ) pChild->isLast = 0;
356 while( zPath[i] ){
357 FileTreeNode *pNew;
358 int iStart = i;
359 int nByte;
360 while( zPath[i] && zPath[i]!='/' ){ i++; }
361 nByte = sizeof(*pNew) + i + 1;
362 if( zUuid!=0 && zPath[i]==0 ) nByte += UUID_SIZE+1;
363 pNew = fossil_malloc( nByte );
364 pNew->zFullName = (char*)&pNew[1];
365 memcpy(pNew->zFullName, zPath, i);
366 pNew->zFullName[i] = 0;
367 pNew->nFullName = i;
368 if( zUuid!=0 && zPath[i]==0 ){
369 pNew->zUuid = pNew->zFullName + i + 1;
370 memcpy(pNew->zUuid, zUuid, UUID_SIZE+1);
371 }else{
372 pNew->zUuid = 0;
373 }
374 pNew->zName = pNew->zFullName + iStart;
375 if( pTree->pLast ){
376 pTree->pLast->pNext = pNew;
377 }else{
378 pTree->pFirst = pNew;
379 }
380 pNew->pPrev = pTree->pLast;
381 pNew->pNext = 0;
382 pNew->pParent = pParent;
383 pTree->pLast = pNew;
384 pNew->iLevel = pParent ? pParent->iLevel+1 : 0;
385 pNew->isDir = zPath[i]=='/';
386 pNew->isLast = 1;
387 while( zPath[i]=='/' ){ i++; }
388 pParent = pNew;
389 }
390 }
391
392 /*
393 ** Render parent lines for pNode
394 */
395 static void tree_indentation(FileTreeNode *p){
396 if( p==0 ) return;
397 tree_indentation(p->pParent);
398 if( p->isLast ){
399 cgi_append_content(" ", 4);
400 }else{
401 cgi_append_content("&#x2502; ", 11);
402 }
403 }
404
405
406 /*
407 ** WEBPAGE: tree
408 **
409 ** Query parameters:
410 **
411 ** name=PATH Directory to display. Optional
412 ** ci=LABEL Show only files in this check-in. Optional.
413 ** re=REGEXP Show only files matching REGEXP. Optional.
414 */
415 void page_tree(void){
416 char *zD = fossil_strdup(P("name"));
417 int nD = zD ? strlen(zD)+1 : 0;
418 const char *zCI = P("ci");
419 int rid = 0;
420 char *zUuid = 0;
421 Blob dirname;
422 Manifest *pM = 0;
423 int nFile = 0; /* Number of files */
424 int linkTrunk = 1; /* include link to "trunk" */
425 int linkTip = 1; /* include link to "tip" */
426 const char *zRE; /* the value for the re=REGEXP query parameter */
427 char *zPrefix; /* Prefix on all filenames */
428 char *zREx = ""; /* Extra parameters for path hyperlinks */
429 ReCompiled *pRE = 0; /* Compiled regular expression */
430 FileTreeNode *p; /* One line of the tree */
431 FileTree sTree; /* The complete tree of files */
432 HQuery sURI; /* Hyperlink */
433
434 if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
435 memset(&sTree, 0, sizeof(sTree));
436 login_check_credentials();
437 if( !g.perm.Read ){ login_needed(); return; }
438 while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
439 style_header("File List");
440 sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
441 pathelementFunc, 0, 0);
442 url_initialize(&sURI, "tree");
443
444 /* If a regular expression is specified, compile it */
445 zRE = P("re");
446 if( zRE ){
447 re_compile(&pRE, zRE, 0);
448 url_add_parameter(&sURI, "re", zRE);
449 zREx = mprintf("&re=%T", zRE);
450 }
451
452 /* If the name= parameter is an empty string, make it a NULL pointer */
453 if( zD && strlen(zD)==0 ){ zD = 0; }
454
455 /* If a specific check-in is requested, fetch and parse it. If the
456 ** specific check-in does not exist, clear zCI. zCI==0 will cause all
457 ** files from all check-ins to be displayed.
458 */
459 if( zCI ){
460 pM = manifest_get_by_name(zCI, &rid);
461 if( pM ){
462 int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
463 linkTrunk = trunkRid && rid != trunkRid;
464 linkTip = rid != symbolic_name_to_rid("tip", "ci");
465 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
466 url_add_parameter(&sURI, "ci", zCI);
467 }else{
468 zCI = 0;
469 }
470 }
471
472 /* Compute the title of the page */
473 blob_zero(&dirname);
474 if( zD ){
475 url_add_parameter(&sURI, "name", zD);
476 blob_append(&dirname, "within directory ", -1);
477 hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
478 if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
479 zPrefix = mprintf("%T/", zD);
480 style_submenu_element("Top-Level", "Top-Level", "%s",
481 url_render(&sURI, "name", 0, 0, 0));
482 }else{
483 if( zRE ){
484 blob_appendf(&dirname, "matching \"%s\"", zRE);
485 }
486 zPrefix = "";
487 }
488 if( zCI ){
489 style_submenu_element("All", "All", "%s",
490 url_render(&sURI, "ci", 0, 0, 0));
491 }
492 if( linkTrunk ){
493 style_submenu_element("Trunk", "Trunk", "%s",
494 url_render(&sURI, "ci", "trunk", 0, 0));
495 }
496 if ( linkTip ){
497 style_submenu_element("Tip", "Tip", "%s",
498 url_render(&sURI, "ci", "tip", 0, 0));
499 }
500 style_submenu_element("Flat-View", "Flat-View", "%s",
501 url_render(&sURI, "type", "flat", 0, 0));
502 /* Compute the file hierarchy.
503 */
504 if( zCI ){
505 Stmt ins, q;
506 ManifestFile *pFile;
507
508 db_multi_exec(
509 "CREATE TEMP TABLE filelist("
510 " x TEXT PRIMARY KEY COLLATE nocase,"
511 " uuid TEXT"
512 ") WITHOUT ROWID;"
513 );
514 db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)");
515 manifest_file_rewind(pM);
516 while( (pFile = manifest_file_next(pM,0))!=0 ){
517 if( nD>0
518 && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
519 || pFile->zName[nD-1]!='/')
520 ){
521 continue;
522 }
523 if( pRE && re_match(pRE, (const u8*)pFile->zName, -1)==0 ) continue;
524 db_bind_text(&ins, ":f", &pFile->zName[nD]);
525 db_bind_text(&ins, ":u", pFile->zUuid);
526 db_step(&ins);
527 db_reset(&ins);
528 }
529 db_finalize(&ins);
530 db_prepare(&q, "SELECT x, uuid FROM filelist ORDER BY x");
531 while( db_step(&q)==SQLITE_ROW ){
532 tree_add_node(&sTree, db_column_text(&q,0), db_column_text(&q,1));
533 nFile++;
534 }
535 db_finalize(&q);
536 }else{
537 Stmt q;
538 db_prepare(&q, "SELECT name FROM filename ORDER BY name COLLATE nocase");
539 while( db_step(&q)==SQLITE_ROW ){
540 const char *z = db_column_text(&q, 0);
541 if( nD>0 && (fossil_strncmp(z, zD, nD-1)!=0 || z[nD-1]!='/') ){
542 continue;
543 }
544 if( pRE && re_match(pRE, (const u8*)z, -1)==0 ) continue;
545 tree_add_node(&sTree, z+nD, 0);
546 nFile++;
547 }
548 db_finalize(&q);
549 }
550
551 if( zCI ){
552 @ <h2>%d(nFile) files of
553 @ check-in [%z(href("vinfo?name=%T",zUuid))%S(zUuid)</a>]
554 @ %s(blob_str(&dirname))</h2>
555 }else{
556 int n = db_int(0, "SELECT count(*) FROM plink");
557 @ <h2>%d(nFile) files from all %d(n) check-ins
558 @ %s(blob_str(&dirname))</h2>
559 }
560
561
562 /* Generate a multi-column table listing the contents of zD[]
563 ** directory.
564 */
565 @ <pre>
566 if( nD ){
567 cgi_printf("%.*h\n", nD, zD);
568 }else{
569 @ .
570 }
571 for(p=sTree.pFirst; p; p=p->pNext){
572 tree_indentation(p->pParent);
573 if( p->isLast ){
574 cgi_append_content("&#x2514;&#x2500;&#x2500; ", 25);
575 }else{
576 cgi_append_content("&#x251c;&#x2500;&#x2500; ", 25);
577 }
578 if( p->isDir ){
579 char *zName = mprintf("%s%T", zPrefix, p->zFullName);
580 char *zLink = href("%s", url_render(&sURI, "name", zName, 0, 0));
581 fossil_free(zName);
582 @ %z(zLink)%h(p->zName)</a>
583 }else{
584 char *zLink;
585 if( zCI ){
586 zLink = href("%R/artifact/%s",p->zUuid);
587 }else{
588 zLink = href("%R/finfo?name=%s%T",zPrefix,p->zFullName);
589 }
590 @ %z(zLink)%h(p->zName)</a>
591 }
592 }
593 @ </pre>
594 style_footer();
595
596 /* We could free memory used by sTree here if we needed to. But
597 ** the process is about to exit, so doing so would not really accomplish
598 ** anything useful. */
599 }
600
601 /*
602 ** Return a CSS class name based on the given filename's extension.
603 ** Result must be freed by the caller.
604 **/
605
+2 -2
--- src/finfo.c
+++ src/finfo.c
@@ -376,16 +376,16 @@
376376
blob_zero(&title);
377377
if( baseCheckin ){
378378
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379379
char *zLink = href("%R/info/%S", zUuid);
380380
blob_appendf(&title, "Ancestors of file ");
381
- hyperlinked_path(zFilename, &title, zUuid);
381
+ hyperlinked_path(zFilename, &title, zUuid, "dir", "");
382382
blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
383383
fossil_free(zUuid);
384384
}else{
385385
blob_appendf(&title, "History of files named ");
386
- hyperlinked_path(zFilename, &title, 0);
386
+ hyperlinked_path(zFilename, &title, 0, "dir", "");
387387
}
388388
@ <h2>%b(&title)</h2>
389389
blob_reset(&title);
390390
pGraph = graph_init();
391391
@ <div id="canvas" style="position:relative;width:1px;height:1px;"
392392
--- src/finfo.c
+++ src/finfo.c
@@ -376,16 +376,16 @@
376 blob_zero(&title);
377 if( baseCheckin ){
378 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379 char *zLink = href("%R/info/%S", zUuid);
380 blob_appendf(&title, "Ancestors of file ");
381 hyperlinked_path(zFilename, &title, zUuid);
382 blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
383 fossil_free(zUuid);
384 }else{
385 blob_appendf(&title, "History of files named ");
386 hyperlinked_path(zFilename, &title, 0);
387 }
388 @ <h2>%b(&title)</h2>
389 blob_reset(&title);
390 pGraph = graph_init();
391 @ <div id="canvas" style="position:relative;width:1px;height:1px;"
392
--- src/finfo.c
+++ src/finfo.c
@@ -376,16 +376,16 @@
376 blob_zero(&title);
377 if( baseCheckin ){
378 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379 char *zLink = href("%R/info/%S", zUuid);
380 blob_appendf(&title, "Ancestors of file ");
381 hyperlinked_path(zFilename, &title, zUuid, "dir", "");
382 blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
383 fossil_free(zUuid);
384 }else{
385 blob_appendf(&title, "History of files named ");
386 hyperlinked_path(zFilename, &title, 0, "dir", "");
387 }
388 @ <h2>%b(&title)</h2>
389 blob_reset(&title);
390 pGraph = graph_init();
391 @ <div id="canvas" style="position:relative;width:1px;height:1px;"
392
+2 -2
--- src/finfo.c
+++ src/finfo.c
@@ -376,16 +376,16 @@
376376
blob_zero(&title);
377377
if( baseCheckin ){
378378
char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379379
char *zLink = href("%R/info/%S", zUuid);
380380
blob_appendf(&title, "Ancestors of file ");
381
- hyperlinked_path(zFilename, &title, zUuid);
381
+ hyperlinked_path(zFilename, &title, zUuid, "dir", "");
382382
blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
383383
fossil_free(zUuid);
384384
}else{
385385
blob_appendf(&title, "History of files named ");
386
- hyperlinked_path(zFilename, &title, 0);
386
+ hyperlinked_path(zFilename, &title, 0, "dir", "");
387387
}
388388
@ <h2>%b(&title)</h2>
389389
blob_reset(&title);
390390
pGraph = graph_init();
391391
@ <div id="canvas" style="position:relative;width:1px;height:1px;"
392392
--- src/finfo.c
+++ src/finfo.c
@@ -376,16 +376,16 @@
376 blob_zero(&title);
377 if( baseCheckin ){
378 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379 char *zLink = href("%R/info/%S", zUuid);
380 blob_appendf(&title, "Ancestors of file ");
381 hyperlinked_path(zFilename, &title, zUuid);
382 blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
383 fossil_free(zUuid);
384 }else{
385 blob_appendf(&title, "History of files named ");
386 hyperlinked_path(zFilename, &title, 0);
387 }
388 @ <h2>%b(&title)</h2>
389 blob_reset(&title);
390 pGraph = graph_init();
391 @ <div id="canvas" style="position:relative;width:1px;height:1px;"
392
--- src/finfo.c
+++ src/finfo.c
@@ -376,16 +376,16 @@
376 blob_zero(&title);
377 if( baseCheckin ){
378 char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
379 char *zLink = href("%R/info/%S", zUuid);
380 blob_appendf(&title, "Ancestors of file ");
381 hyperlinked_path(zFilename, &title, zUuid, "dir", "");
382 blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
383 fossil_free(zUuid);
384 }else{
385 blob_appendf(&title, "History of files named ");
386 hyperlinked_path(zFilename, &title, 0, "dir", "");
387 }
388 @ <h2>%b(&title)</h2>
389 blob_reset(&title);
390 pGraph = graph_init();
391 @ <div id="canvas" style="position:relative;width:1px;height:1px;"
392
+7 -1
--- src/info.c
+++ src/info.c
@@ -629,11 +629,11 @@
629629
fossil_free(zUrl);
630630
}
631631
@ </td></tr>
632632
@ <tr><th>Other&nbsp;Links:</th>
633633
@ <td>
634
- @ %z(href("%R/dir?ci=%S",zUuid))files</a>
634
+ @ %z(href("%R/tree?ci=%S",zUuid))files</a>
635635
@ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
636636
@ | %z(href("%R/artifact/%S",zUuid))manifest</a>
637637
if( g.perm.Write ){
638638
@ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
639639
}
@@ -1367,17 +1367,23 @@
13671367
** Return the uninterpreted content of an artifact. Used primarily
13681368
** to view artifacts that are images.
13691369
*/
13701370
void rawartifact_page(void){
13711371
int rid;
1372
+ char *zUuid;
13721373
const char *zMime;
13731374
Blob content;
13741375
13751376
rid = name_to_rid_www("name");
13761377
login_check_credentials();
13771378
if( !g.perm.Read ){ login_needed(); return; }
13781379
if( rid==0 ) fossil_redirect_home();
1380
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1381
+ if( fossil_strcmp(P("name"), zUuid)==0 ){
1382
+ g.isConst = 1;
1383
+ }
1384
+ free(zUuid);
13791385
zMime = P("m");
13801386
if( zMime==0 ){
13811387
char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
13821388
" WHERE mlink.fid=%d"
13831389
" AND filename.fnid=mlink.fnid", rid);
13841390
--- src/info.c
+++ src/info.c
@@ -629,11 +629,11 @@
629 fossil_free(zUrl);
630 }
631 @ </td></tr>
632 @ <tr><th>Other&nbsp;Links:</th>
633 @ <td>
634 @ %z(href("%R/dir?ci=%S",zUuid))files</a>
635 @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
636 @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
637 if( g.perm.Write ){
638 @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
639 }
@@ -1367,17 +1367,23 @@
1367 ** Return the uninterpreted content of an artifact. Used primarily
1368 ** to view artifacts that are images.
1369 */
1370 void rawartifact_page(void){
1371 int rid;
 
1372 const char *zMime;
1373 Blob content;
1374
1375 rid = name_to_rid_www("name");
1376 login_check_credentials();
1377 if( !g.perm.Read ){ login_needed(); return; }
1378 if( rid==0 ) fossil_redirect_home();
 
 
 
 
 
1379 zMime = P("m");
1380 if( zMime==0 ){
1381 char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
1382 " WHERE mlink.fid=%d"
1383 " AND filename.fnid=mlink.fnid", rid);
1384
--- src/info.c
+++ src/info.c
@@ -629,11 +629,11 @@
629 fossil_free(zUrl);
630 }
631 @ </td></tr>
632 @ <tr><th>Other&nbsp;Links:</th>
633 @ <td>
634 @ %z(href("%R/tree?ci=%S",zUuid))files</a>
635 @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
636 @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
637 if( g.perm.Write ){
638 @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
639 }
@@ -1367,17 +1367,23 @@
1367 ** Return the uninterpreted content of an artifact. Used primarily
1368 ** to view artifacts that are images.
1369 */
1370 void rawartifact_page(void){
1371 int rid;
1372 char *zUuid;
1373 const char *zMime;
1374 Blob content;
1375
1376 rid = name_to_rid_www("name");
1377 login_check_credentials();
1378 if( !g.perm.Read ){ login_needed(); return; }
1379 if( rid==0 ) fossil_redirect_home();
1380 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1381 if( fossil_strcmp(P("name"), zUuid)==0 ){
1382 g.isConst = 1;
1383 }
1384 free(zUuid);
1385 zMime = P("m");
1386 if( zMime==0 ){
1387 char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
1388 " WHERE mlink.fid=%d"
1389 " AND filename.fnid=mlink.fnid", rid);
1390
+7 -1
--- src/info.c
+++ src/info.c
@@ -629,11 +629,11 @@
629629
fossil_free(zUrl);
630630
}
631631
@ </td></tr>
632632
@ <tr><th>Other&nbsp;Links:</th>
633633
@ <td>
634
- @ %z(href("%R/dir?ci=%S",zUuid))files</a>
634
+ @ %z(href("%R/tree?ci=%S",zUuid))files</a>
635635
@ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
636636
@ | %z(href("%R/artifact/%S",zUuid))manifest</a>
637637
if( g.perm.Write ){
638638
@ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
639639
}
@@ -1367,17 +1367,23 @@
13671367
** Return the uninterpreted content of an artifact. Used primarily
13681368
** to view artifacts that are images.
13691369
*/
13701370
void rawartifact_page(void){
13711371
int rid;
1372
+ char *zUuid;
13721373
const char *zMime;
13731374
Blob content;
13741375
13751376
rid = name_to_rid_www("name");
13761377
login_check_credentials();
13771378
if( !g.perm.Read ){ login_needed(); return; }
13781379
if( rid==0 ) fossil_redirect_home();
1380
+ zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1381
+ if( fossil_strcmp(P("name"), zUuid)==0 ){
1382
+ g.isConst = 1;
1383
+ }
1384
+ free(zUuid);
13791385
zMime = P("m");
13801386
if( zMime==0 ){
13811387
char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
13821388
" WHERE mlink.fid=%d"
13831389
" AND filename.fnid=mlink.fnid", rid);
13841390
--- src/info.c
+++ src/info.c
@@ -629,11 +629,11 @@
629 fossil_free(zUrl);
630 }
631 @ </td></tr>
632 @ <tr><th>Other&nbsp;Links:</th>
633 @ <td>
634 @ %z(href("%R/dir?ci=%S",zUuid))files</a>
635 @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
636 @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
637 if( g.perm.Write ){
638 @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
639 }
@@ -1367,17 +1367,23 @@
1367 ** Return the uninterpreted content of an artifact. Used primarily
1368 ** to view artifacts that are images.
1369 */
1370 void rawartifact_page(void){
1371 int rid;
 
1372 const char *zMime;
1373 Blob content;
1374
1375 rid = name_to_rid_www("name");
1376 login_check_credentials();
1377 if( !g.perm.Read ){ login_needed(); return; }
1378 if( rid==0 ) fossil_redirect_home();
 
 
 
 
 
1379 zMime = P("m");
1380 if( zMime==0 ){
1381 char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
1382 " WHERE mlink.fid=%d"
1383 " AND filename.fnid=mlink.fnid", rid);
1384
--- src/info.c
+++ src/info.c
@@ -629,11 +629,11 @@
629 fossil_free(zUrl);
630 }
631 @ </td></tr>
632 @ <tr><th>Other&nbsp;Links:</th>
633 @ <td>
634 @ %z(href("%R/tree?ci=%S",zUuid))files</a>
635 @ | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
636 @ | %z(href("%R/artifact/%S",zUuid))manifest</a>
637 if( g.perm.Write ){
638 @ | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
639 }
@@ -1367,17 +1367,23 @@
1367 ** Return the uninterpreted content of an artifact. Used primarily
1368 ** to view artifacts that are images.
1369 */
1370 void rawartifact_page(void){
1371 int rid;
1372 char *zUuid;
1373 const char *zMime;
1374 Blob content;
1375
1376 rid = name_to_rid_www("name");
1377 login_check_credentials();
1378 if( !g.perm.Read ){ login_needed(); return; }
1379 if( rid==0 ) fossil_redirect_home();
1380 zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
1381 if( fossil_strcmp(P("name"), zUuid)==0 ){
1382 g.isConst = 1;
1383 }
1384 free(zUuid);
1385 zMime = P("m");
1386 if( zMime==0 ){
1387 char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
1388 " WHERE mlink.fid=%d"
1389 " AND filename.fnid=mlink.fnid", rid);
1390
+4 -2
--- src/setup.c
+++ src/setup.c
@@ -1597,13 +1597,15 @@
15971597
15981598
/*
15991599
** WEBPAGE: setup_logo
16001600
*/
16011601
void setup_logo(void){
1602
+ const char *zLogoMtime = db_get_mtime("logo-image", 0, 0);
16021603
const char *zLogoMime = db_get("logo-mimetype","image/gif");
16031604
const char *aLogoImg = P("logoim");
16041605
int szLogoImg = atoi(PD("logoim:bytes","0"));
1606
+ const char *zBgMtime = db_get_mtime("background-image", 0, 0);
16051607
const char *zBgMime = db_get("background-mimetype","image/gif");
16061608
const char *aBgImg = P("bgim");
16071609
int szBgImg = atoi(PD("bgim:bytes","0"));
16081610
if( szLogoImg>0 ){
16091611
zLogoMime = PD("logoim:mimetype","image/gif");
@@ -1667,11 +1669,11 @@
16671669
cgi_redirect("setup_logo");
16681670
}
16691671
style_header("Edit Project Logo And Background");
16701672
@ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
16711673
@ and looks like this:</p>
1672
- @ <blockquote><p><img src="%s(g.zTop)/logo" alt="logo" border="1" />
1674
+ @ <blockquote><p><img src="%s(g.zTop)/logo/%z(zLogoMtime)" alt="logo" border="1" />
16731675
@ </p></blockquote>
16741676
@
16751677
@ <form action="%s(g.zTop)/setup_logo" method="post"
16761678
@ enctype="multipart/form-data"><div>
16771679
@ <p>The logo is accessible to all users at this URL:
@@ -1689,11 +1691,11 @@
16891691
@ </div></form>
16901692
@ <hr />
16911693
@
16921694
@ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
16931695
@ and looks like this:</p>
1694
- @ <blockquote><p><img src="%s(g.zTop)/background" alt="background" border=1 />
1696
+ @ <blockquote><p><img src="%s(g.zTop)/background/%z(zBgMtime)" alt="background" border=1 />
16951697
@ </p></blockquote>
16961698
@
16971699
@ <form action="%s(g.zTop)/setup_logo" method="post"
16981700
@ enctype="multipart/form-data"><div>
16991701
@ <p>The background image is accessible to all users at this URL:
17001702
--- src/setup.c
+++ src/setup.c
@@ -1597,13 +1597,15 @@
1597
1598 /*
1599 ** WEBPAGE: setup_logo
1600 */
1601 void setup_logo(void){
 
1602 const char *zLogoMime = db_get("logo-mimetype","image/gif");
1603 const char *aLogoImg = P("logoim");
1604 int szLogoImg = atoi(PD("logoim:bytes","0"));
 
1605 const char *zBgMime = db_get("background-mimetype","image/gif");
1606 const char *aBgImg = P("bgim");
1607 int szBgImg = atoi(PD("bgim:bytes","0"));
1608 if( szLogoImg>0 ){
1609 zLogoMime = PD("logoim:mimetype","image/gif");
@@ -1667,11 +1669,11 @@
1667 cgi_redirect("setup_logo");
1668 }
1669 style_header("Edit Project Logo And Background");
1670 @ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
1671 @ and looks like this:</p>
1672 @ <blockquote><p><img src="%s(g.zTop)/logo" alt="logo" border="1" />
1673 @ </p></blockquote>
1674 @
1675 @ <form action="%s(g.zTop)/setup_logo" method="post"
1676 @ enctype="multipart/form-data"><div>
1677 @ <p>The logo is accessible to all users at this URL:
@@ -1689,11 +1691,11 @@
1689 @ </div></form>
1690 @ <hr />
1691 @
1692 @ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
1693 @ and looks like this:</p>
1694 @ <blockquote><p><img src="%s(g.zTop)/background" alt="background" border=1 />
1695 @ </p></blockquote>
1696 @
1697 @ <form action="%s(g.zTop)/setup_logo" method="post"
1698 @ enctype="multipart/form-data"><div>
1699 @ <p>The background image is accessible to all users at this URL:
1700
--- src/setup.c
+++ src/setup.c
@@ -1597,13 +1597,15 @@
1597
1598 /*
1599 ** WEBPAGE: setup_logo
1600 */
1601 void setup_logo(void){
1602 const char *zLogoMtime = db_get_mtime("logo-image", 0, 0);
1603 const char *zLogoMime = db_get("logo-mimetype","image/gif");
1604 const char *aLogoImg = P("logoim");
1605 int szLogoImg = atoi(PD("logoim:bytes","0"));
1606 const char *zBgMtime = db_get_mtime("background-image", 0, 0);
1607 const char *zBgMime = db_get("background-mimetype","image/gif");
1608 const char *aBgImg = P("bgim");
1609 int szBgImg = atoi(PD("bgim:bytes","0"));
1610 if( szLogoImg>0 ){
1611 zLogoMime = PD("logoim:mimetype","image/gif");
@@ -1667,11 +1669,11 @@
1669 cgi_redirect("setup_logo");
1670 }
1671 style_header("Edit Project Logo And Background");
1672 @ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
1673 @ and looks like this:</p>
1674 @ <blockquote><p><img src="%s(g.zTop)/logo/%z(zLogoMtime)" alt="logo" border="1" />
1675 @ </p></blockquote>
1676 @
1677 @ <form action="%s(g.zTop)/setup_logo" method="post"
1678 @ enctype="multipart/form-data"><div>
1679 @ <p>The logo is accessible to all users at this URL:
@@ -1689,11 +1691,11 @@
1691 @ </div></form>
1692 @ <hr />
1693 @
1694 @ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
1695 @ and looks like this:</p>
1696 @ <blockquote><p><img src="%s(g.zTop)/background/%z(zBgMtime)" alt="background" border=1 />
1697 @ </p></blockquote>
1698 @
1699 @ <form action="%s(g.zTop)/setup_logo" method="post"
1700 @ enctype="multipart/form-data"><div>
1701 @ <p>The background image is accessible to all users at this URL:
1702
+4 -2
--- src/setup.c
+++ src/setup.c
@@ -1597,13 +1597,15 @@
15971597
15981598
/*
15991599
** WEBPAGE: setup_logo
16001600
*/
16011601
void setup_logo(void){
1602
+ const char *zLogoMtime = db_get_mtime("logo-image", 0, 0);
16021603
const char *zLogoMime = db_get("logo-mimetype","image/gif");
16031604
const char *aLogoImg = P("logoim");
16041605
int szLogoImg = atoi(PD("logoim:bytes","0"));
1606
+ const char *zBgMtime = db_get_mtime("background-image", 0, 0);
16051607
const char *zBgMime = db_get("background-mimetype","image/gif");
16061608
const char *aBgImg = P("bgim");
16071609
int szBgImg = atoi(PD("bgim:bytes","0"));
16081610
if( szLogoImg>0 ){
16091611
zLogoMime = PD("logoim:mimetype","image/gif");
@@ -1667,11 +1669,11 @@
16671669
cgi_redirect("setup_logo");
16681670
}
16691671
style_header("Edit Project Logo And Background");
16701672
@ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
16711673
@ and looks like this:</p>
1672
- @ <blockquote><p><img src="%s(g.zTop)/logo" alt="logo" border="1" />
1674
+ @ <blockquote><p><img src="%s(g.zTop)/logo/%z(zLogoMtime)" alt="logo" border="1" />
16731675
@ </p></blockquote>
16741676
@
16751677
@ <form action="%s(g.zTop)/setup_logo" method="post"
16761678
@ enctype="multipart/form-data"><div>
16771679
@ <p>The logo is accessible to all users at this URL:
@@ -1689,11 +1691,11 @@
16891691
@ </div></form>
16901692
@ <hr />
16911693
@
16921694
@ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
16931695
@ and looks like this:</p>
1694
- @ <blockquote><p><img src="%s(g.zTop)/background" alt="background" border=1 />
1696
+ @ <blockquote><p><img src="%s(g.zTop)/background/%z(zBgMtime)" alt="background" border=1 />
16951697
@ </p></blockquote>
16961698
@
16971699
@ <form action="%s(g.zTop)/setup_logo" method="post"
16981700
@ enctype="multipart/form-data"><div>
16991701
@ <p>The background image is accessible to all users at this URL:
17001702
--- src/setup.c
+++ src/setup.c
@@ -1597,13 +1597,15 @@
1597
1598 /*
1599 ** WEBPAGE: setup_logo
1600 */
1601 void setup_logo(void){
 
1602 const char *zLogoMime = db_get("logo-mimetype","image/gif");
1603 const char *aLogoImg = P("logoim");
1604 int szLogoImg = atoi(PD("logoim:bytes","0"));
 
1605 const char *zBgMime = db_get("background-mimetype","image/gif");
1606 const char *aBgImg = P("bgim");
1607 int szBgImg = atoi(PD("bgim:bytes","0"));
1608 if( szLogoImg>0 ){
1609 zLogoMime = PD("logoim:mimetype","image/gif");
@@ -1667,11 +1669,11 @@
1667 cgi_redirect("setup_logo");
1668 }
1669 style_header("Edit Project Logo And Background");
1670 @ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
1671 @ and looks like this:</p>
1672 @ <blockquote><p><img src="%s(g.zTop)/logo" alt="logo" border="1" />
1673 @ </p></blockquote>
1674 @
1675 @ <form action="%s(g.zTop)/setup_logo" method="post"
1676 @ enctype="multipart/form-data"><div>
1677 @ <p>The logo is accessible to all users at this URL:
@@ -1689,11 +1691,11 @@
1689 @ </div></form>
1690 @ <hr />
1691 @
1692 @ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
1693 @ and looks like this:</p>
1694 @ <blockquote><p><img src="%s(g.zTop)/background" alt="background" border=1 />
1695 @ </p></blockquote>
1696 @
1697 @ <form action="%s(g.zTop)/setup_logo" method="post"
1698 @ enctype="multipart/form-data"><div>
1699 @ <p>The background image is accessible to all users at this URL:
1700
--- src/setup.c
+++ src/setup.c
@@ -1597,13 +1597,15 @@
1597
1598 /*
1599 ** WEBPAGE: setup_logo
1600 */
1601 void setup_logo(void){
1602 const char *zLogoMtime = db_get_mtime("logo-image", 0, 0);
1603 const char *zLogoMime = db_get("logo-mimetype","image/gif");
1604 const char *aLogoImg = P("logoim");
1605 int szLogoImg = atoi(PD("logoim:bytes","0"));
1606 const char *zBgMtime = db_get_mtime("background-image", 0, 0);
1607 const char *zBgMime = db_get("background-mimetype","image/gif");
1608 const char *aBgImg = P("bgim");
1609 int szBgImg = atoi(PD("bgim:bytes","0"));
1610 if( szLogoImg>0 ){
1611 zLogoMime = PD("logoim:mimetype","image/gif");
@@ -1667,11 +1669,11 @@
1669 cgi_redirect("setup_logo");
1670 }
1671 style_header("Edit Project Logo And Background");
1672 @ <p>The current project logo has a MIME-Type of <b>%h(zLogoMime)</b>
1673 @ and looks like this:</p>
1674 @ <blockquote><p><img src="%s(g.zTop)/logo/%z(zLogoMtime)" alt="logo" border="1" />
1675 @ </p></blockquote>
1676 @
1677 @ <form action="%s(g.zTop)/setup_logo" method="post"
1678 @ enctype="multipart/form-data"><div>
1679 @ <p>The logo is accessible to all users at this URL:
@@ -1689,11 +1691,11 @@
1691 @ </div></form>
1692 @ <hr />
1693 @
1694 @ <p>The current background image has a MIME-Type of <b>%h(zBgMime)</b>
1695 @ and looks like this:</p>
1696 @ <blockquote><p><img src="%s(g.zTop)/background/%z(zBgMtime)" alt="background" border=1 />
1697 @ </p></blockquote>
1698 @
1699 @ <form action="%s(g.zTop)/setup_logo" method="post"
1700 @ enctype="multipart/form-data"><div>
1701 @ <p>The background image is accessible to all users at this URL:
1702
+8 -8
--- src/skins.c
+++ src/skins.c
@@ -171,11 +171,11 @@
171171
@ <head>
172172
@ <base href="$baseurl/$current_page" />
173173
@ <title>$<project_name>: $<title></title>
174174
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
175175
@ href="$home/timeline.rss">
176
-@ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
176
+@ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
177177
@ media="screen">
178178
@ </head>
179179
@ <body>
180180
@ <div class="header">
181181
@ <div class="title"><small>$<project_name></small><br />$<title></div>
@@ -379,11 +379,11 @@
379379
@ <head>
380380
@ <base href="$baseurl/$current_page" />
381381
@ <title>$<project_name>: $<title></title>
382382
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
383383
@ href="$home/timeline.rss">
384
-@ <link rel="stylesheet" href="$home/style.css?tan" type="text/css"
384
+@ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
385385
@ media="screen">
386386
@ </head>
387387
@ <body>
388388
@ <div class="header">
389389
@ <div class="title">$<title></div>
@@ -620,17 +620,17 @@
620620
@ <head>
621621
@ <base href="$baseurl/$current_page" />
622622
@ <title>$<project_name>: $<title></title>
623623
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
624624
@ href="$home/timeline.rss">
625
-@ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
625
+@ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
626626
@ media="screen">
627627
@ </head>
628628
@ <body>
629629
@ <div class="header">
630630
@ <div class="logo">
631
-@ <img src="$home/logo" alt="logo">
631
+@ <img src="$logo_image_url" alt="logo">
632632
@ <br />$<project_name>
633633
@ </div>
634634
@ <div class="title">$<title></div>
635635
@ <div class="status"><th1>
636636
@ if {[info exists login]} {
@@ -881,17 +881,17 @@
881881
@ <head>
882882
@ <base href="$baseurl/$current_page" />
883883
@ <title>$<project_name>: $<title></title>
884884
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
885885
@ href="$home/timeline.rss">
886
-@ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
886
+@ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
887887
@ media="screen">
888888
@ </head>
889889
@ <body>
890890
@ <div class="header">
891891
@ <div class="logo">
892
-@ <img src="$home/logo" alt="logo">
892
+@ <img src="$logo_image_url" alt="logo">
893893
@ <br />$<project_name>
894894
@ </div>
895895
@ <div class="title">$<title></div>
896896
@ <div class="status"><th1>
897897
@ if {[info exists login]} {
@@ -1109,11 +1109,11 @@
11091109
@ <head>
11101110
@ <base href="$baseurl/$current_page" />
11111111
@ <title>$<project_name>: $<title></title>
11121112
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
11131113
@ href="$home/timeline.rss" />
1114
-@ <link rel="stylesheet" href="$home/style.css?enhanced" type="text/css"
1114
+@ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
11151115
@ media="screen" />
11161116
@ </head>
11171117
@ <body>
11181118
@ <div class="header">
11191119
@ <div class="logo">
@@ -1176,11 +1176,11 @@
11761176
@ return $logourl
11771177
@ }
11781178
@ set logourl [getLogoUrl $baseurl]
11791179
@ </th1>
11801180
@ <a href="$logourl">
1181
-@ <img src="$baseurl/logo" border="0" alt="$project_name">
1181
+@ <img src="$logo_image_url" border="0" alt="$project_name">
11821182
@ </a>
11831183
@ </div>
11841184
@ <div class="title"><small>$<project_name></small><br />$<title></div>
11851185
@ <div class="status"><th1>
11861186
@ if {[info exists login]} {
11871187
--- src/skins.c
+++ src/skins.c
@@ -171,11 +171,11 @@
171 @ <head>
172 @ <base href="$baseurl/$current_page" />
173 @ <title>$<project_name>: $<title></title>
174 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
175 @ href="$home/timeline.rss">
176 @ <link rel="stylesheet" href="$home/style.css?blackwhite" type="text/css"
177 @ media="screen">
178 @ </head>
179 @ <body>
180 @ <div class="header">
181 @ <div class="title"><small>$<project_name></small><br />$<title></div>
@@ -379,11 +379,11 @@
379 @ <head>
380 @ <base href="$baseurl/$current_page" />
381 @ <title>$<project_name>: $<title></title>
382 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
383 @ href="$home/timeline.rss">
384 @ <link rel="stylesheet" href="$home/style.css?tan" type="text/css"
385 @ media="screen">
386 @ </head>
387 @ <body>
388 @ <div class="header">
389 @ <div class="title">$<title></div>
@@ -620,17 +620,17 @@
620 @ <head>
621 @ <base href="$baseurl/$current_page" />
622 @ <title>$<project_name>: $<title></title>
623 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
624 @ href="$home/timeline.rss">
625 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
626 @ media="screen">
627 @ </head>
628 @ <body>
629 @ <div class="header">
630 @ <div class="logo">
631 @ <img src="$home/logo" alt="logo">
632 @ <br />$<project_name>
633 @ </div>
634 @ <div class="title">$<title></div>
635 @ <div class="status"><th1>
636 @ if {[info exists login]} {
@@ -881,17 +881,17 @@
881 @ <head>
882 @ <base href="$baseurl/$current_page" />
883 @ <title>$<project_name>: $<title></title>
884 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
885 @ href="$home/timeline.rss">
886 @ <link rel="stylesheet" href="$home/style.css?black2" type="text/css"
887 @ media="screen">
888 @ </head>
889 @ <body>
890 @ <div class="header">
891 @ <div class="logo">
892 @ <img src="$home/logo" alt="logo">
893 @ <br />$<project_name>
894 @ </div>
895 @ <div class="title">$<title></div>
896 @ <div class="status"><th1>
897 @ if {[info exists login]} {
@@ -1109,11 +1109,11 @@
1109 @ <head>
1110 @ <base href="$baseurl/$current_page" />
1111 @ <title>$<project_name>: $<title></title>
1112 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
1113 @ href="$home/timeline.rss" />
1114 @ <link rel="stylesheet" href="$home/style.css?enhanced" type="text/css"
1115 @ media="screen" />
1116 @ </head>
1117 @ <body>
1118 @ <div class="header">
1119 @ <div class="logo">
@@ -1176,11 +1176,11 @@
1176 @ return $logourl
1177 @ }
1178 @ set logourl [getLogoUrl $baseurl]
1179 @ </th1>
1180 @ <a href="$logourl">
1181 @ <img src="$baseurl/logo" border="0" alt="$project_name">
1182 @ </a>
1183 @ </div>
1184 @ <div class="title"><small>$<project_name></small><br />$<title></div>
1185 @ <div class="status"><th1>
1186 @ if {[info exists login]} {
1187
--- src/skins.c
+++ src/skins.c
@@ -171,11 +171,11 @@
171 @ <head>
172 @ <base href="$baseurl/$current_page" />
173 @ <title>$<project_name>: $<title></title>
174 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
175 @ href="$home/timeline.rss">
176 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
177 @ media="screen">
178 @ </head>
179 @ <body>
180 @ <div class="header">
181 @ <div class="title"><small>$<project_name></small><br />$<title></div>
@@ -379,11 +379,11 @@
379 @ <head>
380 @ <base href="$baseurl/$current_page" />
381 @ <title>$<project_name>: $<title></title>
382 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
383 @ href="$home/timeline.rss">
384 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
385 @ media="screen">
386 @ </head>
387 @ <body>
388 @ <div class="header">
389 @ <div class="title">$<title></div>
@@ -620,17 +620,17 @@
620 @ <head>
621 @ <base href="$baseurl/$current_page" />
622 @ <title>$<project_name>: $<title></title>
623 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
624 @ href="$home/timeline.rss">
625 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
626 @ media="screen">
627 @ </head>
628 @ <body>
629 @ <div class="header">
630 @ <div class="logo">
631 @ <img src="$logo_image_url" alt="logo">
632 @ <br />$<project_name>
633 @ </div>
634 @ <div class="title">$<title></div>
635 @ <div class="status"><th1>
636 @ if {[info exists login]} {
@@ -881,17 +881,17 @@
881 @ <head>
882 @ <base href="$baseurl/$current_page" />
883 @ <title>$<project_name>: $<title></title>
884 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
885 @ href="$home/timeline.rss">
886 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
887 @ media="screen">
888 @ </head>
889 @ <body>
890 @ <div class="header">
891 @ <div class="logo">
892 @ <img src="$logo_image_url" alt="logo">
893 @ <br />$<project_name>
894 @ </div>
895 @ <div class="title">$<title></div>
896 @ <div class="status"><th1>
897 @ if {[info exists login]} {
@@ -1109,11 +1109,11 @@
1109 @ <head>
1110 @ <base href="$baseurl/$current_page" />
1111 @ <title>$<project_name>: $<title></title>
1112 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
1113 @ href="$home/timeline.rss" />
1114 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
1115 @ media="screen" />
1116 @ </head>
1117 @ <body>
1118 @ <div class="header">
1119 @ <div class="logo">
@@ -1176,11 +1176,11 @@
1176 @ return $logourl
1177 @ }
1178 @ set logourl [getLogoUrl $baseurl]
1179 @ </th1>
1180 @ <a href="$logourl">
1181 @ <img src="$logo_image_url" border="0" alt="$project_name">
1182 @ </a>
1183 @ </div>
1184 @ <div class="title"><small>$<project_name></small><br />$<title></div>
1185 @ <div class="status"><th1>
1186 @ if {[info exists login]} {
1187
+38 -2
--- src/style.c
+++ src/style.c
@@ -243,10 +243,41 @@
243243
va_start(ap, zFormat);
244244
local_zCurrentPage = vmprintf(zFormat, ap);
245245
va_end(ap);
246246
}
247247
}
248
+
249
+/*
250
+** Create a TH1 variable containing the URL for the specified config resource.
251
+** The resulting variable name will be of the form $[zVarPrefix]_url.
252
+*/
253
+static void url_var(
254
+ const char *zVarPrefix,
255
+ const char *zConfigName,
256
+ const char *zPageName
257
+){
258
+ char *zMtime = db_get_mtime(zConfigName, 0, 0);
259
+ char *zUrl = mprintf("%s/%s/%s%.5s", g.zTop, zPageName, zMtime,
260
+ MANIFEST_UUID);
261
+ char *zVarName = mprintf("%s_url", zVarPrefix);
262
+ Th_Store(zVarName, zUrl);
263
+ free(zMtime);
264
+ free(zUrl);
265
+ free(zVarName);
266
+}
267
+
268
+/*
269
+** Create a TH1 variable containing the URL for the specified config image.
270
+** The resulting variable name will be of the form $[zImageName]_image_url.
271
+*/
272
+static void image_url_var(const char *zImageName){
273
+ char *zVarPrefix = mprintf("%s_image", zImageName);
274
+ char *zConfigName = mprintf("%s-image", zImageName);
275
+ url_var(zVarPrefix, zConfigName, zImageName);
276
+ free(zVarPrefix);
277
+ free(zConfigName);
278
+}
248279
249280
/*
250281
** Draw the header.
251282
*/
252283
void style_header(const char *zTitleFormat, ...){
@@ -275,10 +306,13 @@
275306
Th_Store("csrf_token", g.zCsrfToken);
276307
Th_Store("release_version", RELEASE_VERSION);
277308
Th_Store("manifest_version", MANIFEST_VERSION);
278309
Th_Store("manifest_date", MANIFEST_DATE);
279310
Th_Store("compiler_name", COMPILER_NAME);
311
+ url_var("stylesheet", "css", "style.css");
312
+ image_url_var("logo");
313
+ image_url_var("background");
280314
if( g.zLogin ){
281315
Th_Store("login", g.zLogin);
282316
}
283317
if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
284318
Th_Render(zHeader);
@@ -406,17 +440,17 @@
406440
@ <head>
407441
@ <base href="$baseurl/$current_page" />
408442
@ <title>$<project_name>: $<title></title>
409443
@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
410444
@ href="$home/timeline.rss" />
411
-@ <link rel="stylesheet" href="$home/style.css?default" type="text/css"
445
+@ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
412446
@ media="screen" />
413447
@ </head>
414448
@ <body>
415449
@ <div class="header">
416450
@ <div class="logo">
417
-@ <img src="$home/logo" alt="logo" />
451
+@ <img src="$logo_image_url" alt="logo" />
418452
@ </div>
419453
@ <div class="title"><small>$<project_name></small><br />$<title></div>
420454
@ <div class="status"><th1>
421455
@ if {[info exists login]} {
422456
@ puts "Logged in as $login"
@@ -1151,10 +1185,12 @@
11511185
/* Process through TH1 in order to give an opportunity to substitute
11521186
** variables such as $baseurl.
11531187
*/
11541188
Th_Store("baseurl", g.zBaseURL);
11551189
Th_Store("home", g.zTop);
1190
+ image_url_var("logo");
1191
+ image_url_var("background");
11561192
Th_Render(blob_str(&css));
11571193
11581194
/* Tell CGI that the content returned by this page is considered cacheable */
11591195
g.isConst = 1;
11601196
}
11611197
--- src/style.c
+++ src/style.c
@@ -243,10 +243,41 @@
243 va_start(ap, zFormat);
244 local_zCurrentPage = vmprintf(zFormat, ap);
245 va_end(ap);
246 }
247 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
249 /*
250 ** Draw the header.
251 */
252 void style_header(const char *zTitleFormat, ...){
@@ -275,10 +306,13 @@
275 Th_Store("csrf_token", g.zCsrfToken);
276 Th_Store("release_version", RELEASE_VERSION);
277 Th_Store("manifest_version", MANIFEST_VERSION);
278 Th_Store("manifest_date", MANIFEST_DATE);
279 Th_Store("compiler_name", COMPILER_NAME);
 
 
 
280 if( g.zLogin ){
281 Th_Store("login", g.zLogin);
282 }
283 if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
284 Th_Render(zHeader);
@@ -406,17 +440,17 @@
406 @ <head>
407 @ <base href="$baseurl/$current_page" />
408 @ <title>$<project_name>: $<title></title>
409 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
410 @ href="$home/timeline.rss" />
411 @ <link rel="stylesheet" href="$home/style.css?default" type="text/css"
412 @ media="screen" />
413 @ </head>
414 @ <body>
415 @ <div class="header">
416 @ <div class="logo">
417 @ <img src="$home/logo" alt="logo" />
418 @ </div>
419 @ <div class="title"><small>$<project_name></small><br />$<title></div>
420 @ <div class="status"><th1>
421 @ if {[info exists login]} {
422 @ puts "Logged in as $login"
@@ -1151,10 +1185,12 @@
1151 /* Process through TH1 in order to give an opportunity to substitute
1152 ** variables such as $baseurl.
1153 */
1154 Th_Store("baseurl", g.zBaseURL);
1155 Th_Store("home", g.zTop);
 
 
1156 Th_Render(blob_str(&css));
1157
1158 /* Tell CGI that the content returned by this page is considered cacheable */
1159 g.isConst = 1;
1160 }
1161
--- src/style.c
+++ src/style.c
@@ -243,10 +243,41 @@
243 va_start(ap, zFormat);
244 local_zCurrentPage = vmprintf(zFormat, ap);
245 va_end(ap);
246 }
247 }
248
249 /*
250 ** Create a TH1 variable containing the URL for the specified config resource.
251 ** The resulting variable name will be of the form $[zVarPrefix]_url.
252 */
253 static void url_var(
254 const char *zVarPrefix,
255 const char *zConfigName,
256 const char *zPageName
257 ){
258 char *zMtime = db_get_mtime(zConfigName, 0, 0);
259 char *zUrl = mprintf("%s/%s/%s%.5s", g.zTop, zPageName, zMtime,
260 MANIFEST_UUID);
261 char *zVarName = mprintf("%s_url", zVarPrefix);
262 Th_Store(zVarName, zUrl);
263 free(zMtime);
264 free(zUrl);
265 free(zVarName);
266 }
267
268 /*
269 ** Create a TH1 variable containing the URL for the specified config image.
270 ** The resulting variable name will be of the form $[zImageName]_image_url.
271 */
272 static void image_url_var(const char *zImageName){
273 char *zVarPrefix = mprintf("%s_image", zImageName);
274 char *zConfigName = mprintf("%s-image", zImageName);
275 url_var(zVarPrefix, zConfigName, zImageName);
276 free(zVarPrefix);
277 free(zConfigName);
278 }
279
280 /*
281 ** Draw the header.
282 */
283 void style_header(const char *zTitleFormat, ...){
@@ -275,10 +306,13 @@
306 Th_Store("csrf_token", g.zCsrfToken);
307 Th_Store("release_version", RELEASE_VERSION);
308 Th_Store("manifest_version", MANIFEST_VERSION);
309 Th_Store("manifest_date", MANIFEST_DATE);
310 Th_Store("compiler_name", COMPILER_NAME);
311 url_var("stylesheet", "css", "style.css");
312 image_url_var("logo");
313 image_url_var("background");
314 if( g.zLogin ){
315 Th_Store("login", g.zLogin);
316 }
317 if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
318 Th_Render(zHeader);
@@ -406,17 +440,17 @@
440 @ <head>
441 @ <base href="$baseurl/$current_page" />
442 @ <title>$<project_name>: $<title></title>
443 @ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
444 @ href="$home/timeline.rss" />
445 @ <link rel="stylesheet" href="$stylesheet_url" type="text/css"
446 @ media="screen" />
447 @ </head>
448 @ <body>
449 @ <div class="header">
450 @ <div class="logo">
451 @ <img src="$logo_image_url" alt="logo" />
452 @ </div>
453 @ <div class="title"><small>$<project_name></small><br />$<title></div>
454 @ <div class="status"><th1>
455 @ if {[info exists login]} {
456 @ puts "Logged in as $login"
@@ -1151,10 +1185,12 @@
1185 /* Process through TH1 in order to give an opportunity to substitute
1186 ** variables such as $baseurl.
1187 */
1188 Th_Store("baseurl", g.zBaseURL);
1189 Th_Store("home", g.zTop);
1190 image_url_var("logo");
1191 image_url_var("background");
1192 Th_Render(blob_str(&css));
1193
1194 /* Tell CGI that the content returned by this page is considered cacheable */
1195 g.isConst = 1;
1196 }
1197

Keyboard Shortcuts

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