]> git.sesse.net Git - vlc/blob - src/playlist/fetcher.c
demux: avi: improve broken index offset heuristic (fix #14120)
[vlc] / src / playlist / fetcher.c
1 /*****************************************************************************
2  * fetcher.c: Art fetcher thread.
3  *****************************************************************************
4  * Copyright © 1999-2009 VLC authors and VideoLAN
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 it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * 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 <limits.h>
29 #include <assert.h>
30
31 #include <vlc_common.h>
32 #include <vlc_stream.h>
33 #include <vlc_meta_fetcher.h>
34 #include <vlc_memory.h>
35 #include <vlc_demux.h>
36 #include <vlc_modules.h>
37
38 #include "libvlc.h"
39 #include "art.h"
40 #include "fetcher.h"
41 #include "input/input_interface.h"
42
43 /*****************************************************************************
44  * Structures/definitions
45  *****************************************************************************/
46 typedef enum
47 {
48     PASS1_LOCAL = 0,
49     PASS2_NETWORK
50 } fetcher_pass_t;
51 #define PASS_COUNT 2
52
53 typedef struct
54 {
55     char *psz_artist;
56     char *psz_album;
57     char *psz_arturl;
58     bool b_found;
59     meta_fetcher_scope_t e_scope; /* max scope */
60
61 } playlist_album_t;
62
63 typedef struct fetcher_entry_t fetcher_entry_t;
64
65 struct fetcher_entry_t
66 {
67     input_item_t    *p_item;
68     input_item_meta_request_option_t i_options;
69     fetcher_entry_t *p_next;
70 };
71
72 struct playlist_fetcher_t
73 {
74     vlc_object_t   *object;
75     vlc_mutex_t     lock;
76     vlc_cond_t      wait;
77     bool            b_live;
78
79     fetcher_entry_t *p_waiting_head[PASS_COUNT];
80     fetcher_entry_t *p_waiting_tail[PASS_COUNT];
81
82     DECL_ARRAY(playlist_album_t) albums;
83     meta_fetcher_scope_t e_scope;
84 };
85
86 static void *Thread( void * );
87
88
89 /*****************************************************************************
90  * Public functions
91  *****************************************************************************/
92 playlist_fetcher_t *playlist_fetcher_New( vlc_object_t *parent )
93 {
94     playlist_fetcher_t *p_fetcher = malloc( sizeof(*p_fetcher) );
95     if( !p_fetcher )
96         return NULL;
97
98     p_fetcher->object = parent;
99     vlc_mutex_init( &p_fetcher->lock );
100     vlc_cond_init( &p_fetcher->wait );
101     p_fetcher->b_live = false;
102
103     bool b_access = var_InheritBool( parent, "metadata-network-access" );
104     if ( !b_access )
105         b_access = ( var_InheritInteger( parent, "album-art" ) == ALBUM_ART_ALL );
106
107     p_fetcher->e_scope = ( b_access ) ? FETCHER_SCOPE_ANY : FETCHER_SCOPE_LOCAL;
108
109     memset( p_fetcher->p_waiting_head, 0, PASS_COUNT * sizeof(fetcher_entry_t *) );
110     memset( p_fetcher->p_waiting_tail, 0, PASS_COUNT * sizeof(fetcher_entry_t *) );
111
112     ARRAY_INIT( p_fetcher->albums );
113
114     return p_fetcher;
115 }
116
117 void playlist_fetcher_Push( playlist_fetcher_t *p_fetcher, input_item_t *p_item,
118                             input_item_meta_request_option_t i_options )
119 {
120     fetcher_entry_t *p_entry = malloc( sizeof(fetcher_entry_t) );
121     if ( !p_entry ) return;
122
123     vlc_gc_incref( p_item );
124     p_entry->p_item = p_item;
125     p_entry->p_next = NULL;
126     p_entry->i_options = i_options;
127     vlc_mutex_lock( &p_fetcher->lock );
128     /* Append last */
129     if ( p_fetcher->p_waiting_head[PASS1_LOCAL] )
130         p_fetcher->p_waiting_tail[PASS1_LOCAL]->p_next = p_entry;
131     else
132         p_fetcher->p_waiting_head[PASS1_LOCAL] = p_entry;
133     p_fetcher->p_waiting_tail[PASS1_LOCAL] = p_entry;
134
135     if( !p_fetcher->b_live )
136     {
137         assert( p_fetcher->p_waiting_head[PASS1_LOCAL] );
138         if( vlc_clone_detach( NULL, Thread, p_fetcher,
139                               VLC_THREAD_PRIORITY_LOW ) )
140             msg_Err( p_fetcher->object,
141                      "cannot spawn secondary preparse thread" );
142         else
143             p_fetcher->b_live = true;
144     }
145     vlc_mutex_unlock( &p_fetcher->lock );
146 }
147
148 void playlist_fetcher_Delete( playlist_fetcher_t *p_fetcher )
149 {
150     fetcher_entry_t *p_next;
151     vlc_mutex_lock( &p_fetcher->lock );
152     /* Remove any left-over item, the fetcher will exit */
153     for ( int i_queue=0; i_queue<PASS_COUNT; i_queue++ )
154     {
155         while( p_fetcher->p_waiting_head[i_queue] )
156         {
157             p_next = p_fetcher->p_waiting_head[i_queue]->p_next;
158             vlc_gc_decref( p_fetcher->p_waiting_head[i_queue]->p_item );
159             free( p_fetcher->p_waiting_head[i_queue] );
160             p_fetcher->p_waiting_head[i_queue] = p_next;
161         }
162         p_fetcher->p_waiting_head[i_queue] = NULL;
163     }
164
165     while( p_fetcher->b_live )
166         vlc_cond_wait( &p_fetcher->wait, &p_fetcher->lock );
167     vlc_mutex_unlock( &p_fetcher->lock );
168
169     vlc_cond_destroy( &p_fetcher->wait );
170     vlc_mutex_destroy( &p_fetcher->lock );
171
172     free( p_fetcher );
173 }
174
175
176 /*****************************************************************************
177  * Privates functions
178  *****************************************************************************/
179 /**
180  * This function locates the art associated to an input item.
181  * Return codes:
182  *   0 : Art is in cache or is a local file
183  *   1 : Art found, need to download
184  *  -X : Error/not found
185  */
186 static int FindArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
187 {
188     int i_ret;
189
190     playlist_album_t *p_album = NULL;
191     char *psz_artist = input_item_GetArtist( p_item );
192     char *psz_album = input_item_GetAlbum( p_item );
193     char *psz_title = input_item_GetTitle( p_item );
194     if( !psz_title )
195         psz_title = input_item_GetName( p_item );
196
197     if( !psz_title && !psz_artist && !psz_album )
198         return VLC_EGENERIC;
199
200     free( psz_title );
201
202     /* If we already checked this album in this session, skip */
203     if( psz_artist && psz_album )
204     {
205         FOREACH_ARRAY( playlist_album_t album, p_fetcher->albums )
206             if( !strcmp( album.psz_artist, psz_artist ) &&
207                 !strcmp( album.psz_album, psz_album ) )
208             {
209                 msg_Dbg( p_fetcher->object,
210                          " %s - %s has already been searched",
211                          psz_artist, psz_album );
212                 /* TODO-fenrir if we cache art filename too, we can go faster */
213                 free( psz_artist );
214                 free( psz_album );
215                 if( album.b_found )
216                 {
217                     if( !strncmp( album.psz_arturl, "file://", 7 ) )
218                         input_item_SetArtURL( p_item, album.psz_arturl );
219                     else /* Actually get URL from cache */
220                         playlist_FindArtInCache( p_item );
221                     return 0;
222                 }
223                 else if ( album.e_scope >= p_fetcher->e_scope )
224                 {
225                     return VLC_EGENERIC;
226                 }
227                 msg_Dbg( p_fetcher->object,
228                          " will search at higher scope, if possible" );
229                 p_album = &p_fetcher->albums.p_elems[fe_idx];
230
231                 psz_artist = psz_album = NULL;
232                 break;
233             }
234         FOREACH_END();
235     }
236
237     free( psz_artist );
238     free( psz_album );
239
240     if ( playlist_FindArtInCacheUsingItemUID( p_item ) != VLC_SUCCESS )
241         playlist_FindArtInCache( p_item );
242     else
243         msg_Dbg( p_fetcher->object, "successfully retrieved arturl by uid" );
244
245     char *psz_arturl = input_item_GetArtURL( p_item );
246     if( psz_arturl )
247     {
248         /* We already have a URL */
249         if( !strncmp( psz_arturl, "file://", strlen( "file://" ) ) )
250         {
251             free( psz_arturl );
252             return 0; /* Art is in cache, no need to go further */
253         }
254
255         free( psz_arturl );
256
257         /* Art need to be put in cache */
258         return 1;
259     }
260
261     /* */
262     psz_album = input_item_GetAlbum( p_item );
263     psz_artist = input_item_GetArtist( p_item );
264     if( psz_album && psz_artist )
265     {
266         msg_Dbg( p_fetcher->object, "searching art for %s - %s",
267                  psz_artist, psz_album );
268     }
269     else
270     {
271         psz_title = input_item_GetTitle( p_item );
272         if( !psz_title )
273             psz_title = input_item_GetName( p_item );
274
275         msg_Dbg( p_fetcher->object, "searching art for %s", psz_title );
276         free( psz_title );
277     }
278
279     /* Fetch the art url */
280     i_ret = VLC_EGENERIC;
281
282     vlc_object_t *p_parent = p_fetcher->object;
283     meta_fetcher_t *p_finder =
284         vlc_custom_create( p_parent, sizeof( *p_finder ), "art finder" );
285     if( p_finder != NULL)
286     {
287         module_t *p_module;
288
289         p_finder->p_item = p_item;
290         p_finder->e_scope = p_fetcher->e_scope;
291
292         p_module = module_need( p_finder, "art finder", NULL, false );
293         if( p_module )
294         {
295             module_unneed( p_finder, p_module );
296             /* Try immediately if found in cache by download URL */
297             if( !playlist_FindArtInCache( p_item ) )
298                 i_ret = 0;
299             else
300                 i_ret = 1;
301         }
302         vlc_object_release( p_finder );
303     }
304
305     /* Record this album */
306     if( psz_artist && psz_album )
307     {
308         if ( p_album )
309         {
310             p_album->e_scope = p_fetcher->e_scope;
311             free( p_album->psz_arturl );
312             p_album->psz_arturl = input_item_GetArtURL( p_item );
313             p_album->b_found = (i_ret == VLC_EGENERIC ? false : true );
314             free( psz_artist );
315             free( psz_album );
316         }
317         else
318         {
319             playlist_album_t a;
320             a.psz_artist = psz_artist;
321             a.psz_album = psz_album;
322             a.psz_arturl = input_item_GetArtURL( p_item );
323             a.b_found = (i_ret == VLC_EGENERIC ? false : true );
324             a.e_scope = p_fetcher->e_scope;
325             ARRAY_APPEND( p_fetcher->albums, a );
326         }
327     }
328     else
329     {
330         free( psz_artist );
331         free( psz_album );
332     }
333
334     return i_ret;
335 }
336
337 /**
338  * Download the art using the URL or an art downloaded
339  * This function should be called only if data is not already in cache
340  */
341 static int DownloadArt( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
342 {
343     char *psz_arturl = input_item_GetArtURL( p_item );
344     assert( *psz_arturl );
345
346     if( !strncmp( psz_arturl , "file://", 7 ) )
347     {
348         msg_Dbg( p_fetcher->object,
349                  "Album art is local file, no need to cache" );
350         free( psz_arturl );
351         return VLC_SUCCESS;
352     }
353
354     if( !strncmp( psz_arturl , "APIC", 4 ) )
355     {
356         msg_Warn( p_fetcher->object, "APIC fetch not supported yet" );
357         goto error;
358     }
359
360     stream_t *p_stream = stream_UrlNew( p_fetcher->object, psz_arturl );
361     if( !p_stream )
362         goto error;
363
364     uint8_t *p_data = NULL;
365     int i_data = 0;
366     for( ;; )
367     {
368         int i_read = 65536;
369
370         if( i_data >= INT_MAX - i_read )
371             break;
372
373         p_data = realloc_or_free( p_data, i_data + i_read );
374         if( !p_data )
375             break;
376
377         i_read = stream_Read( p_stream, &p_data[i_data], i_read );
378         if( i_read <= 0 )
379             break;
380
381         i_data += i_read;
382     }
383     stream_Delete( p_stream );
384
385     if( p_data && i_data > 0 )
386     {
387         char *psz_type = strrchr( psz_arturl, '.' );
388         if( psz_type && strlen( psz_type ) > 5 )
389             psz_type = NULL; /* remove extension if it's > to 4 characters */
390
391         playlist_SaveArt( p_fetcher->object, p_item,
392                           p_data, i_data, psz_type );
393     }
394
395     free( p_data );
396
397     free( psz_arturl );
398     return VLC_SUCCESS;
399
400 error:
401     free( psz_arturl );
402     return VLC_EGENERIC;
403 }
404
405 /**
406  * FetchMeta, run the "meta fetcher". They are going to do network
407  * connections, and gather information upon the playing media.
408  * (even artwork).
409  */
410 static void FetchMeta( playlist_fetcher_t *p_fetcher, input_item_t *p_item )
411 {
412     meta_fetcher_t *p_finder =
413         vlc_custom_create( p_fetcher->object, sizeof( *p_finder ), "art finder" );
414     if ( !p_finder )
415         return;
416
417     p_finder->e_scope = p_fetcher->e_scope;
418     p_finder->p_item = p_item;
419
420     module_t *p_module = module_need( p_finder, "meta fetcher", NULL, false );
421     if( p_module )
422         module_unneed( p_finder, p_module );
423
424     vlc_object_release( p_finder );
425 }
426
427 static void *Thread( void *p_data )
428 {
429     playlist_fetcher_t *p_fetcher = p_data;
430     vlc_object_t *obj = p_fetcher->object;
431     fetcher_pass_t e_pass = PASS1_LOCAL;
432     for( ;; )
433     {
434         fetcher_entry_t *p_entry = NULL;
435
436         vlc_mutex_lock( &p_fetcher->lock );
437         for ( int i=0; i<PASS_COUNT; i++ )
438         {
439             if ( p_fetcher->p_waiting_head[i] )
440             {
441                 e_pass = i;
442                 break;
443             }
444         }
445
446         if( p_fetcher->p_waiting_head[e_pass] )
447         {
448             p_entry = p_fetcher->p_waiting_head[e_pass];
449             p_fetcher->p_waiting_head[e_pass] = p_entry->p_next;
450             if ( p_entry->p_next == NULL )
451                 p_fetcher->p_waiting_tail[e_pass] = NULL;
452             p_entry->p_next = NULL;
453         }
454         else
455         {
456             p_fetcher->b_live = false;
457             vlc_cond_signal( &p_fetcher->wait );
458         }
459         vlc_mutex_unlock( &p_fetcher->lock );
460
461         if( !p_entry )
462             break;
463
464         meta_fetcher_scope_t e_prev_scope = p_fetcher->e_scope;
465
466         /* scope override */
467         switch ( p_entry->i_options ) {
468         case META_REQUEST_OPTION_SCOPE_ANY:
469             p_fetcher->e_scope = FETCHER_SCOPE_ANY;
470             break;
471         case META_REQUEST_OPTION_SCOPE_LOCAL:
472             p_fetcher->e_scope = FETCHER_SCOPE_LOCAL;
473             break;
474         case META_REQUEST_OPTION_SCOPE_NETWORK:
475             p_fetcher->e_scope = FETCHER_SCOPE_NETWORK;
476             break;
477         case META_REQUEST_OPTION_NONE:
478         default:
479             break;
480         }
481         /* Triggers "meta fetcher", eventually fetch meta on the network.
482          * They are identical to "meta reader" expect that may actually
483          * takes time. That's why they are running here.
484          * The result of this fetch is not cached. */
485
486         int i_ret = -1;
487
488         if( e_pass == PASS1_LOCAL && ( p_fetcher->e_scope & FETCHER_SCOPE_LOCAL ) )
489         {
490             /* only fetch from local */
491             p_fetcher->e_scope = FETCHER_SCOPE_LOCAL;
492         }
493         else if( e_pass == PASS2_NETWORK && ( p_fetcher->e_scope & FETCHER_SCOPE_NETWORK ) )
494         {
495             /* only fetch from network */
496             p_fetcher->e_scope = FETCHER_SCOPE_NETWORK;
497         }
498         else
499             p_fetcher->e_scope = 0;
500         if ( p_fetcher->e_scope & FETCHER_SCOPE_ANY )
501         {
502             FetchMeta( p_fetcher, p_entry->p_item );
503             i_ret = FindArt( p_fetcher, p_entry->p_item );
504             switch( i_ret )
505             {
506             case 1: /* Found, need to dl */
507                 i_ret = DownloadArt( p_fetcher, p_entry->p_item );
508                 break;
509             case 0: /* Is in cache */
510                 i_ret = VLC_SUCCESS;
511                 //ft
512             default:// error
513                 break;
514             }
515         }
516
517         p_fetcher->e_scope = e_prev_scope;
518         /* */
519         if ( i_ret != VLC_SUCCESS && (e_pass != PASS2_NETWORK) )
520         {
521             /* Move our entry to next pass queue */
522             vlc_mutex_lock( &p_fetcher->lock );
523             if ( p_fetcher->p_waiting_head[e_pass + 1] )
524                 p_fetcher->p_waiting_tail[e_pass + 1]->p_next = p_entry;
525             else
526                 p_fetcher->p_waiting_head[e_pass + 1] = p_entry;
527             p_fetcher->p_waiting_tail[e_pass + 1] = p_entry;
528             vlc_mutex_unlock( &p_fetcher->lock );
529         }
530         else
531         {
532             /* */
533             char *psz_name = input_item_GetName( p_entry->p_item );
534             if( i_ret == VLC_SUCCESS ) /* Art is now in cache */
535             {
536                 msg_Dbg( obj, "found art for %s in cache", psz_name );
537                 input_item_SetArtFetched( p_entry->p_item, true );
538                 var_SetAddress( obj, "item-change", p_entry->p_item );
539             }
540             else
541             {
542                 msg_Dbg( obj, "art not found for %s", psz_name );
543                 input_item_SetArtNotFound( p_entry->p_item, true );
544             }
545             free( psz_name );
546             vlc_gc_decref( p_entry->p_item );
547             free( p_entry );
548         }
549     }
550     return NULL;
551 }