/[james]/sargasso2/feed.c
ViewVC logotype

Annotation of /sargasso2/feed.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 78 - (hide annotations) (download) (as text)
Sat Sep 30 21:34:24 2006 UTC (17 years, 7 months ago) by james
File MIME type: text/x-csrc
File size: 19515 byte(s)
Limit to 3 simultaneous fetches.

1 james 77 /*
2     * This file is part of Sargasso, http://zamez.org/sargasso
3     * Licensed under the GNU General Public License,
4     * http://www.opensource.org/licenses/gpl-license
5     * Copyright 2006 James Bursa <james@zamez.org>
6     */
7    
8     #include <assert.h>
9     #include <ctype.h>
10     #include <errno.h>
11     #include <stdio.h>
12     #include <stdlib.h>
13     #include <string.h>
14     #include <libxml/debugXML.h>
15     #include "feed.h"
16    
17    
18     unsigned int feed_count = 0;
19     struct feed *feeds = 0;
20     bool feed_work_needed = false;
21     const char *feed_error = 0;
22     static const char *feed_status_name[] = { "NEW", "FETCHING", "OK", "ERROR" };
23     static CURLM *curl_multi_handle;
24 james 78 static unsigned int fetching = 0;
25     #define MAX_FETCHES 3
26 james 77
27    
28     static void feed_set_status(struct feed *feed, feed_status status);
29     static void feed_work_feed(struct feed *feed);
30     static void feed_create_fetch(struct feed *feed);
31     static void feed_start_fetch(struct feed *feed);
32     static size_t feed_header_callback(void *ptr, size_t size, size_t nmemb,
33     void *stream);
34     static size_t feed_write_callback(void *ptr, size_t size, size_t nmemb,
35     void *stream);
36     static void feed_fetched(struct feed *feed, CURLcode result);
37     static void feed_parse(struct feed *feed);
38     static void feed_parse_item(struct feed *feed, xmlNode *node);
39     static void feed_free_item(struct feed *feed, unsigned int i);
40     static void feed_clean_text(xmlChar *text);
41    
42    
43     /**
44     * Initialise the feed module.
45     */
46    
47     bool feed_init(void)
48     {
49     CURLcode code;
50    
51     code = curl_global_init(CURL_GLOBAL_ALL);
52     if (code != CURLE_OK) {
53     feed_error = curl_easy_strerror(code);
54     return false;
55     }
56    
57     curl_multi_handle = curl_multi_init();
58     if (!curl_multi_handle) {
59     feed_error = "Failed to initialise curl";
60     return false;
61     }
62    
63     xmlInitParser();
64    
65     return true;
66     }
67    
68    
69     /**
70     * Quit the feed module.
71     */
72    
73     void feed_quit(void)
74     {
75     while (feed_count)
76     feed_remove(0);
77     free(feeds);
78     feeds = 0;
79    
80     xmlCleanupParser();
81    
82     curl_multi_cleanup(curl_multi_handle);
83     }
84    
85    
86     /**
87     * Add a new feed.
88     */
89    
90     bool feed_add(const char *url)
91     {
92     struct feed *feeds1;
93     struct feed *feed;
94     char *url1;
95     unsigned int i;
96    
97     assert(url);
98    
99     feeds1 = realloc(feeds, sizeof *feed * (feed_count + 1));
100     if (!feeds1) {
101     feed_error = "Out of memory";
102     return false;
103     }
104     feeds = feeds1;
105    
106     url1 = strdup(url);
107     if (!url1) {
108     feed_error = "Out of memory";
109     free(url1);
110     return false;
111     }
112    
113     feed = &feeds[feed_count];
114     feed->url = url1;
115     feed->status = FEED_NEW;
116     feed->error = 0;
117     feed->status_line = 0;
118     feed->etag = 0;
119     feed->redirect = 0;
120     feed->data = 0;
121     feed->data_size = 0;
122     feed->title = 0;
123     feed->description = 0;
124     feed->link = 0;
125     feed->copyright = 0;
126     feed->pub_date = 0;
127     feed->category = 0;
128     for (i = 0; i != FEED_MAX_ITEMS; i++) {
129     feed->item[i].title = 0;
130     feed->item[i].description = 0;
131     feed->item[i].link = 0;
132     feed->item[i].author = 0;
133     feed->item[i].pub_date = 0;
134     feed->item[i].category = 0;
135     feed->item[i].guid = 0;
136     feed->item[i].new_item = false;
137     }
138     feed->item_count = 0;
139    
140     feed_count++;
141     feed_work_needed = true;
142    
143     printf("added feed %s\n", url);
144     return true;
145     }
146    
147    
148     /**
149     * Remove a feed.
150     */
151    
152     bool feed_remove(unsigned int i)
153     {
154     unsigned int j;
155    
156     assert(i < feed_count);
157    
158     if (feeds[i].status == FEED_FETCHING) {
159     curl_multi_remove_handle(curl_multi_handle, feeds[i].curl);
160     curl_easy_cleanup(feeds[i].curl);
161     curl_slist_free_all(feeds[i].headers);
162     feeds[i].headers = 0;
163     }
164    
165     for (j = 0; j != feeds[i].item_count; j++)
166     feed_free_item(&feeds[i], j);
167    
168     free(feeds[i].url);
169     free(feeds[i].status_line);
170     free(feeds[i].etag);
171     free(feeds[i].redirect);
172     free(feeds[i].data);
173     if (feeds[i].title)
174     xmlFree(feeds[i].title);
175     if (feeds[i].description)
176     xmlFree(feeds[i].description);
177     if (feeds[i].link)
178     xmlFree(feeds[i].link);
179     if (feeds[i].copyright)
180     xmlFree(feeds[i].copyright);
181     if (feeds[i].pub_date)
182     xmlFree(feeds[i].pub_date);
183     if (feeds[i].category)
184     xmlFree(feeds[i].category);
185    
186     if (i != feed_count - 1)
187     memmove(&feeds[i], &feeds[i + 1],
188     (sizeof feeds[0]) * (feed_count - i - 1));
189    
190     feed_count--;
191    
192     return true;
193     }
194    
195    
196     /**
197     * Set the status of a feed.
198     */
199    
200     void feed_set_status(struct feed *feed, feed_status status)
201     {
202     printf("status %s %s => %s\n", feed->url,
203     feed_status_name[feed->status],
204     feed_status_name[status]);
205     feed->status = status;
206     feed->updated = true;
207     }
208    
209    
210     /**
211     * Do some work on the feeds.
212     */
213    
214     bool feed_work(void)
215     {
216     unsigned int i;
217     int running;
218     int queue;
219     CURLMsg *msg;
220     struct feed *feed;
221    
222     for (i = 0; i != feed_count; i++) {
223     feeds[i].updated = false;
224     feed_work_feed(&feeds[i]);
225     }
226    
227     feed_work_needed = false;
228    
229     while (curl_multi_perform(curl_multi_handle, &running) ==
230     CURLM_CALL_MULTI_PERFORM)
231     continue;
232    
233     if ((msg = curl_multi_info_read(curl_multi_handle, &queue))) {
234     if (msg->msg == CURLMSG_DONE) {
235     curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE,
236     &feed);
237     feed_fetched(feed, msg->data.result);
238     }
239     }
240    
241 james 78 for (i = 0; i != feed_count; i++)
242     if (feeds[i].status == FEED_NEW ||
243     feeds[i].status == FEED_FETCHING ||
244     feeds[i].status == FEED_UPDATE)
245     feed_work_needed = true;
246 james 77
247     for (i = 0; i != feed_count; i++)
248     if (feeds[i].updated)
249     return true;
250     return false;
251     }
252    
253    
254     /**
255     * Do some work on a feed.
256     */
257    
258     void feed_work_feed(struct feed *feed)
259     {
260     assert(feed);
261    
262 james 78 if ((feed->status == FEED_NEW || feed->status == FEED_UPDATE) &&
263     fetching < MAX_FETCHES) {
264 james 77 feed_create_fetch(feed);
265     if (feed->status != FEED_ERROR)
266     feed_start_fetch(feed);
267     }
268     }
269    
270    
271     /**
272     * Create a fetch for a feed.
273     */
274    
275     void feed_create_fetch(struct feed *feed)
276     {
277     CURL *curl;
278     struct curl_slist *headers = 0;
279     struct curl_slist *headers2 = 0;
280    
281     curl = curl_easy_init();
282     if (!curl) {
283     feed_set_status(feed, FEED_ERROR);
284     feed->error = "Failed to create curl session";
285     return;
286     }
287    
288     headers2 = curl_slist_append(headers, "Accept: "
289     "application/rss+xml, application/xml, text/xml");
290     if (!headers2) {
291 james 78 curl_easy_cleanup(feed);
292 james 77 curl_slist_free_all(headers);
293     feed_set_status(feed, FEED_ERROR);
294     feed->error = "Out of memory";
295     return;
296     }
297     headers = headers2;
298    
299     if (feed->etag) {
300     size_t n = 20 + strlen(feed->etag);
301     char if_none_match[n];
302     snprintf(if_none_match, n, "If-None-Match: %s", feed->etag);
303     headers2 = curl_slist_append(headers, if_none_match);
304     if (!headers2) {
305 james 78 curl_easy_cleanup(curl);
306 james 77 curl_slist_free_all(headers);
307     feed_set_status(feed, FEED_ERROR);
308     feed->error = "Out of memory";
309     return;
310     }
311     headers = headers2;
312     }
313    
314     curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
315     curl_easy_setopt(curl, CURLOPT_URL, feed->url);
316     curl_easy_setopt(curl, CURLOPT_PRIVATE, feed);
317     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, feed_write_callback);
318     curl_easy_setopt(curl, CURLOPT_WRITEDATA, feed);
319     curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, feed_header_callback);
320     curl_easy_setopt(curl, CURLOPT_HEADERDATA, feed);
321     curl_easy_setopt(curl, CURLOPT_USERAGENT,
322     "Sargasso (http://zamez.org/sargasso)");
323     curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
324     curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
325     curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, 1L);
326     curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, 60L);
327     curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
328     curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
329    
330     feed->curl = curl;
331     feed->headers = headers;
332     feed->redirect_count = 0;
333     }
334    
335    
336     /**
337     * Start fetching a feed.
338     */
339    
340     void feed_start_fetch(struct feed *feed)
341     {
342     CURLMcode mcode;
343    
344     mcode = curl_multi_add_handle(curl_multi_handle, feed->curl);
345     if (mcode != CURLM_OK) {
346     feed_set_status(feed, FEED_ERROR);
347     feed->error = curl_multi_strerror(mcode);
348     curl_easy_cleanup(feed->curl);
349     curl_slist_free_all(feed->headers);
350     feed->curl = 0;
351     feed->headers = 0;
352     return;
353     }
354    
355     free(feed->status_line);
356     feed->status_line = 0;
357     feed->data_size = 0;
358     feed->error = 0;
359     feed_set_status(feed, FEED_FETCHING);
360    
361     printf("fetching feed %s\n", feed->url);
362    
363 james 78 fetching++;
364 james 77 feed_work_needed = true;
365     }
366    
367    
368     /**
369     * Callback for receiving headers for a feed.
370     */
371    
372     size_t feed_header_callback(void *ptr, size_t size, size_t nmemb,
373     void *stream)
374     {
375     struct feed *feed = (struct feed *) stream;
376     size_t n = size * nmemb;
377     char header[n + 1];
378     char *value;
379    
380     strncpy(header, ptr, n);
381     header[n] = 0;
382     while (0 < n && header[n - 1] <= 32)
383     header[--n] = 0;
384    
385     if (!feed->status_line) {
386     feed->status_line = strdup(header);
387     return size * nmemb;
388     }
389    
390     value = strchr(header, ':');
391     if (!value)
392     return size * nmemb;
393     *value = 0;
394     value++;
395     while (isspace(*value))
396     value++;
397    
398     if (strcasecmp(header, "ETag") == 0 && value[0] == '"') {
399     free(feed->etag);
400     feed->etag = strdup(value);
401     } else if (strcasecmp(header, "Location") == 0) {
402     free(feed->redirect);
403     feed->redirect = strdup(value);
404     }
405    
406     return size * nmemb;
407     }
408    
409    
410     /**
411     * Callback for receiving data for a feed.
412     */
413    
414     size_t feed_write_callback(void *ptr, size_t size, size_t nmemb,
415     void *stream)
416     {
417     struct feed *feed = (struct feed *) stream;
418     char *data;
419    
420     printf("received %u for %s\n", size * nmemb, feed->url);
421    
422     data = realloc(feed->data, feed->data_size + size * nmemb);
423     if (!data) {
424     feed_set_status(feed, FEED_ERROR);
425     feed->error = "Out of memory";
426     return 0;
427     }
428    
429     memcpy(data + feed->data_size, ptr, size * nmemb);
430     feed->data = data;
431     feed->data_size += size * nmemb;
432    
433     return size * nmemb;
434     }
435    
436    
437     /**
438     * Process a complete feed fetch.
439     */
440    
441     void feed_fetched(struct feed *feed, CURLcode result)
442     {
443     long http_code;
444    
445     printf("finished %s with result %i %s\n",
446     feed->url, result, curl_easy_strerror(result));
447    
448 james 78 fetching--;
449    
450 james 77 if (result == CURLE_OK) {
451     curl_easy_getinfo(feed->curl, CURLINFO_RESPONSE_CODE,
452     &http_code);
453     printf("HTTP code %li\n", http_code);
454     if (http_code == 0 || http_code == 200 /* OK */) {
455     feed_parse(feed);
456     } else if (http_code == 300 /* Multiple Choices */ ||
457     http_code == 301 /* Moved Permanently */ ||
458     http_code == 302 /* Found */ ||
459     http_code == 303 /* See Other */ ||
460     http_code == 307 /* Temporary Redirect */) {
461     if (feed->redirect_count++ == 5) {
462     feed_set_status(feed, FEED_ERROR);
463     feed->error = "Too many redirects.";
464     } else if (feed->redirect) {
465     curl_multi_remove_handle(curl_multi_handle,
466     feed->curl);
467     curl_easy_setopt(feed->curl, CURLOPT_URL,
468     feed->redirect);
469     feed_start_fetch(feed);
470     if (http_code == 301 /* Moved Permanently */) {
471     free(feed->url);
472     feed->url = feed->redirect;
473     feed->redirect = 0;
474     }
475     return;
476     } else {
477     feed_set_status(feed, FEED_ERROR);
478     feed->error = "Invalid redirect.";
479     }
480     } else if (http_code == 304 /* Not Modified */) {
481     feed_set_status(feed, FEED_OK);
482     } else {
483     feed_set_status(feed, FEED_ERROR);
484     if (feed->status_line)
485     feed->error = feed->status_line;
486     else
487     feed->error = "Response not understood.";
488     }
489    
490     } else {
491     feed_set_status(feed, FEED_ERROR);
492     if (!feed->error)
493     feed->error = curl_easy_strerror(result);
494     }
495    
496     curl_multi_remove_handle(curl_multi_handle, feed->curl);
497     curl_easy_cleanup(feed->curl);
498     feed->curl = 0;
499     curl_slist_free_all(feed->headers);
500     feed->headers = 0;
501     free(feed->data);
502     feed->data = 0;
503     }
504    
505    
506     /**
507     * Parse a feed's XML.
508     */
509    
510     void feed_parse(struct feed *feed)
511     {
512     xmlDoc *doc;
513     xmlNode *rss;
514     xmlNode *channel;
515     xmlNode *node;
516    
517     assert(feed);
518     assert(feed->status == FEED_FETCHING);
519    
520     doc = xmlReadMemory(feed->data, feed->data_size, feed->url, 0, 0);
521     if (!doc) {
522     feed_set_status(feed, FEED_ERROR);
523     feed->error = "failed to parse XML";
524     return;
525     }
526     //xmlDebugDumpDocument(stdout, doc);
527    
528 james 78 free(feed->data);
529     feed->data = 0;
530    
531 james 77 for (rss = doc->children; rss; rss = rss->next)
532     if (rss->type == XML_ELEMENT_NODE && !strcmp(rss->name, "rss"))
533     break;
534     if (!rss) {
535     feed_set_status(feed, FEED_ERROR);
536     feed->error = "rss element not found";
537     xmlFreeDoc(doc);
538     return;
539     }
540    
541     for (channel = rss->children; channel; channel = channel->next)
542     if (channel->type == XML_ELEMENT_NODE &&
543     !strcmp(channel->name, "channel"))
544     break;
545     if (!channel) {
546     feed_set_status(feed, FEED_ERROR);
547     feed->error = "channel element not found";
548     xmlFreeDoc(doc);
549     return;
550     }
551    
552     for (node = channel->last; node; node = node->prev) {
553     if (node->type != XML_ELEMENT_NODE)
554     continue;
555    
556     if (!strcmp(node->name, "title")) {
557     if (feed->title)
558     xmlFree(feed->title);
559     feed->title = xmlNodeGetContent(node);
560    
561     } else if (!strcmp(node->name, "description")) {
562     if (feed->description)
563     xmlFree(feed->description);
564     feed->description = xmlNodeGetContent(node);
565    
566     } else if (!strcmp(node->name, "link")) {
567     if (feed->link)
568     xmlFree(feed->link);
569     feed->link = xmlNodeGetContent(node);
570    
571     } else if (!strcmp(node->name, "copyright")) {
572     if (feed->copyright)
573     xmlFree(feed->copyright);
574     feed->copyright = xmlNodeGetContent(node);
575    
576     } else if (!strcmp(node->name, "pubDate")) {
577     if (feed->pub_date)
578     xmlFree(feed->pub_date);
579     feed->pub_date = xmlNodeGetContent(node);
580    
581     } else if (!strcmp(node->name, "category")) {
582     if (feed->category)
583     xmlFree(feed->category);
584     feed->category = xmlNodeGetContent(node);
585    
586     } else if (!strcmp(node->name, "item")) {
587     feed_parse_item(feed, node);
588     }
589     }
590    
591     xmlFreeDoc(doc);
592    
593     feed_clean_text(feed->title);
594     feed_clean_text(feed->description);
595     feed_clean_text(feed->link);
596     feed_clean_text(feed->copyright);
597     feed_clean_text(feed->category);
598    
599     feed_set_status(feed, FEED_OK);
600     }
601    
602    
603     void feed_parse_item(struct feed *feed, xmlNode *node)
604     {
605     xmlNode *child;
606     xmlChar *title = 0;
607     xmlChar *description = 0;
608     xmlChar *link = 0;
609     xmlChar *author = 0;
610     xmlChar *pub_date = 0;
611     xmlChar *category = 0;
612     xmlChar *guid = 0;
613     unsigned int i;
614    
615     for (child = node->children; child; child = child->next) {
616     if (child->type != XML_ELEMENT_NODE)
617     continue;
618     if (!strcmp(child->name, "title"))
619     title = xmlNodeGetContent(child);
620     else if (!strcmp(child->name, "description"))
621     description = xmlNodeGetContent(child);
622     else if (!strcmp(child->name, "link"))
623     link = xmlNodeGetContent(child);
624     else if (!strcmp(child->name, "author"))
625     author = xmlNodeGetContent(child);
626     else if (!strcmp(child->name, "pubDate"))
627     pub_date = xmlNodeGetContent(child);
628     else if (!strcmp(child->name, "category"))
629     category = xmlNodeGetContent(child);
630     else if (!strcmp(child->name, "guid"))
631     guid = xmlNodeGetContent(child);
632     }
633    
634     feed_clean_text(title);
635     feed_clean_text(description);
636     feed_clean_text(link);
637     feed_clean_text(author);
638     feed_clean_text(category);
639     feed_clean_text(guid);
640    
641     for (i = 0; i != feed->item_count; i++) {
642     if (guid) {
643     if (feed->item[i].guid &&
644     !strcmp(feed->item[i].guid, guid))
645     break;
646     } else if (link) {
647     if (feed->item[i].link &&
648     !strcmp(feed->item[i].link, link))
649     break;
650     }
651     }
652     if (i != feed->item_count) {
653     /* old item */
654     feed_free_item(feed, i);
655     feed->item[i].title = title;
656     feed->item[i].description = description;
657     feed->item[i].link = link;
658     feed->item[i].author = author;
659     feed->item[i].pub_date = pub_date;
660     feed->item[i].category = category;
661     feed->item[i].guid = guid;
662    
663     } else {
664     /* new item */
665     if (feed->item_count == FEED_MAX_ITEMS)
666     feed_free_item(feed, FEED_MAX_ITEMS - 1);
667     memmove(feed->item + 1, feed->item,
668     sizeof *feed->item * (FEED_MAX_ITEMS - 1));
669     feed->item[0].title = title;
670     feed->item[0].description = description;
671     feed->item[0].link = link;
672     feed->item[0].author = author;
673     feed->item[0].pub_date = pub_date;
674     feed->item[0].category = category;
675     feed->item[0].guid = guid;
676     feed->item[0].new_item = true;
677     if (feed->item_count != FEED_MAX_ITEMS)
678     feed->item_count++;
679     }
680     }
681    
682    
683     void feed_free_item(struct feed *feed, unsigned int i)
684     {
685     if (feed->item[i].title)
686     xmlFree(feed->item[i].title);
687     if (feed->item[i].description)
688     xmlFree(feed->item[i].description);
689     if (feed->item[i].link)
690     xmlFree(feed->item[i].link);
691     if (feed->item[i].author)
692     xmlFree(feed->item[i].author);
693     if (feed->item[i].pub_date)
694     xmlFree(feed->item[i].pub_date);
695     if (feed->item[i].category)
696     xmlFree(feed->item[i].category);
697     if (feed->item[i].guid)
698     xmlFree(feed->item[i].guid);
699     }
700    
701    
702     void feed_clean_text(xmlChar *text)
703     {
704     xmlChar *s, *d;
705    
706     if (!text)
707     return;
708    
709     s = d = text;
710     while (*s) {
711     char *gt;
712     if (*s == '<' && (gt = strchr(s, '>'))) {
713     if (s[1] == '/' && s[2] == 't' && s[3] == 'd')
714     *d++ = ' ', *d++ = '|', *d++ = ' ';
715     else if (s[1] == 'b' && s[2] == 'r')
716     *d++ = 0xe2, *d++ = 0x80, *d++ = 0x94;
717     s = gt + 1;
718     } else if (*s == '&') {
719     if (s[1] == '#' && s[2] == '3' && s[3] == '9' &&
720     s[4] == ';')
721     *d++ = '\'', s += 5;
722     else if (s[1] == 'n' && s[2] == 'b' && s[3] == 's' &&
723     s[4] == 'p' && s[5] == ';')
724     *d++ = ' ', s += 6;
725     else if (s[1] == 'q' && s[2] == 'u' && s[3] == 'o' &&
726     s[4] == 't' && s[5] == ';')
727     *d++ = '"', s += 6;
728     else if (s[1] == 'a' && s[2] == 'm' && s[3] == 'p' &&
729     s[4] == ';')
730     *d++ = '&', s += 5;
731     else if (s[1] == 'c' && s[2] == 'o' && s[3] == 'p' &&
732     s[4] == 'y' && s[5] == ';')
733     *d++ = 0xc2, *d++ = 0xa9, s += 6;
734     else
735     *d++ = *s++;
736     } else
737     *d++ = *s++;
738     }
739     *d = 0;
740    
741     /* collapse whitespace */
742     s = d = text;
743     while (*s == '\t' || *s == '\r' || *s == '\n' || *s == ' ')
744     s++;
745     while (*s) {
746     while (*s && !(*s == '\t' || *s == '\r' || *s == '\n' ||
747     *s == ' '))
748     *d++ = *s++;
749     if (*s)
750     *d++ = ' ';
751     while (*s && (*s == '\t' || *s == '\r' || *s == '\n' ||
752     *s == ' '))
753     s++;
754     }
755     *d = 0;
756     }
757    
758    
759     /**
760     * Start updating all feeds.
761     */
762    
763     void feed_update(void)
764     {
765     unsigned int i;
766    
767     for (i = 0; i != feed_count; i++)
768     feeds[i].status = FEED_UPDATE;
769    
770     feed_work_needed = true;
771     }
772    
773    
774     /**
775     * Load list of feeds.
776     */
777    
778     bool feed_list_load(const char *path)
779     {
780     FILE *stream;
781     char url[4000];
782    
783     stream = fopen(path, "r");
784     if (!stream) {
785     feed_error = strerror(errno);
786     return false;
787     }
788    
789     while (!feof(stream)) {
790     url[0] = 0;
791     fgets(url, sizeof url, stream);
792     if (url[0] == 0 || url[0] == '\n')
793     continue;
794     url[strlen(url) - 1] = 0;
795     if (!feed_add(url)) {
796     fclose(stream);
797     return false;
798     }
799     }
800    
801     if (fclose(stream)) {
802     feed_error = strerror(errno);
803     return false;
804     }
805    
806     return true;
807     }
808    
809    
810     /**
811     * Save list of feeds.
812     */
813    
814     bool feed_list_save(const char *path)
815     {
816     FILE *stream;
817     unsigned int i;
818    
819     stream = fopen(path, "w");
820     if (!stream) {
821     feed_error = strerror(errno);
822     return false;
823     }
824    
825     for (i = 0; i != feed_count; i++) {
826     fputs(feeds[i].url, stream);
827     fputc('\n', stream);
828     }
829    
830     if (fclose(stream)) {
831     feed_error = strerror(errno);
832     return false;
833     }
834    
835     return true;
836     }
837    
838    
839     /**
840     * Output a feed.
841     */
842    
843     void feed_print(struct feed *feed)
844     {
845     unsigned int i;
846    
847     assert(feed);
848    
849     printf("URL: %s\n", feed->url);
850     printf("Status: %s\n", feed_status_name[feed->status]);
851    
852     if (feed->status == FEED_OK) {
853     printf("Title: %s\n", feed->title);
854     printf("Description: %s\n", feed->description);
855     printf("Link: %s\n", feed->link);
856     printf("Copyright: %s\n", feed->copyright);
857     printf("Publ'n date: %s\n", feed->pub_date);
858     printf("Category: %s\n", feed->category);
859     for (i = 0; i != feed->item_count; i++) {
860     printf(" Title: %s\n", feed->item[i].title);
861     printf(" Description: %s\n",
862     feed->item[i].description);
863     printf(" Link: %s\n", feed->item[i].link);
864     printf(" Author: %s\n", feed->item[i].author);
865     printf(" Publ'n date: %s\n", feed->item[i].pub_date);
866     printf(" Category: %s\n", feed->item[i].category);
867     printf(" GUID: %s\n", feed->item[i].guid);
868     printf(" New item: %s\n", feed->item[i].new_item ?
869     "yes" : "no");
870     }
871     } else if (feed->status == FEED_ERROR) {
872     printf("Error: %s\n", feed->error);
873     }
874     }

  ViewVC Help
Powered by ViewVC 1.1.26