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

Contents of /sargasso2/feed.c

Parent Directory Parent Directory | Revision Log Revision Log


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

1 /*
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 static unsigned int fetching = 0;
25 #define MAX_FETCHES 3
26
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 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
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 if ((feed->status == FEED_NEW || feed->status == FEED_UPDATE) &&
263 fetching < MAX_FETCHES) {
264 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 curl_easy_cleanup(feed);
292 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 curl_easy_cleanup(curl);
306 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 fetching++;
364 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 fetching--;
449
450 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 free(feed->data);
529 feed->data = 0;
530
531 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