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