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

Annotation of /sargasso2/feed.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 77 - (hide annotations) (download) (as text)
Tue Sep 26 21:31:28 2006 UTC (17 years, 7 months ago) by james
File MIME type: text/x-csrc
File size: 19179 byte(s)
Rewrite of Sargasso in C.

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

  ViewVC Help
Powered by ViewVC 1.1.26