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 ** drh@hwaci.com
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 /*
26 ** WEBPAGE: timeline.rss
27 ** URL: /timeline.rss?y=TYPE&n=LIMIT&tkt=HASH&tag=TAG&wiki=NAME&name=FILENAME
28 **
29 ** Produce an RSS feed of the timeline.
30 **
31 ** TYPE may be: all, ci (show check-ins only), t (show ticket changes only),
32 ** w (show wiki only), e (show tech notes only), f (show forum posts only),
33 ** g (show tag/branch changes only).
34 **
35 ** LIMIT is the number of items to show.
36 **
37 ** tkt=HASH filters for only those events for the specified ticket. tag=TAG
38 ** filters for a tag, and wiki=NAME for a wiki page. Only one may be used.
39 **
40 ** In addition, name=FILENAME filters for a specific file. This may be
41 ** combined with one of the other filters (useful for looking at a specific
42 ** branch).
43 */
44
page_timeline_rss(void)45 void page_timeline_rss(void){
46 Stmt q;
47 int nLine=0;
48 char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
49 Blob bSQL;
50 const char *zType = PD("y","all"); /* Type of events. All if NULL */
51 const char *zTicketUuid = PD("tkt",NULL);
52 const char *zTag = PD("tag",NULL);
53 const char *zFilename = PD("name",NULL);
54 const char *zWiki = PD("wiki",NULL);
55 int nLimit = atoi(PD("n","20"));
56 int nTagId;
57 const char zSQL1[] =
58 @ SELECT
59 @ blob.rid,
60 @ uuid,
61 @ event.mtime,
62 @ coalesce(ecomment,comment),
63 @ coalesce(euser,user),
64 @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
65 @ (SELECT count(*) FROM plink WHERE cid=blob.rid),
66 @ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
67 @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
68 @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags
69 @ FROM event, blob
70 @ WHERE blob.rid=event.objid
71 ;
72
73 login_check_credentials();
74 if( !g.perm.Read && !g.perm.RdTkt && !g.perm.RdWiki ){
75 return;
76 }
77
78 blob_zero(&bSQL);
79 blob_append( &bSQL, zSQL1, -1 );
80
81 if( zType[0]!='a' ){
82 if( zType[0]=='c' && !g.perm.Read ) zType = "x";
83 if( zType[0]=='w' && !g.perm.RdWiki ) zType = "x";
84 if( zType[0]=='t' && !g.perm.RdTkt ) zType = "x";
85 blob_append_sql(&bSQL, " AND event.type=%Q", zType);
86 }else{
87 if( !g.perm.Read ){
88 if( g.perm.RdTkt && g.perm.RdWiki ){
89 blob_append(&bSQL, " AND event.type!='ci'", -1);
90 }else if( g.perm.RdTkt ){
91 blob_append(&bSQL, " AND event.type=='t'", -1);
92
93 }else{
94 blob_append(&bSQL, " AND event.type=='w'", -1);
95 }
96 }else if( !g.perm.RdWiki ){
97 if( g.perm.RdTkt ){
98 blob_append(&bSQL, " AND event.type!='w'", -1);
99 }else{
100 blob_append(&bSQL, " AND event.type=='ci'", -1);
101 }
102 }else if( !g.perm.RdTkt ){
103 assert( !g.perm.RdTkt && g.perm.Read && g.perm.RdWiki );
104 blob_append(&bSQL, " AND event.type!='t'", -1);
105 }
106 }
107
108 if( zTicketUuid ){
109 nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
110 zTicketUuid);
111 if ( nTagId==0 ){
112 nTagId = -1;
113 }
114 }else if( zTag ){
115 nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'",
116 zTag);
117 if ( nTagId==0 ){
118 nTagId = -1;
119 }
120 }else if( zWiki ){
121 nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'",
122 zWiki);
123 if ( nTagId==0 ){
124 nTagId = -1;
125 }
126 }else{
127 nTagId = 0;
128 }
129
130 if( nTagId==-1 ){
131 blob_append_sql(&bSQL, " AND 0");
132 }else if( nTagId!=0 ){
133 blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref"
134 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId);
135 }
136
137 if( zFilename ){
138 blob_append_sql(&bSQL,
139 " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)",
140 zFilename, filename_collation()
141 );
142 }
143
144 blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
145
146 cgi_set_content_type("application/rss+xml");
147
148 zProjectName = db_get("project-name", 0);
149 if( zProjectName==0 ){
150 zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s",
151 g.zBaseURL);
152 }
153 zProjectDescr = db_get("project-description", 0);
154 if( zProjectDescr==0 ){
155 zProjectDescr = zProjectName;
156 }
157
158 zPubDate = cgi_rfc822_datestamp(time(NULL));
159
160 @ <?xml version="1.0"?>
161 @ <rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
162 @ <channel>
163 @ <title>%h(zProjectName)</title>
164 @ <link>%s(g.zBaseURL)</link>
165 @ <description>%h(zProjectDescr)</description>
166 @ <pubDate>%s(zPubDate)</pubDate>
167 @ <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
168 free(zPubDate);
169 db_prepare(&q, "%s", blob_sql_text(&bSQL));
170 blob_reset( &bSQL );
171 while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){
172 const char *zId = db_column_text(&q, 1);
173 const char *zCom = db_column_text(&q, 3);
174 const char *zAuthor = db_column_text(&q, 4);
175 char *zPrefix = "";
176 char *zSuffix = 0;
177 char *zDate;
178 int nChild = db_column_int(&q, 5);
179 int nParent = db_column_int(&q, 6);
180 const char *zTagList = db_column_text(&q, 7);
181 time_t ts;
182
183 if( zTagList && zTagList[0]==0 ) zTagList = 0;
184 ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0);
185 zDate = cgi_rfc822_datestamp(ts);
186
187 if( nParent>1 && nChild>1 ){
188 zPrefix = "*MERGE/FORK* ";
189 }else if( nParent>1 ){
190 zPrefix = "*MERGE* ";
191 }else if( nChild>1 ){
192 zPrefix = "*FORK* ";
193 }
194
195 if( zTagList ){
196 zSuffix = mprintf(" (tags: %s)", zTagList);
197 }
198
199 @ <item>
200 @ <title>%s(zPrefix)%h(zCom)%h(zSuffix)</title>
201 @ <link>%s(g.zBaseURL)/info/%s(zId)</link>
202 @ <description>%s(zPrefix)%h(zCom)%h(zSuffix)</description>
203 @ <pubDate>%s(zDate)</pubDate>
204 @ <dc:creator>%h(zAuthor)</dc:creator>
205 @ <guid>%s(g.zBaseURL)/info/%s(zId)</guid>
206 @ </item>
207 free(zDate);
208 free(zSuffix);
209 nLine++;
210 }
211
212 db_finalize(&q);
213 @ </channel>
214 @ </rss>
215
216 if( zFreeProjectName != 0 ){
217 free( zFreeProjectName );
218 }
219 }
220
221 /*
222 ** COMMAND: rss*
223 **
224 ** Usage: %fossil rss ?OPTIONS?
225 **
226 ** The CLI variant of the /timeline.rss page, this produces an RSS
227 ** feed of the timeline to stdout. Options:
228 **
229 ** -type|y FLAG May be: all (default), ci (show check-ins only),
230 ** t (show tickets only), w (show wiki only).
231 **
232 ** -limit|n LIMIT The maximum number of items to show.
233 **
234 ** -tkt HASH Filter for only those events for the specified ticket.
235 **
236 ** -tag TAG Filter for a tag
237 **
238 ** -wiki NAME Filter on a specific wiki page.
239 **
240 ** Only one of -tkt, -tag, or -wiki may be used.
241 **
242 ** -name FILENAME Filter for a specific file. This may be combined
243 ** with one of the other filters (useful for looking
244 ** at a specific branch).
245 **
246 ** -url STRING Set the RSS feed's root URL to the given string.
247 ** The default is "URL-PLACEHOLDER" (without quotes).
248 */
cmd_timeline_rss(void)249 void cmd_timeline_rss(void){
250 Stmt q;
251 int nLine=0;
252 char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
253 Blob bSQL;
254 const char *zType = find_option("type","y",1); /* Type of events. All if NULL */
255 const char *zTicketUuid = find_option("tkt",NULL,1);
256 const char *zTag = find_option("tag",NULL,1);
257 const char *zFilename = find_option("name",NULL,1);
258 const char *zWiki = find_option("wiki",NULL,1);
259 const char *zLimit = find_option("limit", "n",1);
260 const char *zBaseURL = find_option("url", NULL, 1);
261 int nLimit = atoi( (zLimit && *zLimit) ? zLimit : "20" );
262 int nTagId;
263 const char zSQL1[] =
264 @ SELECT
265 @ blob.rid,
266 @ uuid,
267 @ event.mtime,
268 @ coalesce(ecomment,comment),
269 @ coalesce(euser,user),
270 @ (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
271 @ (SELECT count(*) FROM plink WHERE cid=blob.rid),
272 @ (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref
273 @ WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
274 @ AND tagxref.rid=blob.rid AND tagxref.tagtype>0) AS tags
275 @ FROM event, blob
276 @ WHERE blob.rid=event.objid
277 ;
278 if(!zType || !*zType){
279 zType = "all";
280 }
281 if(!zBaseURL || !*zBaseURL){
282 zBaseURL = "URL-PLACEHOLDER";
283 }
284
285 db_find_and_open_repository(0, 0);
286
287 /* We should be done with options.. */
288 verify_all_options();
289
290 blob_zero(&bSQL);
291 blob_append( &bSQL, zSQL1, -1 );
292
293 if( zType[0]!='a' ){
294 blob_append_sql(&bSQL, " AND event.type=%Q", zType);
295 }
296
297 if( zTicketUuid ){
298 nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
299 zTicketUuid);
300 if ( nTagId==0 ){
301 nTagId = -1;
302 }
303 }else if( zTag ){
304 nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'",
305 zTag);
306 if ( nTagId==0 ){
307 nTagId = -1;
308 }
309 }else if( zWiki ){
310 nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'",
311 zWiki);
312 if ( nTagId==0 ){
313 nTagId = -1;
314 }
315 }else{
316 nTagId = 0;
317 }
318
319 if( nTagId==-1 ){
320 blob_append_sql(&bSQL, " AND 0");
321 }else if( nTagId!=0 ){
322 blob_append_sql(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref"
323 " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId);
324 }
325
326 if( zFilename ){
327 blob_append_sql(&bSQL,
328 " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)",
329 zFilename, filename_collation()
330 );
331 }
332
333 blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );
334
335 zProjectName = db_get("project-name", 0);
336 if( zProjectName==0 ){
337 zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s",
338 zBaseURL);
339 }
340 zProjectDescr = db_get("project-description", 0);
341 if( zProjectDescr==0 ){
342 zProjectDescr = zProjectName;
343 }
344
345 zPubDate = cgi_rfc822_datestamp(time(NULL));
346
347 fossil_print("<?xml version=\"1.0\"?>");
348 fossil_print("<rss xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"2.0\">");
349 fossil_print("<channel>\n");
350 fossil_print("<title>%h</title>\n", zProjectName);
351 fossil_print("<link>%s</link>\n", zBaseURL);
352 fossil_print("<description>%h</description>\n", zProjectDescr);
353 fossil_print("<pubDate>%s</pubDate>\n", zPubDate);
354 fossil_print("<generator>Fossil version %s %s</generator>\n",
355 MANIFEST_VERSION, MANIFEST_DATE);
356 free(zPubDate);
357 db_prepare(&q, "%s", blob_sql_text(&bSQL));
358 blob_reset( &bSQL );
359 while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){
360 const char *zId = db_column_text(&q, 1);
361 const char *zCom = db_column_text(&q, 3);
362 const char *zAuthor = db_column_text(&q, 4);
363 char *zPrefix = "";
364 char *zSuffix = 0;
365 char *zDate;
366 int nChild = db_column_int(&q, 5);
367 int nParent = db_column_int(&q, 6);
368 const char *zTagList = db_column_text(&q, 7);
369 time_t ts;
370
371 if( zTagList && zTagList[0]==0 ) zTagList = 0;
372 ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0);
373 zDate = cgi_rfc822_datestamp(ts);
374
375 if( nParent>1 && nChild>1 ){
376 zPrefix = "*MERGE/FORK* ";
377 }else if( nParent>1 ){
378 zPrefix = "*MERGE* ";
379 }else if( nChild>1 ){
380 zPrefix = "*FORK* ";
381 }
382
383 if( zTagList ){
384 zSuffix = mprintf(" (tags: %s)", zTagList);
385 }
386
387 fossil_print("<item>");
388 fossil_print("<title>%s%h%h</title>\n", zPrefix, zCom, zSuffix);
389 fossil_print("<link>%s/info/%s</link>\n", zBaseURL, zId);
390 fossil_print("<description>%s%h%h</description>\n", zPrefix, zCom, zSuffix);
391 fossil_print("<pubDate>%s</pubDate>\n", zDate);
392 fossil_print("<dc:creator>%h</dc:creator>\n", zAuthor);
393 fossil_print("<guid>%s/info/%s</guid>\n", g.zBaseURL, zId);
394 fossil_print("</item>\n");
395 free(zDate);
396 free(zSuffix);
397 nLine++;
398 }
399
400 db_finalize(&q);
401 fossil_print("</channel>\n");
402 fossil_print("</rss>\n");
403
404 if( zFreeProjectName != 0 ){
405 free( zFreeProjectName );
406 }
407 }
408