1 /*****************************************************************************
2 * fetcher.c: Art fetcher thread.
3 *****************************************************************************
4 * Copyright © 1999-2009 VLC authors and VideoLAN
7 * Authors: Samuel Hocevar <sam@zoy.org>
8 * Clément Stenac <zorglub@videolan.org>
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.
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.
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 *****************************************************************************/
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>
41 #include "input/input_interface.h"
43 /*****************************************************************************
44 * Structures/definitions
45 *****************************************************************************/
59 meta_fetcher_scope_t e_scope
; /* max scope */
63 typedef struct fetcher_entry_t fetcher_entry_t
;
65 struct fetcher_entry_t
68 input_item_meta_request_option_t i_options
;
69 fetcher_entry_t
*p_next
;
72 struct playlist_fetcher_t
79 fetcher_entry_t
*p_waiting_head
[PASS_COUNT
];
80 fetcher_entry_t
*p_waiting_tail
[PASS_COUNT
];
82 DECL_ARRAY(playlist_album_t
) albums
;
83 meta_fetcher_scope_t e_scope
;
86 static void *Thread( void * );
89 /*****************************************************************************
91 *****************************************************************************/
92 playlist_fetcher_t
*playlist_fetcher_New( vlc_object_t
*parent
)
94 playlist_fetcher_t
*p_fetcher
= malloc( sizeof(*p_fetcher
) );
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;
103 bool b_access
= var_InheritBool( parent
, "metadata-network-access" );
105 b_access
= ( var_InheritInteger( parent
, "album-art" ) == ALBUM_ART_ALL
);
107 p_fetcher
->e_scope
= ( b_access
) ? FETCHER_SCOPE_ANY
: FETCHER_SCOPE_LOCAL
;
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
*) );
112 ARRAY_INIT( p_fetcher
->albums
);
117 void playlist_fetcher_Push( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
,
118 input_item_meta_request_option_t i_options
)
120 fetcher_entry_t
*p_entry
= malloc( sizeof(fetcher_entry_t
) );
121 if ( !p_entry
) return;
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
);
129 if ( p_fetcher
->p_waiting_head
[PASS1_LOCAL
] )
130 p_fetcher
->p_waiting_tail
[PASS1_LOCAL
]->p_next
= p_entry
;
132 p_fetcher
->p_waiting_head
[PASS1_LOCAL
] = p_entry
;
133 p_fetcher
->p_waiting_tail
[PASS1_LOCAL
] = p_entry
;
135 if( !p_fetcher
->b_live
)
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" );
143 p_fetcher
->b_live
= true;
145 vlc_mutex_unlock( &p_fetcher
->lock
);
148 void playlist_fetcher_Delete( playlist_fetcher_t
*p_fetcher
)
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
++ )
155 while( p_fetcher
->p_waiting_head
[i_queue
] )
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
;
162 p_fetcher
->p_waiting_head
[i_queue
] = NULL
;
165 while( p_fetcher
->b_live
)
166 vlc_cond_wait( &p_fetcher
->wait
, &p_fetcher
->lock
);
167 vlc_mutex_unlock( &p_fetcher
->lock
);
169 vlc_cond_destroy( &p_fetcher
->wait
);
170 vlc_mutex_destroy( &p_fetcher
->lock
);
176 /*****************************************************************************
178 *****************************************************************************/
180 * This function locates the art associated to an input item.
182 * 0 : Art is in cache or is a local file
183 * 1 : Art found, need to download
184 * -X : Error/not found
186 static int FindArt( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
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
);
195 psz_title
= input_item_GetName( p_item
);
197 if( !psz_title
&& !psz_artist
&& !psz_album
)
202 /* If we already checked this album in this session, skip */
203 if( psz_artist
&& psz_album
)
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
) )
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 */
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
);
223 else if ( album
.e_scope
>= p_fetcher
->e_scope
)
227 msg_Dbg( p_fetcher
->object
,
228 " will search at higher scope, if possible" );
229 p_album
= &p_fetcher
->albums
.p_elems
[fe_idx
];
240 if ( playlist_FindArtInCacheUsingItemUID( p_item
) != VLC_SUCCESS
)
241 playlist_FindArtInCache( p_item
);
243 msg_Dbg( p_fetcher
->object
, "successfully retrieved arturl by uid" );
245 char *psz_arturl
= input_item_GetArtURL( p_item
);
248 /* We already have a URL */
249 if( !strncmp( psz_arturl
, "file://", strlen( "file://" ) ) )
252 return 0; /* Art is in cache, no need to go further */
257 /* Art need to be put in cache */
262 psz_album
= input_item_GetAlbum( p_item
);
263 psz_artist
= input_item_GetArtist( p_item
);
264 if( psz_album
&& psz_artist
)
266 msg_Dbg( p_fetcher
->object
, "searching art for %s - %s",
267 psz_artist
, psz_album
);
271 psz_title
= input_item_GetTitle( p_item
);
273 psz_title
= input_item_GetName( p_item
);
275 msg_Dbg( p_fetcher
->object
, "searching art for %s", psz_title
);
279 /* Fetch the art url */
280 i_ret
= VLC_EGENERIC
;
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
)
289 p_finder
->p_item
= p_item
;
290 p_finder
->e_scope
= p_fetcher
->e_scope
;
292 p_module
= module_need( p_finder
, "art finder", NULL
, false );
295 module_unneed( p_finder
, p_module
);
296 /* Try immediately if found in cache by download URL */
297 if( !playlist_FindArtInCache( p_item
) )
302 vlc_object_release( p_finder
);
305 /* Record this album */
306 if( psz_artist
&& psz_album
)
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 );
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
);
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
341 static int DownloadArt( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
343 char *psz_arturl
= input_item_GetArtURL( p_item
);
344 assert( *psz_arturl
);
346 if( !strncmp( psz_arturl
, "file://", 7 ) )
348 msg_Dbg( p_fetcher
->object
,
349 "Album art is local file, no need to cache" );
354 if( !strncmp( psz_arturl
, "APIC", 4 ) )
356 msg_Warn( p_fetcher
->object
, "APIC fetch not supported yet" );
360 stream_t
*p_stream
= stream_UrlNew( p_fetcher
->object
, psz_arturl
);
364 uint8_t *p_data
= NULL
;
370 if( i_data
>= INT_MAX
- i_read
)
373 p_data
= realloc_or_free( p_data
, i_data
+ i_read
);
377 i_read
= stream_Read( p_stream
, &p_data
[i_data
], i_read
);
383 stream_Delete( p_stream
);
385 if( p_data
&& i_data
> 0 )
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 */
391 playlist_SaveArt( p_fetcher
->object
, p_item
,
392 p_data
, i_data
, psz_type
);
406 * FetchMeta, run the "meta fetcher". They are going to do network
407 * connections, and gather information upon the playing media.
410 static void FetchMeta( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
412 meta_fetcher_t
*p_finder
=
413 vlc_custom_create( p_fetcher
->object
, sizeof( *p_finder
), "art finder" );
417 p_finder
->e_scope
= p_fetcher
->e_scope
;
418 p_finder
->p_item
= p_item
;
420 module_t
*p_module
= module_need( p_finder
, "meta fetcher", NULL
, false );
422 module_unneed( p_finder
, p_module
);
424 vlc_object_release( p_finder
);
427 static void *Thread( void *p_data
)
429 playlist_fetcher_t
*p_fetcher
= p_data
;
430 vlc_object_t
*obj
= p_fetcher
->object
;
431 fetcher_pass_t e_pass
= PASS1_LOCAL
;
434 fetcher_entry_t
*p_entry
= NULL
;
436 vlc_mutex_lock( &p_fetcher
->lock
);
437 for ( int i
=0; i
<PASS_COUNT
; i
++ )
439 if ( p_fetcher
->p_waiting_head
[i
] )
446 if( p_fetcher
->p_waiting_head
[e_pass
] )
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
;
456 p_fetcher
->b_live
= false;
457 vlc_cond_signal( &p_fetcher
->wait
);
459 vlc_mutex_unlock( &p_fetcher
->lock
);
464 meta_fetcher_scope_t e_prev_scope
= p_fetcher
->e_scope
;
467 switch ( p_entry
->i_options
) {
468 case META_REQUEST_OPTION_SCOPE_ANY
:
469 p_fetcher
->e_scope
= FETCHER_SCOPE_ANY
;
471 case META_REQUEST_OPTION_SCOPE_LOCAL
:
472 p_fetcher
->e_scope
= FETCHER_SCOPE_LOCAL
;
474 case META_REQUEST_OPTION_SCOPE_NETWORK
:
475 p_fetcher
->e_scope
= FETCHER_SCOPE_NETWORK
;
477 case META_REQUEST_OPTION_NONE
:
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. */
488 if( e_pass
== PASS1_LOCAL
&& ( p_fetcher
->e_scope
& FETCHER_SCOPE_LOCAL
) )
490 /* only fetch from local */
491 p_fetcher
->e_scope
= FETCHER_SCOPE_LOCAL
;
493 else if( e_pass
== PASS2_NETWORK
&& ( p_fetcher
->e_scope
& FETCHER_SCOPE_NETWORK
) )
495 /* only fetch from network */
496 p_fetcher
->e_scope
= FETCHER_SCOPE_NETWORK
;
499 p_fetcher
->e_scope
= 0;
500 if ( p_fetcher
->e_scope
& FETCHER_SCOPE_ANY
)
502 FetchMeta( p_fetcher
, p_entry
->p_item
);
503 i_ret
= FindArt( p_fetcher
, p_entry
->p_item
);
506 case 1: /* Found, need to dl */
507 i_ret
= DownloadArt( p_fetcher
, p_entry
->p_item
);
509 case 0: /* Is in cache */
517 p_fetcher
->e_scope
= e_prev_scope
;
519 if ( i_ret
!= VLC_SUCCESS
&& (e_pass
!= PASS2_NETWORK
) )
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
;
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
);
533 char *psz_name
= input_item_GetName( p_entry
->p_item
);
534 if( i_ret
== VLC_SUCCESS
) /* Art is now in cache */
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
);
542 msg_Dbg( obj
, "art not found for %s", psz_name
);
543 input_item_SetArtNotFound( p_entry
->p_item
, true );
546 vlc_gc_decref( p_entry
->p_item
);