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