]> git.sesse.net Git - vlc/blob - src/playlist/fetcher.c
Fixed undefined behavior with integer overflow.
[vlc] / src / playlist / fetcher.c
1 /*****************************************************************************
2  * fetcher.c: Art fetcher thread.
3  *****************************************************************************
4  * Copyright © 1999-2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Samuel Hocevar <sam@zoy.org>
8  *          Clément Stenac <zorglub@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <vlc_common.h>
29 #include <vlc_playlist.h>
30 #include <vlc_stream.h>
31 #include <limits.h>
32
33 #include "art.h"
34 #include "fetcher.h"
35 #include "playlist_internal.h"
36
37
38 /*****************************************************************************
39  * Structures/definitions
40  *****************************************************************************/
41 struct playlist_fetcher_t
42 {
43     playlist_t      *p_playlist;
44
45     vlc_thread_t    thread;
46     vlc_mutex_t     lock;
47     vlc_cond_t      wait;
48     int             i_art_policy;
49     int             i_waiting;
50     input_item_t    **pp_waiting;
51
52     DECL_ARRAY(playlist_album_t) albums;
53 };
54
55 static void *Thread( void * );
56
57
58 /*****************************************************************************
59  * Public functions
60  *****************************************************************************/
61 playlist_fetcher_t *playlist_fetcher_New( playlist_t *p_playlist )
62 {
63     playlist_fetcher_t *p_fetcher = malloc( sizeof(*p_fetcher) );
64     if( !p_fetcher )
65         return NULL;
66
67     p_fetcher->p_playlist = p_playlist;
68     vlc_mutex_init( &p_fetcher->lock );
69     vlc_cond_init( &p_fetcher->wait );
70     p_fetcher->i_waiting = 0;
71     p_fetcher->pp_waiting = NULL;
72     p_fetcher->i_art_policy = var_GetInteger( p_playlist, "album-art" );
73     ARRAY_INIT( p_fetcher->albums );
74
75     if( vlc_clone( &p_fetcher->thread, Thread, p_fetcher,
76                    VLC_THREAD_PRIORITY_LOW ) )
77     {
78         msg_Err( p_playlist, "cannot spawn secondary preparse thread" );
79         free( p_fetcher );
80         return NULL;
81     }
82
83     return p_fetcher;
84 }
85
86 void playlist_fetcher_Push( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
87 {
88     vlc_gc_incref( p_item );
89
90     vlc_mutex_lock( &p_fetcher->lock );
91     INSERT_ELEM( p_fetcher->pp_waiting, p_fetcher->i_waiting,
92                  p_fetcher->i_waiting, p_item );
93     vlc_cond_signal( &p_fetcher->wait );
94     vlc_mutex_unlock( &p_fetcher->lock );
95 }
96
97 void playlist_fetcher_Delete( playlist_fetcher_t *p_fetcher )
98 {
99     /* Destroy the item meta-infos fetcher */
100     vlc_cancel( p_fetcher->thread );
101     vlc_join( p_fetcher->thread, NULL );
102
103     while( p_fetcher->i_waiting > 0 )
104     {   /* Any left-over unparsed item? */
105         vlc_gc_decref( p_fetcher->pp_waiting[0] );
106         REMOVE_ELEM( p_fetcher->pp_waiting, p_fetcher->i_waiting, 0 );
107     }
108     vlc_cond_destroy( &p_fetcher->wait );
109     vlc_mutex_destroy( &p_fetcher->lock );
110     free( p_fetcher );
111 }
112
113
114 /*****************************************************************************
115  * Privates functions
116  *****************************************************************************/
117 /**
118  * This function locates the art associated to an input item.
119  * Return codes:
120  *   0 : Art is in cache or is a local file
121  *   1 : Art found, need to download
122  *  -X : Error/not found
123  */
124 static int FindArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
125 {
126     playlist_t *p_playlist = p_fetcher->p_playlist;
127     int i_ret = VLC_EGENERIC;
128     module_t *p_module;
129     char *psz_title, *psz_artist, *psz_album;
130
131     psz_artist = input_item_GetArtist( p_item );
132     psz_album = input_item_GetAlbum( p_item );
133     psz_title = input_item_GetTitle( p_item );
134     if( !psz_title )
135         psz_title = input_item_GetName( p_item );
136
137     if( !psz_title && !psz_artist && !psz_album )
138         return VLC_EGENERIC;
139
140     free( psz_title );
141
142     /* If we already checked this album in this session, skip */
143     if( psz_artist && psz_album )
144     {
145         FOREACH_ARRAY( playlist_album_t album, p_fetcher->albums )
146             if( !strcmp( album.psz_artist, psz_artist ) &&
147                 !strcmp( album.psz_album, psz_album ) )
148             {
149                 msg_Dbg( p_playlist, " %s - %s has already been searched",
150                          psz_artist, psz_album );
151                 /* TODO-fenrir if we cache art filename too, we can go faster */
152                 free( psz_artist );
153                 free( psz_album );
154                 if( album.b_found )
155                 {
156                     if( !strncmp( album.psz_arturl, "file://", 7 ) )
157                         input_item_SetArtURL( p_item, album.psz_arturl );
158                     else /* Actually get URL from cache */
159                         playlist_FindArtInCache( p_item );
160                     return 0;
161                 }
162                 else
163                 {
164                     return VLC_EGENERIC;
165                 }
166             }
167         FOREACH_END();
168     }
169     free( psz_artist );
170     free( psz_album );
171
172     playlist_FindArtInCache( p_item );
173
174     char *psz_arturl = input_item_GetArtURL( p_item );
175     if( psz_arturl )
176     {
177         /* We already have an URL */
178         if( !strncmp( psz_arturl, "file://", strlen( "file://" ) ) )
179         {
180             free( psz_arturl );
181             return 0; /* Art is in cache, no need to go further */
182         }
183
184         free( psz_arturl );
185
186         /* Art need to be put in cache */
187         return 1;
188     }
189
190     PL_LOCK;
191     p_playlist->p_private = p_item;
192     psz_album = input_item_GetAlbum( p_item );
193     psz_artist = input_item_GetArtist( p_item );
194     psz_title = input_item_GetTitle( p_item );
195     if( !psz_title )
196         psz_title = input_item_GetName( p_item );
197
198     if( psz_album && psz_artist )
199     {
200         msg_Dbg( p_playlist, "searching art for %s - %s",
201              psz_artist, psz_album );
202     }
203     else
204     {
205         msg_Dbg( p_playlist, "searching art for %s",
206              psz_title );
207     }
208     free( psz_title );
209
210     p_module = module_need( p_playlist, "art finder", NULL, false );
211
212     if( p_module )
213         i_ret = 1;
214     else
215         msg_Dbg( p_playlist, "unable to find art" );
216
217     /* Record this album */
218     if( psz_artist && psz_album )
219     {
220         playlist_album_t a;
221         a.psz_artist = psz_artist;
222         a.psz_album = psz_album;
223         a.psz_arturl = input_item_GetArtURL( p_item );
224         a.b_found = (i_ret == VLC_EGENERIC ? false : true );
225         ARRAY_APPEND( p_fetcher->albums, a );
226     }
227     else
228     {
229         free( psz_artist );
230         free( psz_album );
231     }
232
233     if( p_module )
234         module_unneed( p_playlist, p_module );
235     p_playlist->p_private = NULL;
236     PL_UNLOCK;
237
238     return i_ret;
239 }
240
241 /**
242  * Download the art using the URL or an art downloaded
243  * This function should be called only if data is not already in cache
244  */
245 static int DownloadArt( playlist_t *p_playlist, input_item_t *p_item )
246 {
247     char *psz_arturl = input_item_GetArtURL( p_item );
248     assert( *psz_arturl );
249
250     if( !strncmp( psz_arturl , "file://", 7 ) )
251     {
252         msg_Dbg( p_playlist, "Album art is local file, no need to cache" );
253         free( psz_arturl );
254         return VLC_SUCCESS;
255     }
256
257     if( !strncmp( psz_arturl , "APIC", 4 ) )
258     {
259         msg_Warn( p_playlist, "APIC fetch not supported yet" );
260         goto error;
261     }
262
263     stream_t *p_stream = stream_UrlNew( p_playlist, psz_arturl );
264     if( !p_stream )
265         goto error;
266
267     uint8_t *p_data = NULL;
268     int i_data = 0;
269     for( ;; )
270     {
271         int i_read = 65536;
272
273         if( i_data >= INT_MAX - i_read )
274             break;
275
276         p_data = realloc( p_data, i_data + i_read );
277         if( !p_data )
278             break;
279
280         i_read = stream_Read( p_stream, &p_data[i_data], i_read );
281         if( i_read <= 0 )
282             break;
283
284         i_data += i_read;
285     }
286     stream_Delete( p_stream );
287
288     if( p_data && i_data > 0 )
289     {
290         char *psz_type = strrchr( psz_arturl, '.' );
291         if( psz_type && strlen( psz_type ) > 5 )
292             psz_type = NULL; /* remove extension if it's > to 4 characters */
293
294         playlist_SaveArt( p_playlist, p_item, p_data, i_data, psz_type );
295     }
296
297     free( p_data );
298
299     free( psz_arturl );
300     return VLC_SUCCESS;
301
302 error:
303     free( psz_arturl );
304     return VLC_EGENERIC;
305 }
306
307
308 static int InputEvent( vlc_object_t *p_this, char const *psz_cmd,
309                        vlc_value_t oldval, vlc_value_t newval, void *p_data )
310 {
311     VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
312     playlist_fetcher_t *p_fetcher = p_data;
313
314     if( newval.i_int == INPUT_EVENT_ITEM_META )
315         vlc_cond_signal( &p_fetcher->wait );
316
317     return VLC_SUCCESS;
318 }
319
320
321 /* Check if it is not yet preparsed and if so wait for it
322  * (at most 0.5s)
323  * (This can happen if we fetch art on play)
324  * FIXME this doesn't work if we need to fetch meta before art...
325  */
326 static void WaitPreparsed( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
327 {
328     playlist_t *p_playlist = p_fetcher->p_playlist;
329
330     if( input_item_IsPreparsed( p_item ) )
331         return;
332
333     input_thread_t *p_input = playlist_CurrentInput( p_playlist );
334     if( !p_input )
335         return;
336
337     if( input_GetItem( p_input ) != p_item )
338         goto exit;
339
340     var_AddCallback( p_input, "intf-event", InputEvent, p_fetcher );
341
342     const mtime_t i_deadline = mdate() + 500*1000;
343
344     while( !p_input->b_eof && !p_input->b_error && !input_item_IsPreparsed( p_item ) )
345     {
346         /* A bit weird, but input_item_IsPreparsed does held the protected value */
347         vlc_mutex_lock( &p_fetcher->lock );
348         vlc_cond_timedwait( &p_fetcher->wait, &p_fetcher->lock, i_deadline );
349         vlc_mutex_unlock( &p_fetcher->lock );
350
351         if( i_deadline <= mdate() )
352             break;
353     }
354
355     var_DelCallback( p_input, "intf-event", InputEvent, p_fetcher );
356
357 exit:
358     vlc_object_release( p_input );
359 }
360
361 static void *Thread( void *p_data )
362 {
363     playlist_fetcher_t *p_fetcher = p_data;
364     playlist_t *p_playlist = p_fetcher->p_playlist;
365
366     for( ;; )
367     {
368         input_item_t *p_item;
369
370         /* Be sure to be cancellable before our queue is empty */
371         vlc_testcancel();
372
373         /* */
374         vlc_mutex_lock( &p_fetcher->lock );
375         mutex_cleanup_push( &p_fetcher->lock );
376
377         while( p_fetcher->i_waiting == 0 )
378             vlc_cond_wait( &p_fetcher->wait, &p_fetcher->lock );
379
380         p_item = p_fetcher->pp_waiting[0];
381         REMOVE_ELEM( p_fetcher->pp_waiting, p_fetcher->i_waiting, 0 );
382         vlc_cleanup_run( );
383
384         if( !p_item )
385             continue;
386
387         /* */
388         int canc = vlc_savecancel();
389
390         /* Wait that the input item is preparsed if it is being played */
391         WaitPreparsed( p_fetcher, p_item );
392
393         /* Find art, and download it if needed */
394         int i_ret = FindArt( p_fetcher, p_item );
395         if( i_ret == 1 )
396             i_ret = DownloadArt( p_playlist, p_item );
397
398         /* */
399         char *psz_name = input_item_GetName( p_item );
400         if( !i_ret ) /* Art is now in cache */
401         {
402             PL_DEBUG( "found art for %s in cache", psz_name );
403             input_item_SetArtFetched( p_item, true );
404             var_SetInteger( p_playlist, "item-change", p_item->i_id );
405         }
406         else
407         {
408             PL_DEBUG( "art not found for %s", psz_name );
409             input_item_SetArtNotFound( p_item, true );
410         }
411         free( psz_name );
412         vlc_gc_decref( p_item );
413
414         vlc_restorecancel( canc );
415
416         int i_activity = var_GetInteger( p_playlist, "activity" );
417         if( i_activity < 0 ) i_activity = 0;
418         /* Sleep at least 1ms */
419         msleep( (i_activity+1) * 1000 );
420     }
421     return NULL;
422 }
423
424