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