]> git.sesse.net Git - vlc/blob - modules/video_filter/rss.c
video_filter_rss: asynch fetch (no more lags while fetching/parsing the rss feeds).
[vlc] / modules / video_filter / rss.c
1 /*****************************************************************************
2  * rss.c : rss/atom feed display video plugin for vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Antoine Cellerier <dionoea -at- videolan -dot- org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Atom : http://www.ietf.org/rfc/rfc4287.txt
26  * RSS : http://www.rssboard.org/rss-specification
27  *****************************************************************************/
28
29 /*****************************************************************************
30  * Preamble
31  *****************************************************************************/
32
33 #ifdef HAVE_CONFIG_H
34 # include "config.h"
35 #endif
36
37 #include <vlc_common.h>
38 #include <vlc_plugin.h>
39
40 #include <vlc_filter.h>
41 #include <vlc_block.h>
42 #include <vlc_osd.h>
43
44 #include <vlc_block.h>
45 #include <vlc_stream.h>
46 #include <vlc_xml.h>
47 #include <vlc_charset.h>
48
49 #include <vlc_image.h>
50
51 #include <time.h>
52
53 /*****************************************************************************
54  * Local prototypes
55  *****************************************************************************/
56 static int  CreateFilter ( vlc_object_t * );
57 static void DestroyFilter( vlc_object_t * );
58 static subpicture_t *Filter( filter_t *, mtime_t );
59
60 static struct rss_feed_t *FetchRSS( filter_t * );
61 static void FreeRSS( struct rss_feed_t *, int );
62 static int ParseUrls( filter_t *, char * );
63
64 static void Fetch( void * );
65
66 static const int pi_color_values[] = {
67                0xf0000000, 0x00000000, 0x00808080, 0x00C0C0C0,
68                0x00FFFFFF, 0x00800000, 0x00FF0000, 0x00FF00FF, 0x00FFFF00,
69                0x00808000, 0x00008000, 0x00008080, 0x0000FF00, 0x00800080,
70                0x00000080, 0x000000FF, 0x0000FFFF};
71 static const char *const ppsz_color_descriptions[] = {
72                N_("Default"), N_("Black"),
73                N_("Gray"), N_("Silver"), N_("White"), N_("Maroon"), N_("Red"),
74                N_("Fuchsia"), N_("Yellow"), N_("Olive"), N_("Green"),
75                N_("Teal"), N_("Lime"), N_("Purple"), N_("Navy"), N_("Blue"),
76                N_("Aqua") };
77
78 /*****************************************************************************
79  * filter_sys_t: rss filter descriptor
80  *****************************************************************************/
81
82 typedef struct rss_item_t
83 {
84     char *psz_title;
85     char *psz_description;
86     char *psz_link;
87 } rss_item_t;
88
89 typedef struct rss_feed_t
90 {
91     char *psz_url;
92     char *psz_title;
93     char *psz_description;
94     char *psz_link;
95     char *psz_image;
96     picture_t *p_pic;
97
98     int i_items;
99     rss_item_t *p_items;
100 } rss_feed_t;
101
102 struct filter_sys_t
103 {
104     vlc_mutex_t lock;
105     vlc_timer_t timer;  /* Timer to refresh the rss feeds */
106     bool b_fetched;
107
108     int i_xoff, i_yoff;  /* offsets for the display string in the video window */
109     int i_pos; /* permit relative positioning (top, bottom, left, right, center) */
110     int i_speed;
111     int i_length;
112
113     char *psz_marquee;    /* marquee string */
114
115     text_style_t *p_style; /* font control */
116
117     mtime_t last_date;
118
119     int i_feeds;
120     rss_feed_t *p_feeds;
121
122     int i_ttl;
123     bool b_images;
124     int i_title;
125
126     int i_cur_feed;
127     int i_cur_item;
128     int i_cur_char;
129 };
130
131 #define MSG_TEXT N_("Feed URLs")
132 #define MSG_LONGTEXT N_("RSS/Atom feed '|' (pipe) separated URLs.")
133 #define SPEED_TEXT N_("Speed of feeds")
134 #define SPEED_LONGTEXT N_("Speed of the RSS/Atom feeds in microseconds (bigger is slower).")
135 #define LENGTH_TEXT N_("Max length")
136 #define LENGTH_LONGTEXT N_("Maximum number of characters displayed on the " \
137                 "screen." )
138 #define TTL_TEXT N_("Refresh time")
139 #define TTL_LONGTEXT N_("Number of seconds between each forced refresh " \
140         "of the feeds. 0 means that the feeds are never updated." )
141 #define IMAGE_TEXT N_("Feed images")
142 #define IMAGE_LONGTEXT N_("Display feed images if available.")
143
144 #define POSX_TEXT N_("X offset")
145 #define POSX_LONGTEXT N_("X offset, from the left screen edge." )
146 #define POSY_TEXT N_("Y offset")
147 #define POSY_LONGTEXT N_("Y offset, down from the top." )
148 #define OPACITY_TEXT N_("Opacity")
149 #define OPACITY_LONGTEXT N_("Opacity (inverse of transparency) of " \
150     "overlay text. 0 = transparent, 255 = totally opaque." )
151
152 #define SIZE_TEXT N_("Font size, pixels")
153 #define SIZE_LONGTEXT N_("Font size, in pixels. Default is -1 (use default " \
154     "font size)." )
155
156 #define COLOR_TEXT N_("Color")
157 #define COLOR_LONGTEXT N_("Color of the text that will be rendered on "\
158     "the video. This must be an hexadecimal (like HTML colors). The first two "\
159     "chars are for red, then green, then blue. #000000 = black, #FF0000 = red,"\
160     " #00FF00 = green, #FFFF00 = yellow (red + green), #FFFFFF = white" )
161
162 #define POS_TEXT N_("Text position")
163 #define POS_LONGTEXT N_( \
164   "You can enforce the text position on the video " \
165   "(0=center, 1=left, 2=right, 4=top, 8=bottom; you can " \
166   "also use combinations of these values, eg 6 = top-right).")
167
168 #define TITLE_TEXT N_("Title display mode")
169 #define TITLE_LONGTEXT N_("Title display mode. Default is 0 (hidden) if the feed has an image and feed images are enabled, 1 otherwise.")
170
171 static const int pi_pos_values[] = { 0, 1, 2, 4, 8, 5, 6, 9, 10 };
172 static const char *const ppsz_pos_descriptions[] =
173      { N_("Center"), N_("Left"), N_("Right"), N_("Top"), N_("Bottom"),
174      N_("Top-Left"), N_("Top-Right"), N_("Bottom-Left"), N_("Bottom-Right") };
175
176 enum title_modes {
177     default_title=-1,
178     hide_title,
179     prepend_title,
180     scroll_title };
181
182 static const int pi_title_modes[] = { default_title, hide_title, prepend_title, scroll_title };
183 static const char *const ppsz_title_modes[] =
184     { N_("Default"), N_("Don't show"), N_("Always visible"), N_("Scroll with feed") };
185
186 #define CFG_PREFIX "rss-"
187
188 /*****************************************************************************
189  * Module descriptor
190  *****************************************************************************/
191 vlc_module_begin ()
192     set_capability( "sub filter", 1 )
193     set_shortname( "RSS / Atom" )
194     set_callbacks( CreateFilter, DestroyFilter )
195     set_category( CAT_VIDEO )
196     set_subcategory( SUBCAT_VIDEO_SUBPIC )
197     add_string( CFG_PREFIX "urls", "rss", NULL, MSG_TEXT, MSG_LONGTEXT, false )
198
199     set_section( N_("Position"), NULL )
200     add_integer( CFG_PREFIX "x", 0, NULL, POSX_TEXT, POSX_LONGTEXT, true )
201     add_integer( CFG_PREFIX "y", 0, NULL, POSY_TEXT, POSY_LONGTEXT, true )
202     add_integer( CFG_PREFIX "position", -1, NULL, POS_TEXT, POS_LONGTEXT, false )
203         change_integer_list( pi_pos_values, ppsz_pos_descriptions, NULL )
204
205     set_section( N_("Font"), NULL )
206     /* 5 sets the default to top [1] left [4] */
207     add_integer_with_range( CFG_PREFIX "opacity", 255, 0, 255, NULL,
208         OPACITY_TEXT, OPACITY_LONGTEXT, false )
209     add_integer( CFG_PREFIX "color", 0xFFFFFF, NULL, COLOR_TEXT, COLOR_LONGTEXT,
210                   false )
211         change_integer_list( pi_color_values, ppsz_color_descriptions, NULL )
212     add_integer( CFG_PREFIX "size", -1, NULL, SIZE_TEXT, SIZE_LONGTEXT, false )
213
214     set_section( N_("Misc"), NULL )
215     add_integer( CFG_PREFIX "speed", 100000, NULL, SPEED_TEXT, SPEED_LONGTEXT,
216                  false )
217     add_integer( CFG_PREFIX "length", 60, NULL, LENGTH_TEXT, LENGTH_LONGTEXT,
218                  false )
219     add_integer( CFG_PREFIX "ttl", 1800, NULL, TTL_TEXT, TTL_LONGTEXT, false )
220     add_bool( CFG_PREFIX "images", 1, NULL, IMAGE_TEXT, IMAGE_LONGTEXT, false )
221     add_integer( CFG_PREFIX "title", default_title, NULL, TITLE_TEXT, TITLE_LONGTEXT, false )
222         change_integer_list( pi_title_modes, ppsz_title_modes, NULL )
223
224     set_description( N_("RSS and Atom feed display") )
225     add_shortcut( "rss" )
226     add_shortcut( "atom" )
227 vlc_module_end ()
228
229 static const char *const ppsz_filter_options[] = {
230     "urls", "x", "y", "position", "color", "size", "speed", "length",
231     "ttl", "images", "title", NULL
232 };
233
234 /*****************************************************************************
235  * CreateFilter: allocates RSS video filter
236  *****************************************************************************/
237 static int CreateFilter( vlc_object_t *p_this )
238 {
239     filter_t *p_filter = (filter_t *)p_this;
240     filter_sys_t *p_sys;
241     int i_ret = VLC_ENOMEM;
242     char *psz_urls;
243
244     /* Allocate structure */
245     p_sys = p_filter->p_sys = malloc( sizeof( filter_sys_t ) );
246     if( p_sys == NULL )
247         return VLC_ENOMEM;
248
249     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options,
250                        p_filter->p_cfg );
251
252     psz_urls = var_CreateGetString( p_filter, CFG_PREFIX "urls" );
253     p_sys->i_title = var_CreateGetInteger( p_filter, CFG_PREFIX "title" );
254     p_sys->i_cur_feed = 0;
255     p_sys->i_cur_item = p_sys->i_title == scroll_title ? -1 : 0;
256     p_sys->i_cur_char = 0;
257     p_sys->i_feeds = 0;
258     p_sys->p_feeds = NULL;
259     p_sys->i_speed = var_CreateGetInteger( p_filter, CFG_PREFIX "speed" );
260     p_sys->i_length = var_CreateGetInteger( p_filter, CFG_PREFIX "length" );
261     p_sys->i_ttl = __MAX( 0, var_CreateGetInteger( p_filter, CFG_PREFIX "ttl" ) );
262     p_sys->b_images = var_CreateGetBool( p_filter, CFG_PREFIX "images" );
263
264     p_sys->psz_marquee = malloc( p_sys->i_length + 1 );
265     if( p_sys->psz_marquee == NULL )
266         goto error;
267     p_sys->psz_marquee[p_sys->i_length] = '\0';
268
269     p_sys->p_style = text_style_New();
270     if( p_sys->p_style == NULL )
271         goto error;
272
273     p_sys->i_xoff = var_CreateGetInteger( p_filter, CFG_PREFIX "x" );
274     p_sys->i_yoff = var_CreateGetInteger( p_filter, CFG_PREFIX "y" );
275     p_sys->i_pos = var_CreateGetInteger( p_filter, CFG_PREFIX "position" );
276     p_sys->p_style->i_font_alpha = 255 - var_CreateGetInteger( p_filter, CFG_PREFIX "opacity" );
277     p_sys->p_style->i_font_color = var_CreateGetInteger( p_filter, CFG_PREFIX "color" );
278     p_sys->p_style->i_font_size = var_CreateGetInteger( p_filter, CFG_PREFIX "size" );
279
280     if( p_sys->b_images == true && p_sys->p_style->i_font_size == -1 )
281     {
282         msg_Warn( p_filter, "rss-size wasn't specified. Feed images will thus be displayed without being resized" );
283     }
284
285     /* Parse the urls */
286     if( ParseUrls( p_filter, psz_urls ) )
287     {
288         free( psz_urls );
289         goto error;
290     }
291     free( psz_urls );
292
293     /* Misc init */
294     vlc_mutex_init( &p_sys->lock );
295     p_filter->pf_sub_filter = Filter;
296     p_sys->last_date = (mtime_t)0;
297     p_sys->b_fetched = false;
298
299     /* Create and arm the timer */
300     if( vlc_timer_create( &p_sys->timer, Fetch, p_filter ) )
301     {
302         vlc_mutex_destroy( &p_sys->lock );
303         goto error;
304     }
305     vlc_timer_schedule( p_sys->timer, false, 1,
306                         (mtime_t)(p_sys->i_ttl)*1000000 );
307
308     return VLC_SUCCESS;
309
310 error:
311     free( p_sys->psz_marquee );
312     free( p_sys );
313     return i_ret;
314 }
315 /*****************************************************************************
316  * DestroyFilter: destroy RSS video filter
317  *****************************************************************************/
318 static void DestroyFilter( vlc_object_t *p_this )
319 {
320     filter_t *p_filter = (filter_t *)p_this;
321     filter_sys_t *p_sys = p_filter->p_sys;
322
323     vlc_timer_destroy( p_sys->timer );
324     vlc_mutex_destroy( &p_sys->lock );
325
326     text_style_Delete( p_sys->p_style );
327     free( p_sys->psz_marquee );
328     FreeRSS( p_sys->p_feeds, p_sys->i_feeds );
329     free( p_sys );
330 }
331
332 /****************************************************************************
333  * Filter: the whole thing
334  ****************************************************************************
335  * This function outputs subpictures at regular time intervals.
336  ****************************************************************************/
337 static subpicture_t *Filter( filter_t *p_filter, mtime_t date )
338 {
339     filter_sys_t *p_sys = p_filter->p_sys;
340     subpicture_t *p_spu;
341     video_format_t fmt;
342     subpicture_region_t *p_region;
343
344     int i_feed, i_item;
345     rss_feed_t *p_feed;
346
347     memset( &fmt, 0, sizeof(video_format_t) );
348
349     vlc_mutex_lock( &p_sys->lock );
350
351     /* Check if the feeds have been fetched and that we have some feeds */
352     /* TODO: check that we have items for each feeds */
353     if( !p_sys->b_fetched && p_sys->i_feeds > 0 )
354     {
355         vlc_mutex_unlock( &p_sys->lock );
356         return NULL;
357     }
358
359     if( p_sys->last_date
360        + ( p_sys->i_cur_char == 0 && p_sys->i_cur_item == ( p_sys->i_title == scroll_title ? -1 : 0 ) ? 5 : 1 )
361            /* ( ... ? 5 : 1 ) means "wait 5 times more for the 1st char" */
362        * p_sys->i_speed > date )
363     {
364         vlc_mutex_unlock( &p_sys->lock );
365         return NULL;
366     }
367
368     p_sys->last_date = date;
369     p_sys->i_cur_char++;
370     if( p_sys->i_cur_item == -1 ? p_sys->p_feeds[p_sys->i_cur_feed].psz_title[p_sys->i_cur_char] == 0 : p_sys->p_feeds[p_sys->i_cur_feed].p_items[p_sys->i_cur_item].psz_title[p_sys->i_cur_char] == 0 )
371     {
372         p_sys->i_cur_char = 0;
373         p_sys->i_cur_item++;
374         if( p_sys->i_cur_item >= p_sys->p_feeds[p_sys->i_cur_feed].i_items )
375         {
376             if( p_sys->i_title == scroll_title )
377                 p_sys->i_cur_item = -1;
378             else
379                 p_sys->i_cur_item = 0;
380             p_sys->i_cur_feed = (p_sys->i_cur_feed + 1)%p_sys->i_feeds;
381         }
382     }
383
384     p_spu = filter_NewSubpicture( p_filter );
385     if( !p_spu )
386     {
387         vlc_mutex_unlock( &p_sys->lock );
388         return NULL;
389     }
390
391     fmt.i_chroma = VLC_CODEC_TEXT;
392
393     p_spu->p_region = subpicture_region_New( &fmt );
394     if( !p_spu->p_region )
395     {
396         p_filter->pf_sub_buffer_del( p_filter, p_spu );
397         vlc_mutex_unlock( &p_sys->lock );
398         return NULL;
399     }
400
401     /* Generate the string that will be displayed. This string is supposed to
402        be p_sys->i_length characters long. */
403     i_item = p_sys->i_cur_item;
404     i_feed = p_sys->i_cur_feed;
405     p_feed = &p_sys->p_feeds[i_feed];
406
407     if( ( p_feed->p_pic && p_sys->i_title == default_title )
408         || p_sys->i_title == hide_title )
409     {
410         /* Don't display the feed's title if we have an image */
411         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
412                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
413                   +p_sys->i_cur_char );
414     }
415     else if( ( !p_feed->p_pic && p_sys->i_title == default_title )
416              || p_sys->i_title == prepend_title )
417     {
418         snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
419                   p_sys->p_feeds[i_feed].psz_title,
420                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title
421                   +p_sys->i_cur_char );
422     }
423     else /* scrolling title */
424     {
425         if( i_item == -1 )
426             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s : %s",
427                       p_sys->p_feeds[i_feed].psz_title + p_sys->i_cur_char,
428                       p_sys->p_feeds[i_feed].p_items[i_item+1].psz_title );
429         else
430             snprintf( p_sys->psz_marquee, p_sys->i_length, "%s",
431                       p_sys->p_feeds[i_feed].p_items[i_item].psz_title
432                       +p_sys->i_cur_char );
433     }
434
435     while( strlen( p_sys->psz_marquee ) < (unsigned int)p_sys->i_length )
436     {
437         i_item++;
438         if( i_item == p_sys->p_feeds[i_feed].i_items ) break;
439         snprintf( strchr( p_sys->psz_marquee, 0 ),
440                   p_sys->i_length - strlen( p_sys->psz_marquee ),
441                   " - %s",
442                   p_sys->p_feeds[i_feed].p_items[i_item].psz_title );
443     }
444
445     /* Calls to snprintf might split multibyte UTF8 chars ...
446      * which freetype doesn't like. */
447     {
448         char *a = strdup( p_sys->psz_marquee );
449         char *a2 = a;
450         char *b = p_sys->psz_marquee;
451         EnsureUTF8( p_sys->psz_marquee );
452         /* we want to use ' ' instead of '?' for erroneous chars */
453         while( *b != '\0' )
454         {
455             if( *b != *a ) *b = ' ';
456             b++;a++;
457         }
458         free( a2 );
459     }
460
461     p_spu->p_region->psz_text = strdup(p_sys->psz_marquee);
462     if( p_sys->p_style->i_font_size > 0 )
463         p_spu->p_region->fmt.i_visible_height = p_sys->p_style->i_font_size;
464     p_spu->i_start = date;
465     p_spu->i_stop  = 0;
466     p_spu->b_ephemer = true;
467
468     /*  where to locate the string: */
469     if( p_sys->i_pos < 0 )
470     {   /*  set to an absolute xy */
471         p_spu->p_region->i_align = OSD_ALIGN_LEFT | OSD_ALIGN_TOP;
472         p_spu->b_absolute = true;
473     }
474     else
475     {   /* set to one of the 9 relative locations */
476         p_spu->p_region->i_align = p_sys->i_pos;
477         p_spu->b_absolute = false;
478     }
479
480     p_spu->p_region->p_style = text_style_Duplicate( p_sys->p_style );
481
482     if( p_feed->p_pic )
483     {
484         /* Display the feed's image */
485         picture_t *p_pic = p_feed->p_pic;
486         video_format_t fmt_out;
487
488         memset( &fmt_out, 0, sizeof(video_format_t) );
489
490         fmt_out.i_chroma = VLC_CODEC_YUVA;
491         fmt_out.i_aspect = VOUT_ASPECT_FACTOR;
492         fmt_out.i_sar_num = fmt_out.i_sar_den = 1;
493         fmt_out.i_width =
494             fmt_out.i_visible_width = p_pic->p[Y_PLANE].i_visible_pitch;
495         fmt_out.i_height =
496             fmt_out.i_visible_height = p_pic->p[Y_PLANE].i_visible_lines;
497
498         p_region = subpicture_region_New( &fmt_out );
499         if( !p_region )
500         {
501             msg_Err( p_filter, "cannot allocate SPU region" );
502         }
503         else
504         {
505             p_region->i_x = p_sys->i_xoff;
506             p_region->i_y = p_sys->i_yoff;
507             /* FIXME the copy is probably not needed anymore */
508             picture_Copy( p_region->p_picture, p_pic );
509             p_spu->p_region->p_next = p_region;
510         }
511
512         /* Offset text to display right next to the image */
513         p_spu->p_region->i_x = p_pic->p[Y_PLANE].i_visible_pitch;
514     }
515
516     vlc_mutex_unlock( &p_sys->lock );
517     return p_spu;
518 }
519
520 /****************************************************************************
521  * RSS related functions
522  ****************************************************************************
523  * You should always lock the p_filter mutex before using any of these
524  * functions
525  ***************************************************************************/
526
527 #undef LoadImage /* do not conflict with Win32 API */
528
529 /****************************************************************************
530  * download and resize image located at psz_url
531  ***************************************************************************/
532 static picture_t *LoadImage( filter_t *p_filter, const char *psz_url )
533 {
534     filter_sys_t *p_sys = p_filter->p_sys;
535     video_format_t fmt_in;
536     video_format_t fmt_out;
537     picture_t *p_orig;
538     picture_t *p_pic = NULL;
539     image_handler_t *p_handler = image_HandlerCreate( p_filter );
540
541     memset( &fmt_in, 0, sizeof(video_format_t) );
542     memset( &fmt_out, 0, sizeof(video_format_t) );
543
544     fmt_out.i_chroma = VLC_CODEC_YUVA;
545     p_orig = image_ReadUrl( p_handler, psz_url, &fmt_in, &fmt_out );
546
547     if( !p_orig )
548     {
549         msg_Warn( p_filter, "Unable to read image %s", psz_url );
550     }
551     else if( p_sys->p_style->i_font_size > 0 )
552     {
553
554         fmt_in.i_chroma = VLC_CODEC_YUVA;
555         fmt_in.i_height = p_orig->p[Y_PLANE].i_visible_lines;
556         fmt_in.i_width = p_orig->p[Y_PLANE].i_visible_pitch;
557         fmt_out.i_width = p_orig->p[Y_PLANE].i_visible_pitch
558             *p_sys->p_style->i_font_size/p_orig->p[Y_PLANE].i_visible_lines;
559         fmt_out.i_height = p_sys->p_style->i_font_size;
560
561         p_pic = image_Convert( p_handler, p_orig, &fmt_in, &fmt_out );
562         picture_Release( p_orig );
563         if( !p_pic )
564         {
565             msg_Warn( p_filter, "Error while converting %s", psz_url );
566         }
567     }
568     else
569     {
570         p_pic = p_orig;
571     }
572
573     image_HandlerDelete( p_handler );
574
575     return p_pic;
576 }
577
578 /****************************************************************************
579  * remove all ' ' '\t' '\n' '\r' characters from the begining and end of the
580  * string.
581  ***************************************************************************/
582 static char *removeWhiteChars( const char *psz_src )
583 {
584     char *psz_src2,*psz_clean, *psz_clean2;
585     psz_src2 = psz_clean = strdup( psz_src );
586     int i;
587
588     while( ( *psz_clean == ' ' || *psz_clean == '\t'
589            || *psz_clean == '\n' || *psz_clean == '\r' )
590            && *psz_clean != '\0' )
591     {
592         psz_clean++;
593     }
594     i = strlen( psz_clean );
595     while( --i > 0 &&
596          ( psz_clean[i] == ' ' || psz_clean[i] == '\t'
597         || psz_clean[i] == '\n' || psz_clean[i] == '\r' ) );
598     psz_clean[i+1] = '\0';
599     psz_clean2 = strdup( psz_clean );
600     free( psz_src2 );
601     return psz_clean2;
602 }
603
604
605 /****************************************************************************
606  * Parse url list, psz_urls must be non empty (TODO: check it !)
607  ***************************************************************************/
608 static int ParseUrls( filter_t *p_filter, char *psz_urls )
609 {
610     filter_sys_t *p_sys = p_filter->p_sys;
611     char *psz_urls2 = psz_urls;
612
613     p_sys->i_feeds = 1;
614
615     /* Count the number of feeds */
616     while( *psz_urls )
617     {
618         if( *psz_urls == '|' )
619             p_sys->i_feeds++;
620         psz_urls++;
621     }
622
623     /* Allocate the structure */
624     p_sys->p_feeds = malloc( p_sys->i_feeds * sizeof( rss_feed_t ) );
625     if( !p_sys->p_feeds )
626         return VLC_ENOMEM;
627
628     /* Loop on all urls and fill in the struct */
629     psz_urls = psz_urls2;
630     for( int i = 0; i < p_sys->i_feeds; i++ )
631     {
632         rss_feed_t* p_feed = p_sys->p_feeds + i;
633         char *psz_end;
634
635         if( i < p_sys->i_feeds - 1 )
636         {
637             psz_end = strchr( psz_urls, '|' );
638             *psz_end = '\0';
639         }
640         else
641             psz_end = psz_urls;
642
643         p_feed->i_items = 0;
644         p_feed->p_items = NULL;
645         p_feed->psz_title = NULL;
646         p_feed->psz_link = NULL;
647         p_feed->psz_description = NULL;
648         p_feed->psz_image = NULL;
649         p_feed->p_pic = NULL;
650         p_feed->psz_url = strdup( psz_urls );
651
652         psz_urls = psz_end + 1;
653     }
654
655     return VLC_SUCCESS;
656 }
657
658
659
660 /****************************************************************************
661  * Parse the rss feed
662  ***************************************************************************/
663 static bool ParseFeed( filter_t *p_filter, xml_reader_t *p_xml_reader,
664                       rss_feed_t *p_feed )
665 {
666     VLC_UNUSED(p_filter);
667     char *psz_eltname = NULL;
668
669     bool b_is_item = false;
670     bool b_is_image = false;
671
672     int i_item = 0;
673
674     while( xml_ReaderRead( p_xml_reader ) == 1 )
675     {
676         switch( xml_ReaderNodeType( p_xml_reader ) )
677         {
678         // Error
679         case -1:
680             goto end;
681
682         case XML_READER_STARTELEM:
683             free( psz_eltname );
684             psz_eltname = xml_ReaderName( p_xml_reader );
685             if( !psz_eltname )
686                 goto end;
687
688 #ifdef RSS_DEBUG
689             msg_Dbg( p_filter, "element name: %s", psz_eltname );
690 #endif
691             /* rss or atom */
692             if( !strcmp( psz_eltname, "item" ) || !strcmp( psz_eltname, "entry" ) )
693             {
694                 b_is_item = true;
695                 p_feed->i_items++;
696                 p_feed->p_items = realloc( p_feed->p_items,
697                                            p_feed->i_items * sizeof( rss_item_t ) );
698                 p_feed->p_items[p_feed->i_items-1].psz_title = NULL;
699                 p_feed->p_items[p_feed->i_items-1].psz_description = NULL;
700                 p_feed->p_items[p_feed->i_items-1].psz_link = NULL;
701             }
702             /* rss */
703             else if( !strcmp( psz_eltname, "image" ) )
704             {
705                 b_is_image = true;
706             }
707             /* atom */
708             else if( !strcmp( psz_eltname, "link" ) )
709             {
710                 char *psz_href = NULL;
711                 char *psz_rel = NULL;
712                 while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
713                 {
714                     char *psz_name = xml_ReaderName( p_xml_reader );
715                     char *psz_value = xml_ReaderValue( p_xml_reader );
716                     if( !strcmp( psz_name, "rel" ) )
717                     {
718                         free( psz_rel );
719                         psz_rel = psz_value;
720                     }
721                     else if( !strcmp( psz_name, "href" ) )
722                     {
723                         free( psz_href );
724                         psz_href = psz_value;
725                     }
726                     else
727                     {
728                         free( psz_value );
729                     }
730                     free( psz_name );
731                 }
732
733                 /* "rel" and "href" must be defined */
734                 if( psz_rel && psz_href )
735                 {
736                     if( !strcmp( psz_rel, "alternate" ) && !b_is_item &&
737                         !b_is_image && !p_feed->psz_link )
738                     {
739                         p_feed->psz_link = psz_href;
740                     }
741                     /* this isn't in the rfc but i found some ... */
742                     else if( ( !strcmp( psz_rel, "logo" ) ||
743                                !strcmp( psz_rel, "icon" ) )
744                              && !b_is_item && !b_is_image
745                              && !p_feed->psz_image )
746                     {
747                         p_feed->psz_image = psz_href;
748                     }
749                     else
750                     {
751                         free( psz_href );
752                     }
753                 }
754                 else
755                 {
756                     free( psz_href );
757                 }
758                 free( psz_rel );
759             }
760             break;
761
762         case XML_READER_ENDELEM:
763             free( psz_eltname );
764             psz_eltname = xml_ReaderName( p_xml_reader );
765             if( !psz_eltname )
766                 goto end;
767
768 #ifdef RSS_DEBUG
769             msg_Dbg( p_filter, "element end : %s", psz_eltname );
770 #endif
771             /* rss or atom */
772             if( !strcmp( psz_eltname, "item" ) || !strcmp( psz_eltname, "entry" ) )
773             {
774                 b_is_item = false;
775                 i_item++;
776             }
777             /* rss */
778             else if( !strcmp( psz_eltname, "image" ) )
779             {
780                 b_is_image = false;
781             }
782             FREENULL( psz_eltname );
783             break;
784
785         case XML_READER_TEXT:
786         {
787             if( !psz_eltname )
788                 break;
789             char *psz_eltvalue = xml_ReaderValue( p_xml_reader );
790             if( !psz_eltvalue )
791                 goto end;
792
793             char *psz_clean = removeWhiteChars( psz_eltvalue );
794             free( psz_eltvalue );
795             psz_eltvalue = psz_clean;
796
797 #ifdef RSS_DEBUG
798             msg_Dbg( p_filter, "  text : <%s>", psz_eltvalue );
799 #endif
800             /* Is it an item ? */
801             if( b_is_item )
802             {
803                 rss_item_t *p_item = p_feed->p_items+i_item;
804                 /* rss/atom */
805                 if( !strcmp( psz_eltname, "title" ) && !p_item->psz_title )
806                 {
807                     p_item->psz_title = psz_eltvalue;
808                 }
809                 else if( !strcmp( psz_eltname, "link" ) /* rss */
810                          && !p_item->psz_link )
811                 {
812                     p_item->psz_link = psz_eltvalue;
813                 }
814                 /* rss/atom */
815                 else if( ( !strcmp( psz_eltname, "description" ) ||
816                            !strcmp( psz_eltname, "summary" ) )
817                           && !p_item->psz_description )
818                 {
819                     p_item->psz_description = psz_eltvalue;
820                 }
821                 else
822                 {
823                     free( psz_eltvalue );
824                 }
825             }
826             /* Is it an image ? */
827             else if( b_is_image )
828             {
829                 if( !strcmp( psz_eltname, "url" ) && !p_feed->psz_image )
830                     p_feed->psz_image = psz_eltvalue;
831                 else
832                     free( psz_eltvalue );
833             }
834             else
835             {
836                 /* rss/atom */
837                 if( !strcmp( psz_eltname, "title" ) && !p_feed->psz_title )
838                 {
839                     p_feed->psz_title = psz_eltvalue;
840                 }
841                 /* rss */
842                 else if( !strcmp( psz_eltname, "link" ) && !p_feed->psz_link )
843                 {
844                     p_feed->psz_link = psz_eltvalue;
845                 }
846                 /* rss ad atom */
847                 else if( ( !strcmp( psz_eltname, "description" ) ||
848                            !strcmp( psz_eltname, "subtitle" ) )
849                          && !p_feed->psz_description )
850                 {
851                     p_feed->psz_description = psz_eltvalue;
852                 }
853                 /* rss */
854                 else if( ( !strcmp( psz_eltname, "logo" ) ||
855                            !strcmp( psz_eltname, "icon" ) )
856                          && !p_feed->psz_image )
857                 {
858                     p_feed->psz_image = psz_eltvalue;
859                 }
860                 else
861                 {
862                     free( psz_eltvalue );
863                 }
864             }
865             break;
866         }
867         }
868     }
869
870     free( psz_eltname );
871     return true;
872
873 end:
874     free( psz_eltname );
875     return false;
876 }
877
878
879 /****************************************************************************
880  * FetchRSS (or Atom) feeds
881  ***************************************************************************/
882 static rss_feed_t* FetchRSS( filter_t *p_filter )
883 {
884     filter_sys_t *p_sys = p_filter->p_sys;
885
886     stream_t *p_stream;
887     xml_t *p_xml;
888     xml_reader_t *p_xml_reader;
889
890     /* These data are not modified after the creation of the module so we don't
891        need to hold the lock */
892     int i_feeds = p_sys->i_feeds;
893     bool b_images = p_sys->b_images;
894
895     /* Allocate a new structure */
896     rss_feed_t *p_feeds = malloc( i_feeds * sizeof( rss_feed_t ) );
897     if( !p_feeds )
898         return NULL;
899
900     p_xml = xml_Create( p_filter );
901     if( !p_xml )
902     {
903         msg_Err( p_filter, "Failed to open XML parser" );
904         free( p_feeds );
905         return NULL;
906     }
907
908     /* Fetch all feeds and parse them */
909     for( int i_feed = 0; i_feed < i_feeds; i_feed++ )
910     {
911         rss_feed_t *p_feed = p_feeds + i_feed;
912         rss_feed_t *p_old_feed = p_sys->p_feeds + i_feed;
913
914         /* Initialize the structure */
915         p_feed->psz_title = NULL;
916         p_feed->psz_description = NULL;
917         p_feed->psz_link = NULL;
918         p_feed->psz_image = NULL;
919         p_feed->p_pic = NULL;
920         p_feed->i_items = 0;
921         p_feed->p_items = NULL;
922
923         p_feed->psz_url = strdup( p_old_feed->psz_url );
924
925         /* Fetch the feed */
926         msg_Dbg( p_filter, "opening %s RSS/Atom feed ...", p_feed->psz_url );
927
928         p_stream = stream_UrlNew( p_filter, p_feed->psz_url );
929         if( !p_stream )
930         {
931             msg_Err( p_filter, "Failed to open %s for reading", p_feed->psz_url );
932             p_xml_reader = NULL;
933             goto error;
934         }
935
936         p_xml_reader = xml_ReaderCreate( p_xml, p_stream );
937         if( !p_xml_reader )
938         {
939             msg_Err( p_filter, "Failed to open %s for parsing", p_feed->psz_url );
940             goto error;
941         }
942
943         /* Parse the feed */
944         if( !ParseFeed( p_filter, p_xml_reader, p_feed ) )
945             goto error;
946
947         /* If we have a image: load it if requiere */
948         if( b_images && p_feed->psz_image && !p_feed->p_pic )
949         {
950             p_feed->p_pic = LoadImage( p_filter, p_feed->psz_image );
951         }
952
953         msg_Dbg( p_filter, "done with %s RSS/Atom feed", p_feed->psz_url );
954         xml_ReaderDelete( p_xml, p_xml_reader );
955         stream_Delete( p_stream );
956     }
957
958     xml_Delete( p_xml );
959     return p_feeds;
960
961 error:
962
963     /*TODO: still a memleak */
964     if( p_xml_reader )
965         xml_ReaderDelete( p_xml, p_xml_reader );
966     if( p_stream )
967         stream_Delete( p_stream );
968     if( p_xml )
969         xml_Delete( p_xml );
970
971     return NULL;
972 }
973
974 /****************************************************************************
975  * FreeRSS
976  ***************************************************************************/
977 static void FreeRSS( rss_feed_t *p_feeds, int i_feeds )
978 {
979     for( int i_feed = 0; i_feed < i_feeds; i_feed++ )
980     {
981         rss_feed_t *p_feed = p_feeds+i_feed;
982         for( int i_item = 0; i_item < p_feed->i_items; i_item++ )
983         {
984             rss_item_t *p_item = p_feed->p_items+i_item;
985             free( p_item->psz_title );
986             free( p_item->psz_link );
987             free( p_item->psz_description );
988         }
989         free( p_feed->p_items );
990         free( p_feed->psz_title);
991         free( p_feed->psz_link );
992         free( p_feed->psz_description );
993         free( p_feed->psz_image );
994         if( p_feed->p_pic != NULL )
995             picture_Release( p_feed->p_pic );
996         free( p_feed->psz_url );
997     }
998     free( p_feeds );
999 }
1000
1001 static void Fetch( void *p_data )
1002 {
1003     filter_t *p_filter = p_data;
1004     filter_sys_t *p_sys = p_filter->p_sys;
1005
1006     rss_feed_t *p_feeds = FetchRSS( p_filter );
1007     rss_feed_t *p_old_feeds = p_sys->p_feeds;
1008
1009     if( !p_feeds )
1010         return;
1011
1012     vlc_mutex_lock( &p_sys->lock );
1013     p_sys->p_feeds = p_feeds;
1014     p_sys->b_fetched = true;
1015     vlc_mutex_unlock( &p_sys->lock );
1016
1017     if( p_old_feeds )
1018         FreeRSS( p_old_feeds, p_sys->i_feeds );
1019 }