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

Contents of /sargasso2/feed.c

Parent Directory Parent Directory | Revision Log Revision Log


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

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
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