1 /* -*- linux-c -*-
2 Copyright (C) 2004 Tom Szilagyi
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 $Id: podcast.c 1286 2014-04-27 20:50:41Z tszilagyi $
19 */
20
21 #include <config.h>
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <glib.h>
29 #include <libxml/globals.h>
30 #include <libxml/parser.h>
31 #include <libxml/tree.h>
32
33 #include "athread.h"
34 #include "common.h"
35 #include "i18n.h"
36 #include "decoder/file_decoder.h"
37 #include "options.h"
38 #include "httpc.h"
39 #include "store_podcast.h"
40 #include "podcast.h"
41
42
43 #define BUFSIZE 10240
44
45 extern options_t options;
46
47
48 podcast_t *
podcast_new(void)49 podcast_new(void) {
50
51 podcast_t * podcast;
52
53 if ((podcast = (podcast_t *)calloc(1, sizeof(podcast_t))) == NULL) {
54 fprintf(stderr, "podcast_new: calloc error\n");
55 return NULL;
56 }
57
58 podcast->state = PODCAST_STATE_IDLE;
59 podcast->items = NULL;
60
61 return podcast;
62 }
63
64 void
podcast_get_display_name(podcast_t * podcast,char * buf)65 podcast_get_display_name(podcast_t * podcast, char * buf) {
66
67 if (podcast->author != NULL && podcast->title != NULL) {
68 snprintf(buf, MAXLEN-1, "%s: %s", podcast->author, podcast->title);
69 } else if (podcast->title != NULL) {
70 strncpy(buf, podcast->title, MAXLEN-1);
71 } else {
72 strncpy(buf, _("Untitled"), MAXLEN-1);
73 }
74 }
75
76 void
podcast_free(podcast_t * podcast)77 podcast_free(podcast_t * podcast) {
78
79 if (podcast->dir) {
80 free(podcast->dir);
81 }
82 if (podcast->title) {
83 free(podcast->title);
84 }
85 if (podcast->author) {
86 free(podcast->author);
87 }
88 if (podcast->desc) {
89 free(podcast->desc);
90 }
91 if (podcast->url) {
92 free(podcast->url);
93 }
94
95 g_slist_free(podcast->items);
96
97 free(podcast);
98 }
99
100 podcast_item_t *
podcast_item_new(void)101 podcast_item_new(void) {
102
103 podcast_item_t * item;
104
105 if ((item = (podcast_item_t *)calloc(1, sizeof(podcast_item_t))) == NULL) {
106 fprintf(stderr, "podcast_item_new: calloc error\n");
107 return NULL;
108 }
109
110 item->new = 1;
111 item->size = 0;
112 item->date = 0;
113 item->duration = 0.0f;
114
115 return item;
116 }
117
118 void
podcast_item_free(podcast_item_t * item)119 podcast_item_free(podcast_item_t * item) {
120
121 if (item->file) {
122 free(item->file);
123 }
124 if (item->title) {
125 free(item->title);
126 }
127 if (item->desc) {
128 free(item->desc);
129 }
130 if (item->url) {
131 free(item->url);
132 }
133 free(item);
134 }
135
136 gint
podcast_item_compare_date(gconstpointer list1,gconstpointer list2)137 podcast_item_compare_date(gconstpointer list1, gconstpointer list2) {
138
139 unsigned date1 = ((podcast_item_t *)list1)->date;
140 unsigned date2 = ((podcast_item_t *)list2)->date;
141
142 if (date1 < date2) {
143 return 1;
144 } else if (date1 > date2) {
145 return -1;
146 }
147
148 return 0;
149 }
150
151 gint
podcast_item_compare_url(gconstpointer list,gconstpointer url)152 podcast_item_compare_url(gconstpointer list, gconstpointer url) {
153
154 return strcmp(((podcast_item_t *)list)->url, (char * )url);
155 }
156
157
158 char *
podcast_file_from_url(char * url)159 podcast_file_from_url(char * url) {
160
161 char * str;
162 char * valid = "abcdefghijklmnopqrstuvwxyz0123456789";
163 char * lastdot;
164 char * file;
165
166 str = g_ascii_strdown(url, -1);
167 lastdot = strrchr(str, '.');
168 g_strcanon(str, valid, '_');
169
170 if (lastdot != NULL) {
171 *lastdot = '.';
172 }
173
174 if (strstr(str, "http___") != NULL) {
175 file = strdup(str + 7);
176 } else {
177 file = strdup(str);
178 }
179
180 g_free(str);
181
182 return file;
183 }
184
185 unsigned
parse_rfc822(char * str)186 parse_rfc822(char * str) {
187
188 char * months[] = { "Ja", "F", "Mar", "Ap", "May", "Jun",
189 "Jul", "Au", "S", "O", "N", "D" };
190
191 char * tz[] = { "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT",
192 "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M",
193 "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
194 int tzval[] = { 0, 0, -5, -4, -6, -5, -7, -6, -8, -7,
195 -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12,
196 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0 };
197 int i;
198
199 int y = 0; /* year */
200 int m = 0; /* month */
201 int d = 0; /* day */
202 int H = 0; /* hour */
203 int M = 0; /* min */
204 int S = 0; /* sec */
205 char b[16]; /* month name */
206 char z[16]; /* timezone */
207
208
209 if (sscanf(str, "%*[^,], %d %15s %d %d:%d:%d %15s", &d, b, &y, &H, &M, &S, z) == 7) {
210
211 GDate * epoch;
212 GDate * date;
213
214 for (i = 0; i < 12; i++) {
215 if (strstr(b, months[i]) != NULL) {
216 m = i + 1;
217 break;
218 }
219 }
220
221 if (y < 100) {
222 y += 2000;
223 } else if (y < 1000) {
224 y += 1900;
225 }
226
227 if (!g_date_valid_dmy(d, m, y)) {
228 return 0;
229 }
230
231 for (i = 0; i < sizeof(tzval) / sizeof(int); i++) {
232 if (!strcmp(tz[i], z)) {
233 H += tzval[i];
234 break;
235 }
236 }
237
238 if (sscanf(z, "%d", &i) == 1) {
239 H -= i / 100;
240 M -= i % 100;
241 }
242
243 epoch = g_date_new_dmy(1, 1, 1970);
244 date = g_date_new_dmy(d, m, y);
245
246 return g_date_days_between(epoch, date) * 86400 + H * 3600 + M * 60 + S;
247 }
248
249 return 0;
250 }
251
252 unsigned
parse_ymd(char * str)253 parse_ymd(char * str) {
254
255 int a = 0;
256 int b = 0;
257 int c = 0;
258
259 if (sscanf(str, "%d-%d-%d", &a, &b, &c) == 3) {
260
261 GDate * epoch;
262 GDate * date;
263 int y = 0, m = 0, d = 0;
264
265 if (a > 1900) {
266 y = a;
267 m = b;
268 d = c;
269 } else if (c > 1900) {
270 y = c;
271 m = a;
272 d = b;
273 } else {
274 return 0;
275 }
276
277 if (!g_date_valid_dmy(d, m, y)) {
278 return 0;
279 }
280
281 epoch = g_date_new_dmy(1, 1, 1970);
282 date = g_date_new_dmy(d, m, y);
283
284 return g_date_days_between(epoch, date) * 86400;
285 }
286
287 return 0;
288 }
289
290 unsigned
parse_rss_date(char * str)291 parse_rss_date(char * str) {
292
293 GTimeVal tval;
294 unsigned val;
295
296 if ((val = parse_rfc822(str)) > 0) {
297 return val;
298 }
299
300 if ((val = parse_ymd(str)) > 0) {
301 return val;
302 }
303
304 g_get_current_time(&tval);
305 return tval.tv_sec;
306 }
307
308 unsigned
parse_atom_date(char * str)309 parse_atom_date(char * str) {
310
311 GTimeVal tval;
312
313 if (!g_time_val_from_iso8601(str, &tval))
314 {
315 g_get_current_time(&tval);
316 }
317
318 return tval.tv_sec;
319 }
320
321 int
podcast_generic_download(podcast_t * podcast,char * url,char * path,void (* callback)(podcast_download_t *),podcast_download_t * pd)322 podcast_generic_download(podcast_t * podcast, char * url, char * path,
323 void (* callback)(podcast_download_t *), podcast_download_t * pd) {
324
325 http_session_t * session;
326 char buf[BUFSIZE];
327 FILE * out;
328 long long pos = 0;
329 int n_read;
330 int ret;
331 int credit = 5;
332 int penalty = 0;
333 int content_length = 0;
334 int percent = 0;
335 int _percent = 0;
336
337
338 if ((out = fopen(path, "wb")) == NULL) {
339 fprintf(stderr, "podcast_generic_download: unable to open file %s\n", path);
340 return -1;
341 }
342
343 while (credit > 0) {
344
345 if (podcast->state == PODCAST_STATE_ABORTED) {
346 break;
347 }
348
349 if ((session = httpc_new()) == NULL) {
350 fclose(out);
351 unlink(path);
352 return -1;
353 }
354
355 if ((ret = httpc_init(session, NULL, url,
356 options.inet_use_proxy,
357 options.inet_proxy,
358 options.inet_proxy_port,
359 options.inet_noproxy_domains, 0L)) != HTTPC_OK) {
360
361 fprintf(stderr, "podcast_generic_download: httpc_init failed, ret = %d\n", ret);
362 httpc_del(session);
363 --credit;
364 continue;
365 }
366
367 content_length = session->headers.content_length;
368
369 if (httpc_seek(session, pos, SEEK_SET) < -1) {
370 fprintf(stderr, "httpc_seek failed\n");
371 --credit;
372 continue;
373 }
374
375 penalty = 1;
376 while ((n_read = httpc_read(session, buf, BUFSIZE)) > 0) {
377
378 if (podcast->state == PODCAST_STATE_ABORTED) {
379 break;
380 }
381
382 pos += n_read;
383 penalty = 0;
384 fwrite(buf, sizeof(char), n_read, out);
385
386 if (callback != NULL && content_length > 0) {
387 _percent = (int)((100.0 * pos) / content_length);
388 if (_percent > percent) {
389 pd->percent = percent = _percent;
390 callback(pd);
391 }
392 }
393 }
394
395 httpc_close(session);
396 httpc_del(session);
397
398 if (podcast->state == PODCAST_STATE_ABORTED) {
399 break;
400 }
401
402 if (n_read < 0) {
403 credit -= penalty;
404 continue;
405 }
406
407 break;
408 }
409
410 if (podcast->state == PODCAST_STATE_ABORTED || credit == 0) {
411
412 fclose(out);
413 unlink(path);
414 return -1;
415 }
416
417 fclose(out);
418 return 0;
419 }
420
421 void
string_remove_html(char * str)422 string_remove_html(char * str) {
423
424 int i, j;
425
426 if (str == NULL) {
427 return;
428 }
429
430 for (i = j = 0; str[i]; i++) {
431 if (str[i] == '<') {
432 while (str[i] && str[i] != '>') {
433 ++i;
434 }
435 if (str[i] == '\0') {
436 break;
437 }
438 } else {
439 str[j++] = str[i];
440 }
441 }
442
443 str[j] = '\0';
444 }
445
446 void
parse_rss_item(podcast_t * podcast,GSList ** list,xmlDocPtr doc,xmlNodePtr item)447 parse_rss_item(podcast_t * podcast, GSList ** list, xmlDocPtr doc, xmlNodePtr item) {
448
449 podcast_item_t * pitem;
450 xmlNodePtr node;
451
452 if ((pitem = podcast_item_new()) == NULL) {
453 return;
454 }
455
456 for (node = item->xmlChildrenNode; node != NULL; node = node->next) {
457 if (!xmlStrcmp(node->name, (const xmlChar *)"title")) {
458 pitem->title = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
459 } else if (!xmlStrcmp(node->name, (const xmlChar *)"description")) {
460 pitem->desc = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
461 } else if (!xmlStrcmp(node->name, (const xmlChar *)"summary")) {
462 if (pitem->desc == NULL) {
463 pitem->desc = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
464 }
465 } else if (!xmlStrcmp(node->name, (const xmlChar *)"enclosure")) {
466 xmlChar * len;
467 if ((len = xmlGetProp(node, (const xmlChar *)"length")) != NULL) {
468 sscanf((char *)len, "%u", &pitem->size);
469 xmlFree(len);
470 }
471 pitem->url = (char *)xmlGetProp(node, (const xmlChar *)"url");
472 } else if (!xmlStrcmp(node->name, (const xmlChar *)"pubDate")) {
473 xmlChar * tmp = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
474 pitem->date = parse_rss_date((char *)tmp);
475 xmlFree(tmp);
476 }
477 }
478
479 if (pitem->url == NULL) {
480 podcast_item_free(pitem);
481 return;
482 }
483
484 string_remove_html(pitem->desc);
485
486 if (pitem->title == NULL) {
487 pitem->title = strdup(_("Untitled"));
488 }
489
490 if (g_slist_find_custom(*list, pitem->url, podcast_item_compare_url) == NULL) {
491 *list = g_slist_prepend(*list, pitem);
492 } else {
493 podcast_item_free(pitem);
494 }
495 }
496
497 void
parse_rss(podcast_t * podcast,GSList ** list,xmlDocPtr doc,xmlNodePtr rss)498 parse_rss(podcast_t * podcast, GSList ** list, xmlDocPtr doc, xmlNodePtr rss) {
499
500 xmlNodePtr channel;
501 xmlNodePtr node;
502
503
504 for (channel = rss->xmlChildrenNode; channel != NULL; channel = channel->next) {
505 if (!xmlStrcmp(channel->name, (const xmlChar *)"channel")) {
506 break;
507 }
508 }
509
510 if (channel == NULL) {
511 fprintf(stderr, "parse_rss: no channel found\n");
512 return;
513 }
514
515 for (node = channel->xmlChildrenNode; node != NULL; node = node->next) {
516
517 if (!xmlStrcmp(node->name, (const xmlChar *)"title")) {
518 if (podcast->title) {
519 free(podcast->title);
520 }
521 podcast->title = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
522 } else if (!xmlStrcmp(node->name, (const xmlChar *)"author")) {
523 if (podcast->author) {
524 free(podcast->author);
525 }
526 podcast->author = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
527 } else if (!xmlStrcmp(node->name, (const xmlChar *)"description")) {
528 if (podcast->desc) {
529 free(podcast->desc);
530 }
531 podcast->desc = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
532 } else if (!xmlStrcmp(node->name, (const xmlChar *)"summary")) {
533 if (podcast->desc == NULL) {
534 podcast->desc = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
535 }
536 }
537 }
538
539 if (podcast->title == NULL) {
540 podcast->title = strdup(_("Untitled"));
541 }
542
543 string_remove_html(podcast->desc);
544
545 for (node = channel->xmlChildrenNode; node != NULL; node = node->next) {
546 if (!xmlStrcmp(node->name, (const xmlChar *)"item")) {
547 parse_rss_item(podcast, list, doc, node);
548 }
549 }
550 }
551
552 void
parse_atom_item(podcast_t * podcast,GSList ** list,xmlDocPtr doc,xmlNodePtr entry)553 parse_atom_item(podcast_t * podcast, GSList ** list, xmlDocPtr doc, xmlNodePtr entry) {
554
555 podcast_item_t * pitem;
556 xmlNodePtr node;
557
558 if ((pitem = podcast_item_new()) == NULL) {
559 return;
560 }
561
562 for (node = entry->xmlChildrenNode; node != NULL; node = node->next) {
563 if (!xmlStrcmp(node->name, (const xmlChar *)"title")) {
564 pitem->title = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
565 } else if (!xmlStrcmp(node->name, (const xmlChar *)"summary")) {
566 if (pitem->desc) {
567 free(pitem->desc);
568 }
569 pitem->desc = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
570 } else if (pitem->url == NULL && !xmlStrcmp(node->name, (const xmlChar *)"link")) {
571 xmlChar * rel = NULL;
572 if ((rel = xmlGetProp(node, (const xmlChar *)"rel")) != NULL &&
573 !xmlStrcmp(rel, (const xmlChar *)"enclosure")) {
574 xmlChar * len;
575 if ((len = xmlGetProp(node, (const xmlChar *)"length")) != NULL) {
576 sscanf((char *)len, "%u", &pitem->size);
577 xmlFree(len);
578 }
579 pitem->url = (char *)xmlGetProp(node, (const xmlChar *)"href");
580 }
581 if (rel != NULL) {
582 xmlFree(rel);
583 }
584 } else if (!xmlStrcmp(node->name, (const xmlChar *)"updated") || /* Atom 1.0 */
585 !xmlStrcmp(node->name, (const xmlChar *)"modified")) { /* Atom 0.3 */
586 xmlChar * tmp = xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
587 pitem->date = parse_atom_date((char *)tmp);
588 xmlFree(tmp);
589 }
590 }
591
592 if (pitem->url == NULL) {
593 podcast_item_free(pitem);
594 return;
595 }
596
597 string_remove_html(pitem->desc);
598
599 if (pitem->title == NULL) {
600 pitem->title = strdup(_("Untitled"));
601 }
602
603 if (g_slist_find_custom(*list, pitem->url, podcast_item_compare_url) == NULL) {
604 *list = g_slist_prepend(*list, pitem);
605 } else {
606 podcast_item_free(pitem);
607 }
608 }
609
610 void
parse_atom(podcast_t * podcast,GSList ** list,xmlDocPtr doc,xmlNodePtr feed)611 parse_atom(podcast_t * podcast, GSList ** list, xmlDocPtr doc, xmlNodePtr feed) {
612
613 xmlNodePtr node;
614
615 for (node = feed->xmlChildrenNode; node != NULL; node = node->next) {
616
617 if (!xmlStrcmp(node->name, (const xmlChar *)"title")) {
618 if (podcast->title) {
619 free(podcast->title);
620 }
621 podcast->title = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
622 } else if (!xmlStrcmp(node->name, (const xmlChar *)"author")) {
623 xmlNodePtr n;
624 for (n = node->xmlChildrenNode; n; n = n->next) {
625 if (!xmlStrcmp(n->name, (const xmlChar *)"name")) {
626 if (podcast->author) {
627 free(podcast->author);
628 }
629 podcast->author = (char *)xmlNodeListGetString(doc, n->xmlChildrenNode, 1);
630 break;
631 }
632 }
633 } else if (!xmlStrcmp(node->name, (const xmlChar *)"subtitle") || /* Atom 1.0 */
634 !xmlStrcmp(node->name, (const xmlChar *)"tagline")) { /* Atom 0.3 */
635 if (podcast->desc) {
636 free(podcast->desc);
637 }
638 podcast->desc = (char *)xmlNodeListGetString(doc, node->xmlChildrenNode, 1);
639 }
640 }
641
642 if (podcast->title == NULL) {
643 podcast->title = strdup(_("Untitled"));
644 }
645
646 string_remove_html(podcast->desc);
647
648 for (node = feed->xmlChildrenNode; node != NULL; node = node->next) {
649 if (!xmlStrcmp(node->name, (const xmlChar *)"entry")) {
650 parse_atom_item(podcast, list, doc, node);
651 }
652 }
653 }
654
655 int
podcast_parse(podcast_t * podcast,GSList ** list)656 podcast_parse(podcast_t * podcast, GSList ** list) {
657
658 xmlDocPtr doc;
659 xmlNodePtr node;
660 char filename[MAXLEN];
661 char * file;
662
663 file = podcast_file_from_url(podcast->url);
664 snprintf(filename, MAXLEN-1, "%s/.%s", podcast->dir, file);
665 free(file);
666
667 if (podcast_generic_download(podcast, podcast->url, filename, NULL, NULL) < 0) {
668 return -1;
669 }
670
671 doc = xmlParseFile(filename);
672 if (doc == NULL) {
673 unlink(filename);
674 return -1;
675 }
676
677 node = xmlDocGetRootElement(doc);
678 if (node == NULL) {
679 xmlFreeDoc(doc);
680 unlink(filename);
681 return -1;
682 }
683
684 if (!xmlStrcmp(node->name, (const xmlChar *)"rss")) {
685 parse_rss(podcast, list, doc, node);
686 } else if (!xmlStrcmp(node->name, (const xmlChar *)"feed")) {
687 parse_atom(podcast, list, doc, node);
688 } else {
689 fprintf(stderr, "unknown feed format: %s\n", node->name);
690 }
691
692 xmlFreeDoc(doc);
693 unlink(filename);
694
695 return 0;
696 }
697
698
699 GSList *
podcast_list_remove_item(podcast_t * podcast,GSList * list,GSList * litem)700 podcast_list_remove_item(podcast_t * podcast, GSList * list, GSList * litem) {
701
702 podcast_item_t * item = (podcast_item_t *)litem->data;
703
704 if (item->file) {
705 if (unlink(item->file) < 0) {
706 fprintf(stderr, "unlink: unable to unlink %s\n", item->file);
707 perror("unlink");
708 }
709
710 podcast->items = g_slist_remove(podcast->items, item);
711 store_podcast_remove_item(podcast, item);
712 } else {
713 podcast_item_free(item);
714 }
715
716 return g_slist_delete_link(list, litem);
717 }
718
719 void
podcast_item_download(podcast_download_t * pd,GSList ** list,GSList * node)720 podcast_item_download(podcast_download_t * pd, GSList ** list, GSList * node) {
721
722 podcast_item_t * item = (podcast_item_t *)node->data;
723 char * file;
724 char path[MAXLEN];
725 float duration;
726 struct stat statbuf;
727
728
729 file = podcast_file_from_url(item->url);
730 snprintf(path, MAXLEN-1, "%s/%s", pd->podcast->dir, file);
731 free(file);
732
733 pd->ncurrent++;
734 pd->percent = 0;
735
736 store_podcast_update_podcast_download(pd);
737
738 if (podcast_generic_download(pd->podcast, item->url, path, store_podcast_update_podcast_download, pd) < 0) {
739 goto failed;
740 }
741
742 if (stat(path, &statbuf) < 0) {
743 goto failed;
744 }
745
746 if ((duration = get_file_duration(path)) < 0.0f) {
747 goto failed;
748 }
749
750 item->duration = duration;
751 item->size = statbuf.st_size;
752 item->file = strdup(path);
753
754 pd->podcast->items = g_slist_prepend(pd->podcast->items, item);
755 store_podcast_add_item(pd->podcast, item);
756
757 return;
758
759 failed:
760 *list = podcast_list_remove_item(pd->podcast, *list, node);
761 }
762
763 void
podcast_apply_limits(podcast_t * podcast,GSList ** list)764 podcast_apply_limits(podcast_t * podcast, GSList ** list) {
765
766 GSList * node;
767 unsigned size = 0;
768 int count = 0;
769
770 for (node = *list; node; node = node->next) {
771 podcast_item_t * item = (podcast_item_t *)node->data;
772 size += item->size;
773 ++count;
774 }
775
776 node = g_slist_last(*list);
777 while (*list != NULL &&
778 ((podcast->flags & PODCAST_DATE_LIMIT &&
779 podcast->last_checked - ((podcast_item_t *)node->data)->date > podcast->date_limit)
780 ||
781 (podcast->flags & PODCAST_SIZE_LIMIT &&
782 size > podcast->size_limit)
783 ||
784 (podcast->flags & PODCAST_COUNT_LIMIT &&
785 count > podcast->count_limit))) {
786
787 size -= ((podcast_item_t *)node->data)->size;
788 --count;
789 *list = podcast_list_remove_item(podcast, *list, node);
790 node = g_slist_last(*list);
791 }
792 }
793
794 int
podcast_download_next(podcast_download_t * pd,GSList ** list)795 podcast_download_next(podcast_download_t * pd, GSList ** list) {
796
797 GSList * node;
798
799 for (node = *list; node; node = node->next) {
800 podcast_item_t * item = (podcast_item_t *)node->data;
801
802 if (pd->podcast->state == PODCAST_STATE_ABORTED) {
803 if (item->file == NULL) {
804 podcast_item_free(item);
805 }
806 continue;
807 }
808
809 if (item->file == NULL) {
810 podcast_item_download(pd, list, node);
811 return 1;
812 }
813 }
814
815 return 0;
816 }
817
818 void *
podcast_update_thread(void * arg)819 podcast_update_thread(void * arg) {
820
821 podcast_t * podcast = (podcast_t *)arg;
822 podcast_download_t * pd;
823
824 GSList * node;
825 GTimeVal tval;
826 GSList * list;
827
828 AQUALUNG_THREAD_DETACH();
829
830 if ((pd = podcast_download_new(podcast)) == NULL) {
831 return NULL;
832 }
833
834 list = g_slist_copy(podcast->items);
835
836 if (podcast_parse(podcast, &list) < 0) {
837 goto finish;
838 }
839
840 list = g_slist_sort(list, podcast_item_compare_date);
841
842 g_get_current_time(&tval);
843 podcast->last_checked = tval.tv_sec;
844
845 podcast_apply_limits(podcast, &list);
846
847 for (node = list; node; node = node->next) {
848 if (((podcast_item_t *)node->data)->file == NULL) {
849 pd->ndownloads++;
850 }
851 }
852
853 while (podcast_download_next(pd, &list)) {
854 podcast_apply_limits(podcast, &list);
855 }
856
857 finish:
858 g_slist_free(list);
859
860 store_podcast_update_podcast(pd);
861
862 return NULL;
863 }
864
865 void
podcast_update(podcast_t * podcast)866 podcast_update(podcast_t * podcast) {
867
868 if (podcast->state == PODCAST_STATE_IDLE || podcast->state == PODCAST_STATE_PENDING) {
869
870 AQUALUNG_THREAD_DECLARE(thread_id);
871
872 podcast->state = PODCAST_STATE_UPDATE;
873 AQUALUNG_THREAD_CREATE(thread_id, NULL, podcast_update_thread, podcast);
874 }
875 }
876
877