Fossil SCM

fossil-scm / src / rss.c
Blame History Raw 761 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code used to create a RSS feed for the CGI interface.
19
*/
20
#include "config.h"
21
#include <time.h>
22
#include "rss.h"
23
#include <assert.h>
24
25
void forum_render_to_html(struct Blob*, const char*, const char*);
26
char *technote_render_to_html(struct Blob*, int);
27
28
/*
29
** Append text to pOut, escaping any CDATA terminators.
30
*/
31
static void rss_cdata_append(Blob *pOut, const char *zIn, int nIn){
32
const char *zEnd;
33
const char *zDelim;
34
if( pOut==0 ) return;
35
if( zIn==0 ) zIn = "";
36
if( nIn<0 ) nIn = (int)strlen(zIn);
37
zEnd = zIn + nIn;
38
while( zIn<zEnd && (zDelim = strstr(zIn, "]]>") )!=0 ){
39
if( zDelim>=zEnd ) break;
40
blob_append(pOut, zIn, (int)(zDelim - zIn));
41
blob_append_literal(pOut, "]]]]><![CDATA[>");
42
zIn = zDelim + 3;
43
}
44
if( zIn<zEnd ){
45
blob_append(pOut, zIn, (int)(zEnd - zIn));
46
}
47
}
48
49
/*
50
** Return true if zIn looks like an absolute URL.
51
*/
52
static int rss_is_absolute_url(const char *zIn, int nIn){
53
int i;
54
if( zIn==0 || nIn<=0 ) return 0;
55
if( nIn>=2 && zIn[0]=='/' && zIn[1]=='/' ) return 1;
56
if( !((zIn[0]>='a' && zIn[0]<='z') || (zIn[0]>='A' && zIn[0]<='Z')) ){
57
return 0;
58
}
59
for(i=1; i<nIn; i++){
60
char c = zIn[i];
61
if( (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')
62
|| c=='+' || c=='-' || c=='.' ){
63
continue;
64
}
65
return c==':' ? 1 : 0;
66
}
67
return 0;
68
}
69
70
/*
71
** Return the length of zBase without trailing slashes.
72
*/
73
static int rss_trim_base(const char *zBase){
74
int n = zBase ? (int)strlen(zBase) : 0;
75
while( n>0 && zBase[n-1]=='/' ) n--;
76
return n;
77
}
78
79
/*
80
** Compute the repository top path from zBase and return it.
81
*/
82
static const char *rss_top_from_base(Blob *pTop, const char *zBase){
83
const char *z = zBase;
84
const char *zSlash = 0;
85
int n;
86
if( zBase==0 ) return "";
87
if( strncmp(zBase, "http://", 7)==0 ){
88
z = zBase + 7;
89
}else if( strncmp(zBase, "https://", 8)==0 ){
90
z = zBase + 8;
91
}else{
92
return "";
93
}
94
zSlash = strchr(z, '/');
95
if( zSlash==0 ) return "";
96
n = (int)strlen(zSlash);
97
while( n>1 && zSlash[n-1]=='/' ) n--;
98
blob_init(pTop, zSlash, n);
99
return blob_str(pTop);
100
}
101
102
/*
103
** Append an absolute URL to pOut, using zBase/zTop as the base.
104
*/
105
static void rss_append_abs_url(
106
Blob *pOut,
107
const char *zBase,
108
int nBase,
109
const char *zTop,
110
int nTop,
111
const char *zRel,
112
int nRel
113
){
114
if( pOut==0 ) return;
115
if( zRel==0 || nRel<=0 ) return;
116
if( zBase==0 || zBase[0]==0 ){
117
blob_append(pOut, zRel, nRel);
118
return;
119
}
120
if( zRel[0]=='#' || rss_is_absolute_url(zRel, nRel) ){
121
blob_append(pOut, zRel, nRel);
122
return;
123
}
124
if( zRel[0]=='/' ){
125
if( nTop>1 && strncmp(zRel, zTop, nTop)==0 ){
126
blob_append(pOut, zBase, nBase);
127
blob_append(pOut, zRel + nTop, nRel - nTop);
128
}else{
129
blob_append(pOut, zBase, nBase);
130
blob_append(pOut, zRel, nRel);
131
}
132
}else{
133
blob_append(pOut, zBase, nBase);
134
blob_append_char(pOut, '/');
135
blob_append(pOut, zRel, nRel);
136
}
137
}
138
139
/*
140
** Convert relative href/src attributes in zIn to absolute URLs.
141
*/
142
static void rss_make_abs_links(
143
Blob *pOut,
144
const char *zBase,
145
const char *zTop,
146
const char *zIn,
147
int nIn
148
){
149
const char *z = zIn;
150
const char *zEnd = zIn + nIn;
151
const char *zLast = zIn;
152
int nBase = rss_trim_base(zBase);
153
int nTop = zTop ? (int)strlen(zTop) : 0;
154
if( pOut==0 || zIn==0 ) return;
155
while( z<zEnd ){
156
int nAttr = 0;
157
if( (z>zIn && !(z[-1]==' ' || (z[-1]>='\t' && z[-1]<='\r'))
158
&& z[-1]!='<') ){
159
z++;
160
continue;
161
}
162
if( zEnd - z >= 5
163
&& (z[0]=='h' || z[0]=='H')
164
&& (z[1]=='r' || z[1]=='R')
165
&& (z[2]=='e' || z[2]=='E')
166
&& (z[3]=='f' || z[3]=='F')
167
&& z[4]=='='
168
){
169
nAttr = 5;
170
}else if( zEnd - z >= 4
171
&& (z[0]=='s' || z[0]=='S')
172
&& (z[1]=='r' || z[1]=='R')
173
&& (z[2]=='c' || z[2]=='C')
174
&& z[3]=='='
175
){
176
nAttr = 4;
177
}
178
if( nAttr==0 ){
179
z++;
180
continue;
181
}
182
{
183
const char *zVal = z + nAttr;
184
const char *zValEnd = zVal;
185
char quote = 0;
186
if( zVal>=zEnd ) break;
187
if( *zVal=='"' || *zVal=='\'' ){
188
quote = *zVal;
189
zVal++;
190
}
191
zValEnd = zVal;
192
while( zValEnd<zEnd ){
193
if( quote ){
194
if( *zValEnd==quote ) break;
195
}else if( *zValEnd==' ' || (*zValEnd>='\t' && *zValEnd<='\r')
196
|| *zValEnd=='>' ){
197
break;
198
}
199
zValEnd++;
200
}
201
blob_append(pOut, zLast, zVal - zLast);
202
rss_append_abs_url(pOut, zBase, nBase, zTop, nTop,
203
zVal, (int)(zValEnd - zVal));
204
zLast = zValEnd;
205
z = zValEnd;
206
}
207
}
208
if( zLast<zEnd ) blob_append(pOut, zLast, (int)(zEnd - zLast));
209
}
210
211
/*
212
** Render RSS item HTML content into pOut when applicable.
213
** Return 1 if HTML content was produced, 0 if not, and -1 if the
214
** item should be skipped entirely.
215
** If pzAltLink is not NULL, it may be filled with an alternate link id
216
** for event pages. It remains NULL for non-technote items. The caller
217
** must free that result.
218
*/
219
static int rss_render_item_html(
220
Blob *pOut,
221
char **pzAltLink,
222
int rid,
223
const char *zEType,
224
const char *zBase,
225
const char *zTop
226
){
227
Manifest *pPost = 0;
228
int rc = 0;
229
Blob normalized = BLOB_INITIALIZER;
230
if( pzAltLink ) *pzAltLink = 0;
231
if( pOut==0 || zEType==0 ) return 0;
232
if( zEType[0]=='f' ){
233
if( content_is_private(rid) && !g.perm.ModForum ) return -1;
234
pPost = manifest_get(rid, CFTYPE_FORUM, 0);
235
if( pPost ){
236
forum_render_to_html(pOut, pPost->zMimetype, pPost->zWiki);
237
}
238
}else if( zEType[0]=='e' ){
239
char *zAltLink = technote_render_to_html(pOut, rid);
240
if( pzAltLink ) *pzAltLink = zAltLink;
241
else free(zAltLink);
242
}
243
if( pPost ) manifest_destroy(pPost);
244
if( blob_size(pOut)>0 ){
245
rss_make_abs_links(&normalized, zBase, zTop, blob_str(pOut), blob_size(pOut));
246
blob_reset(pOut);
247
blob_append(pOut, blob_str(&normalized), blob_size(&normalized));
248
rc = 1;
249
}
250
blob_reset(&normalized);
251
return rc;
252
}
253
254
/*
255
** Emit HTML content:encoded for the current web RSS item.
256
*/
257
static void rss_web_emit_html_content(Blob *pHtml){
258
Blob cdata = BLOB_INITIALIZER;
259
@ <dc:format>text/html</dc:format>
260
@ <content:encoded><![CDATA[
261
rss_cdata_append(&cdata, blob_str(pHtml), blob_size(pHtml));
262
cgi_append_content(blob_str(&cdata), blob_size(&cdata));
263
blob_reset(&cdata);
264
@ ]]></content:encoded>
265
}
266
267
/*
268
** Emit HTML content:encoded for the current CLI RSS item.
269
*/
270
static void rss_cli_emit_html_content(Blob *pHtml){
271
Blob cdata = BLOB_INITIALIZER;
272
fossil_print("<dc:format>text/html</dc:format>\n");
273
fossil_print("<content:encoded><![CDATA[");
274
rss_cdata_append(&cdata, blob_str(pHtml), blob_size(pHtml));
275
fossil_print("%s", blob_str(&cdata));
276
blob_reset(&cdata);
277
fossil_print("]]></content:encoded>\n");
278
}
279
280
/*
281
** WEBPAGE: timeline.rss
282
** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=HASH&tag=TAG&wiki=NAME&name=FILENAME
283
**
284
** Produce an RSS feed of the timeline.
285
**
286
** TYPE may be: all, ci (show check-ins only), t (show ticket changes only),
287
** w (show wiki only), e (show tech notes only), f (show forum posts only),
288
** g (show tag/branch changes only).
289
**
290
** LIMIT is the number of items to show.
291
**
292
** tkt=HASH filters for only those events for the specified ticket. tag=TAG
293
** filters for a tag, and wiki=NAME for a wiki page. Only one may be used.
294
**
295
** In addition, name=FILENAME filters for a specific file. This may be
296
** combined with one of the other filters (useful for looking at a specific
297
** branch).
298
*/
299
void page_timeline_rss(void){
300
Stmt q;
301
int nLine=0;
302
char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
303
Blob bSQL;
304
Blob base = BLOB_INITIALIZER;
305
Blob top = BLOB_INITIALIZER;
306
const char *zType = PD("y","all"); /* Type of events. All if NULL */
307
const char *zTicketUuid = PD("tkt",NULL);
308
const char *zTag = PD("tag",NULL);
309
const char *zFilename = PD("name",NULL);
310
const char *zWiki = PD("wiki",NULL);
311
int nLimit = atoi(PD("n","20"));
312
int nTagId;
313
int bHasForum;
314
const char zSQL1[] =
315
@ SELECT
316
@ blob.rid,
317
@ uuid,
318
@ event.mtime,
319
@ event.type,
320
@ coalesce(ecomment,comment),
321
@ coalesce(euser,user),
322
@ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
323
@ (SELECT count(*) FROM plink WHERE cid=blob.rid),
324
@ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
325
@ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
326
@ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags
327
@ FROM event, blob
328
@ WHERE blob.rid=event.objid
329
;
330
331
login_check_credentials();
332
if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki && !g.perm.RdForum ){
333
return;
334
}
335
bHasForum = db_table_exists("repository","forumpost");
336
337
blob_zero(&bSQL);
338
blob_append_sql( &bSQL, "%s", zSQL1/*safe-for-%s*/ );
339
if( bHasForum ){
340
blob_append_sql(&bSQL,
341
" AND (event.type!='f' OR event.objid IN ("
342
"SELECT fpid FROM forumpost "
343
"WHERE fpid NOT IN (SELECT fprev FROM forumpost WHERE fprev IS NOT NULL)"
344
"))"
345
);
346
}else{
347
blob_append_sql(&bSQL, " AND event.type!='f'");
348
}
349
350
if( zType[0]!='a' ){
351
if( zType[0]=='c' && !g.perm.Read ) zType = "x";
352
else if( (zType[0]=='w' || zType[0]=='e') && !g.perm.RdWiki ) zType = "x";
353
else if( zType[0]=='t' && !g.perm.RdTkt ) zType = "x";
354
else if( zType[0]=='f' && !g.perm.RdForum ) zType = "x";
355
blob_append_sql(&bSQL, " AND event.type=%Q", zType);
356
}else{
357
blob_append_sql(&bSQL, " AND event.type in (");
358
if( g.perm.Read ){
359
blob_append_sql(&bSQL, "'ci',");
360
}
361
if( g.perm.RdTkt ){
362
blob_append_sql(&bSQL, "'t',");
363
}
364
if( g.perm.RdWiki ){
365
blob_append_sql(&bSQL, "'w','e',");
366
}
367
if( g.perm.RdForum ){
368
blob_append_sql(&bSQL, "'f',");
369
}
370
blob_append_sql(&bSQL, "'x')");
371
}
372
373
if( zTicketUuid ){
374
nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
375
zTicketUuid);
376
if ( nTagId==0 ){
377
nTagId = -1;
378
}
379
}else if( zTag ){
380
nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'",
381
zTag);
382
if ( nTagId==0 ){
383
nTagId = -1;
384
}
385
}else if( zWiki ){
386
nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'",
387
zWiki);
388
if ( nTagId==0 ){
389
nTagId = -1;
390
}
391
}else{
392
nTagId = 0;
393
}
394
395
if( nTagId==-1 ){
396
blob_append_sql(&bSQL, " AND 0");
397
}else if( nTagId!=0 ){
398
blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref"
399
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId);
400
}
401
402
if( zFilename ){
403
blob_append_sql(&bSQL,
404
" AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) "
405
" IN (SELECT fnid FROM filename WHERE name=%Q %s)",
406
zFilename, filename_collation()
407
);
408
}
409
410
blob_append_sql( &bSQL, " ORDER BY event.mtime DESC" );
411
412
cgi_set_content_type("application/rss+xml");
413
414
zProjectName = db_get("project-name", 0);
415
if( zProjectName==0 ){
416
zFreeProjectName = zProjectName =
417
mprintf("Fossil source repository for: %s", g.zBaseURL);
418
}
419
zProjectDescr = db_get("project-description", 0);
420
if( zProjectDescr==0 ){
421
zProjectDescr = zProjectName;
422
}
423
424
zPubDate = cgi_rfc822_datestamp(time(NULL));
425
blob_append(&base, g.zBaseURL, -1);
426
rss_top_from_base(&top, g.zBaseURL);
427
428
@ <?xml version="1.0"?>
429
@ <rss xmlns:dc="http://purl.org/dc/elements/1.1/" \
430
@ xmlns:content="http://purl.org/rss/1.0/modules/content/" \
431
@ version="2.0">
432
@ <channel>
433
@ <title>%h(zProjectName)</title>
434
@ <link>%s(g.zBaseURL)</link>
435
@ <description>%h(zProjectDescr)</description>
436
@ <pubDate>%s(zPubDate)</pubDate>
437
@ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
438
free(zPubDate);
439
db_prepare(&q, "%s", blob_sql_text(&bSQL));
440
blob_reset( &bSQL );
441
while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){
442
int rid = db_column_int(&q, 0);
443
const char *zId = db_column_text(&q, 1);
444
const char *zEType = db_column_text(&q, 3);
445
const char *zCom = db_column_text(&q, 4);
446
const char *zAuthor = db_column_text(&q, 5);
447
char *zPrefix = "";
448
char *zSuffix = 0;
449
char *zDate;
450
int nChild = db_column_int(&q, 6);
451
int nParent = db_column_int(&q, 7);
452
const char *zTagList = db_column_text(&q, 8);
453
char *zTechnoteId = 0;
454
Blob contentHtml = BLOB_INITIALIZER;
455
int bHasContent = 0;
456
time_t ts;
457
458
if( zTagList && zTagList[0]==0 ) zTagList = 0;
459
ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0);
460
zDate = cgi_rfc822_datestamp(ts);
461
462
if( zEType && zEType[0]=='c' ){
463
if( nParent>1 && nChild>1 ){
464
zPrefix = "*MERGE/FORK* ";
465
}else if( nParent>1 ){
466
zPrefix = "*MERGE* ";
467
}else if( nChild>1 ){
468
zPrefix = "*FORK* ";
469
}
470
}else if( zEType && zEType[0]=='w' ){
471
switch(zCom ? zCom[0] : 0){
472
case ':': zPrefix = "Edit wiki page: "; break;
473
case '+': zPrefix = "Add wiki page: "; break;
474
case '-': zPrefix = "Delete wiki page: "; break;
475
}
476
if(*zPrefix) ++zCom;
477
}
478
479
if( zTagList ){
480
zSuffix = mprintf(" (tags: %s)", zTagList);
481
}
482
483
bHasContent = rss_render_item_html(&contentHtml, &zTechnoteId, rid, zEType,
484
blob_str(&base), blob_str(&top));
485
if( bHasContent>=0 ){
486
@ <item>
487
@ <title>%s(zPrefix)%h(zCom)%h(zSuffix)</title>
488
if( zTechnoteId ){
489
@ <link>%s(g.zBaseURL)/info/%s(zTechnoteId)</link>
490
}else{
491
@ <link>%s(g.zBaseURL)/info/%s(zId)</link>
492
}
493
if( bHasContent ){
494
rss_web_emit_html_content(&contentHtml);
495
}else{
496
@ <description>%s(zPrefix)%h(zCom)%h(zSuffix)</description>
497
}
498
@ <pubDate>%s(zDate)</pubDate>
499
@ <dc:creator>%h(zAuthor)</dc:creator>
500
@ <guid>%s(g.zBaseURL)/info/%s(zId)</guid>
501
@ </item>
502
nLine++;
503
}
504
free(zTechnoteId);
505
blob_reset(&contentHtml);
506
free(zDate);
507
free(zSuffix);
508
}
509
510
db_finalize(&q);
511
blob_reset(&base);
512
blob_reset(&top);
513
@ </channel>
514
@ </rss>
515
516
if( zFreeProjectName != 0 ){
517
free( zFreeProjectName );
518
}
519
}
520
521
/*
522
** COMMAND: rss*
523
**
524
** Usage: %fossil rss ?OPTIONS?
525
**
526
** The CLI variant of the /timeline.rss page, this produces an RSS
527
** feed of the timeline to stdout.
528
**
529
** Options:
530
** -type|y FLAG May be: all (default), ci (show check-ins only),
531
** t (show tickets only), w (show wiki only),
532
** e (show tech notes only), f (show forum posts only)
533
**
534
** -limit|n LIMIT The maximum number of items to show
535
**
536
** -tkt HASH Filter for only those events for the specified ticket
537
**
538
** -tag TAG Filter for a tag
539
**
540
** -wiki NAME Filter on a specific wiki page
541
**
542
** Only one of -tkt, -tag, or -wiki may be used.
543
**
544
** -name FILENAME Filter for a specific file. This may be combined
545
** with one of the other filters (useful for looking
546
** at a specific branch).
547
**
548
** -url STRING Set the RSS feed's root URL to the given string.
549
** The default is "URL-PLACEHOLDER" (without quotes).
550
*/
551
void cmd_timeline_rss(void){
552
Stmt q;
553
int nLine=0;
554
char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
555
Blob bSQL;
556
Blob base = BLOB_INITIALIZER;
557
Blob top = BLOB_INITIALIZER;
558
const char *zType = find_option("type","y",1); /* Type of events;All if NULL*/
559
const char *zTicketUuid = find_option("tkt",NULL,1);
560
const char *zTag = find_option("tag",NULL,1);
561
const char *zFilename = find_option("name",NULL,1);
562
const char *zWiki = find_option("wiki",NULL,1);
563
const char *zLimit = find_option("limit", "n",1);
564
const char *zBaseURL = find_option("url", NULL, 1);
565
int nLimit = atoi( (zLimit && *zLimit) ? zLimit : "20" );
566
int nTagId;
567
int bHasForum;
568
const char zSQL1[] =
569
@ SELECT
570
@ blob.rid,
571
@ uuid,
572
@ event.mtime,
573
@ event.type,
574
@ coalesce(ecomment,comment),
575
@ coalesce(euser,user),
576
@ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
577
@ (SELECT count(*) FROM plink WHERE cid=blob.rid),
578
@ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
579
@ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
580
@ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags
581
@ FROM event, blob
582
@ WHERE blob.rid=event.objid
583
;
584
if(!zType || !*zType){
585
zType = "all";
586
}
587
if(!zBaseURL || !*zBaseURL){
588
zBaseURL = "URL-PLACEHOLDER";
589
}
590
591
db_find_and_open_repository(0, 0);
592
593
/* We should be done with options.. */
594
verify_all_options();
595
596
blob_zero(&bSQL);
597
blob_append( &bSQL, zSQL1, -1 );
598
bHasForum = db_table_exists("repository","forumpost");
599
if( bHasForum ){
600
blob_append_sql(&bSQL,
601
" AND (event.type!='f' OR event.objid IN ("
602
"SELECT fpid FROM forumpost "
603
"WHERE fpid NOT IN (SELECT fprev FROM forumpost WHERE fprev IS NOT NULL)"
604
"))"
605
);
606
}else{
607
blob_append_sql(&bSQL, " AND event.type!='f'");
608
}
609
610
if( zType[0]!='a' ){
611
blob_append_sql(&bSQL, " AND event.type=%Q", zType);
612
}
613
614
if( zTicketUuid ){
615
nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
616
zTicketUuid);
617
if ( nTagId==0 ){
618
nTagId = -1;
619
}
620
}else if( zTag ){
621
nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'",
622
zTag);
623
if ( nTagId==0 ){
624
nTagId = -1;
625
}
626
}else if( zWiki ){
627
nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'",
628
zWiki);
629
if ( nTagId==0 ){
630
nTagId = -1;
631
}
632
}else{
633
nTagId = 0;
634
}
635
636
if( nTagId==-1 ){
637
blob_append_sql(&bSQL, " AND 0");
638
}else if( nTagId!=0 ){
639
blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref"
640
" WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId);
641
}
642
643
if( zFilename ){
644
blob_append_sql(&bSQL,
645
" AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) "
646
" IN (SELECT fnid FROM filename WHERE name=%Q %s)",
647
zFilename, filename_collation()
648
);
649
}
650
651
blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
652
653
zProjectName = db_get("project-name", 0);
654
if( zProjectName==0 ){
655
zFreeProjectName = zProjectName =
656
mprintf("Fossil source repository for: %s", zBaseURL);
657
}
658
zProjectDescr = db_get("project-description", 0);
659
if( zProjectDescr==0 ){
660
zProjectDescr = zProjectName;
661
}
662
663
zPubDate = cgi_rfc822_datestamp(time(NULL));
664
blob_append(&base, zBaseURL, -1);
665
rss_top_from_base(&top, zBaseURL);
666
667
fossil_print("<?xml version=\"1.0\"?>");
668
fossil_print("<rss xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
669
" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\" "
670
" version=\"2.0\">\n");
671
fossil_print("<channel>\n");
672
fossil_print("<title>%h</title>\n", zProjectName);
673
fossil_print("<link>%s</link>\n", zBaseURL);
674
fossil_print("<description>%h</description>\n", zProjectDescr);
675
fossil_print("<pubDate>%s</pubDate>\n", zPubDate);
676
fossil_print("<generator>Fossil version %s %s</generator>\n",
677
MANIFEST_VERSION, MANIFEST_DATE);
678
free(zPubDate);
679
db_prepare(&q, "%s", blob_sql_text(&bSQL));
680
blob_reset( &bSQL );
681
while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){
682
int rid = db_column_int(&q, 0);
683
const char *zId = db_column_text(&q, 1);
684
const char *zEType = db_column_text(&q, 3);
685
const char *zCom = db_column_text(&q, 4);
686
const char *zAuthor = db_column_text(&q, 5);
687
char *zPrefix = "";
688
char *zSuffix = 0;
689
char *zDate;
690
int nChild = db_column_int(&q, 6);
691
int nParent = db_column_int(&q, 7);
692
const char *zTagList = db_column_text(&q, 8);
693
char *zTechnoteId = 0;
694
Blob contentHtml = BLOB_INITIALIZER;
695
int bHasContent = 0;
696
time_t ts;
697
698
if( zTagList && zTagList[0]==0 ) zTagList = 0;
699
ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0);
700
zDate = cgi_rfc822_datestamp(ts);
701
702
if( zEType && zEType[0]=='c' ){
703
if( nParent>1 && nChild>1 ){
704
zPrefix = "*MERGE/FORK* ";
705
}else if( nParent>1 ){
706
zPrefix = "*MERGE* ";
707
}else if( nChild>1 ){
708
zPrefix = "*FORK* ";
709
}
710
}else if( zEType && zEType[0]=='w' ){
711
switch(zCom ? zCom[0] : 0){
712
case ':': zPrefix = "Edit wiki page: "; break;
713
case '+': zPrefix = "Add wiki page: "; break;
714
case '-': zPrefix = "Delete wiki page: "; break;
715
}
716
if(*zPrefix) ++zCom;
717
}
718
719
if( zTagList ){
720
zSuffix = mprintf(" (tags: %s)", zTagList);
721
}
722
723
bHasContent = rss_render_item_html(&contentHtml, &zTechnoteId, rid, zEType,
724
blob_str(&base), blob_str(&top));
725
if( bHasContent>=0 ){
726
fossil_print("<item>");
727
fossil_print("<title>%s%h%h</title>\n", zPrefix, zCom, zSuffix);
728
if( zTechnoteId!=0 ){
729
fossil_print("<link>%s/info/%s</link>\n", zBaseURL, zTechnoteId);
730
}else{
731
fossil_print("<link>%s/info/%s</link>\n", zBaseURL, zId);
732
}
733
if( bHasContent ){
734
rss_cli_emit_html_content(&contentHtml);
735
}else{
736
fossil_print("<description>%s%h%h</description>\n",
737
zPrefix, zCom, zSuffix);
738
}
739
fossil_print("<pubDate>%s</pubDate>\n", zDate);
740
fossil_print("<dc:creator>%h</dc:creator>\n", zAuthor);
741
fossil_print("<guid>%s/info/%s</guid>\n", g.zBaseURL, zId);
742
fossil_print("</item>\n");
743
nLine++;
744
}
745
free(zTechnoteId);
746
blob_reset(&contentHtml);
747
free(zDate);
748
free(zSuffix);
749
}
750
751
db_finalize(&q);
752
blob_reset(&base);
753
blob_reset(&top);
754
fossil_print("</channel>\n");
755
fossil_print("</rss>\n");
756
757
if( zFreeProjectName != 0 ){
758
free( zFreeProjectName );
759
}
760
}
761

Keyboard Shortcuts

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