1 /*****************************************************************************
2 * fetcher.c: Art fetcher thread.
3 *****************************************************************************
4 * Copyright © 1999-2009 the VideoLAN team
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
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.
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.
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 *****************************************************************************/
28 #include <vlc_common.h>
29 #include <vlc_playlist.h>
30 #include <vlc_stream.h>
35 #include "playlist_internal.h"
38 /*****************************************************************************
39 * Structures/definitions
40 *****************************************************************************/
41 struct playlist_fetcher_t
45 playlist_t
*p_playlist
;
52 input_item_t
**pp_waiting
;
54 DECL_ARRAY(playlist_album_t
) albums
;
57 static void *Thread( void * );
60 /*****************************************************************************
62 *****************************************************************************/
63 playlist_fetcher_t
*playlist_fetcher_New( playlist_t
*p_playlist
)
65 playlist_fetcher_t
*p_fetcher
=
66 vlc_custom_create( p_playlist
, sizeof(*p_fetcher
),
67 VLC_OBJECT_GENERIC
, "playlist fetcher" );
72 vlc_object_attach( p_fetcher
, p_playlist
);
73 p_fetcher
->p_playlist
= p_playlist
;
74 vlc_mutex_init( &p_fetcher
->lock
);
75 vlc_cond_init( &p_fetcher
->wait
);
76 p_fetcher
->i_waiting
= 0;
77 p_fetcher
->pp_waiting
= NULL
;
78 p_fetcher
->i_art_policy
= var_GetInteger( p_playlist
, "album-art" );
79 ARRAY_INIT( p_fetcher
->albums
);
81 if( vlc_clone( &p_fetcher
->thread
, Thread
, p_fetcher
,
82 VLC_THREAD_PRIORITY_LOW
) )
84 msg_Err( p_fetcher
, "cannot spawn secondary preparse thread" );
85 vlc_object_release( p_fetcher
);
92 void playlist_fetcher_Push( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
94 vlc_gc_incref( p_item
);
96 vlc_mutex_lock( &p_fetcher
->lock
);
97 INSERT_ELEM( p_fetcher
->pp_waiting
, p_fetcher
->i_waiting
,
98 p_fetcher
->i_waiting
, p_item
);
99 vlc_cond_signal( &p_fetcher
->wait
);
100 vlc_mutex_unlock( &p_fetcher
->lock
);
103 void playlist_fetcher_Delete( playlist_fetcher_t
*p_fetcher
)
106 vlc_object_kill( p_fetcher
);
108 /* Destroy the item meta-infos fetcher */
109 vlc_cancel( p_fetcher
->thread
);
110 vlc_join( p_fetcher
->thread
, NULL
);
112 while( p_fetcher
->i_waiting
> 0 )
113 { /* Any left-over unparsed item? */
114 vlc_gc_decref( p_fetcher
->pp_waiting
[0] );
115 REMOVE_ELEM( p_fetcher
->pp_waiting
, p_fetcher
->i_waiting
, 0 );
117 vlc_cond_destroy( &p_fetcher
->wait
);
118 vlc_mutex_destroy( &p_fetcher
->lock
);
119 vlc_object_release( p_fetcher
);
123 /*****************************************************************************
125 *****************************************************************************/
127 * This function locates the art associated to an input item.
129 * 0 : Art is in cache or is a local file
130 * 1 : Art found, need to download
131 * -X : Error/not found
133 static int FindArt( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
137 char *psz_title
, *psz_artist
, *psz_album
;
139 psz_artist
= input_item_GetArtist( p_item
);
140 psz_album
= input_item_GetAlbum( p_item
);
141 psz_title
= input_item_GetTitle( p_item
);
143 psz_title
= input_item_GetName( p_item
);
145 if( !psz_title
&& !psz_artist
&& !psz_album
)
150 /* If we already checked this album in this session, skip */
151 if( psz_artist
&& psz_album
)
153 FOREACH_ARRAY( playlist_album_t album
, p_fetcher
->albums
)
154 if( !strcmp( album
.psz_artist
, psz_artist
) &&
155 !strcmp( album
.psz_album
, psz_album
) )
157 msg_Dbg( p_fetcher
, " %s - %s has already been searched",
158 psz_artist
, psz_album
);
159 /* TODO-fenrir if we cache art filename too, we can go faster */
164 if( !strncmp( album
.psz_arturl
, "file://", 7 ) )
165 input_item_SetArtURL( p_item
, album
.psz_arturl
);
166 else /* Actually get URL from cache */
167 playlist_FindArtInCache( p_item
);
180 playlist_FindArtInCache( p_item
);
182 char *psz_arturl
= input_item_GetArtURL( p_item
);
185 /* We already have an URL */
186 if( !strncmp( psz_arturl
, "file://", strlen( "file://" ) ) )
189 return 0; /* Art is in cache, no need to go further */
194 /* Art need to be put in cache */
199 psz_album
= input_item_GetAlbum( p_item
);
200 psz_artist
= input_item_GetArtist( p_item
);
201 if( psz_album
&& psz_artist
)
203 msg_Dbg( p_fetcher
, "searching art for %s - %s",
204 psz_artist
, psz_album
);
208 psz_title
= input_item_GetTitle( p_item
);
210 psz_title
= input_item_GetName( p_item
);
212 msg_Dbg( p_fetcher
, "searching art for %s", psz_title
);
216 /* Fetch the art url */
217 p_fetcher
->p_private
= p_item
;
219 p_module
= module_need( p_fetcher
, "art finder", NULL
, false );
223 module_unneed( p_fetcher
, p_module
);
228 msg_Dbg( p_fetcher
, "unable to find art" );
229 i_ret
= VLC_EGENERIC
;
232 /* Record this album */
233 if( psz_artist
&& psz_album
)
236 a
.psz_artist
= psz_artist
;
237 a
.psz_album
= psz_album
;
238 a
.psz_arturl
= input_item_GetArtURL( p_item
);
239 a
.b_found
= (i_ret
== VLC_EGENERIC
? false : true );
240 ARRAY_APPEND( p_fetcher
->albums
, a
);
252 * Download the art using the URL or an art downloaded
253 * This function should be called only if data is not already in cache
255 static int DownloadArt( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
257 char *psz_arturl
= input_item_GetArtURL( p_item
);
258 assert( *psz_arturl
);
260 if( !strncmp( psz_arturl
, "file://", 7 ) )
262 msg_Dbg( p_fetcher
, "Album art is local file, no need to cache" );
267 if( !strncmp( psz_arturl
, "APIC", 4 ) )
269 msg_Warn( p_fetcher
, "APIC fetch not supported yet" );
273 stream_t
*p_stream
= stream_UrlNew( p_fetcher
, psz_arturl
);
277 uint8_t *p_data
= NULL
;
283 if( i_data
>= INT_MAX
- i_read
)
286 p_data
= realloc( p_data
, i_data
+ i_read
);
290 i_read
= stream_Read( p_stream
, &p_data
[i_data
], i_read
);
296 stream_Delete( p_stream
);
298 if( p_data
&& i_data
> 0 )
300 char *psz_type
= strrchr( psz_arturl
, '.' );
301 if( psz_type
&& strlen( psz_type
) > 5 )
302 psz_type
= NULL
; /* remove extension if it's > to 4 characters */
304 playlist_SaveArt( p_fetcher
->p_playlist
, p_item
, p_data
, i_data
, psz_type
);
318 static int InputEvent( vlc_object_t
*p_this
, char const *psz_cmd
,
319 vlc_value_t oldval
, vlc_value_t newval
, void *p_data
)
321 VLC_UNUSED(p_this
); VLC_UNUSED(psz_cmd
); VLC_UNUSED(oldval
);
322 playlist_fetcher_t
*p_fetcher
= p_data
;
324 if( newval
.i_int
== INPUT_EVENT_ITEM_META
||
325 newval
.i_int
== INPUT_EVENT_DEAD
)
326 vlc_cond_signal( &p_fetcher
->wait
);
332 /* Check if it is not yet preparsed and if so wait for it
334 * (This can happen if we fetch art on play)
335 * FIXME this doesn't work if we need to fetch meta before art...
337 static void WaitPreparsed( playlist_fetcher_t
*p_fetcher
, input_item_t
*p_item
)
339 if( input_item_IsPreparsed( p_item
) )
342 input_thread_t
*p_input
= playlist_CurrentInput( p_fetcher
->p_playlist
);
346 if( input_GetItem( p_input
) != p_item
)
349 var_AddCallback( p_input
, "intf-event", InputEvent
, p_fetcher
);
351 const mtime_t i_deadline
= mdate() + 500*1000;
353 while( !p_input
->b_eof
&& !p_input
->b_error
&& !input_item_IsPreparsed( p_item
) )
355 /* A bit weird, but input_item_IsPreparsed does held the protected value */
356 vlc_mutex_lock( &p_fetcher
->lock
);
357 vlc_cond_timedwait( &p_fetcher
->wait
, &p_fetcher
->lock
, i_deadline
);
358 vlc_mutex_unlock( &p_fetcher
->lock
);
360 if( i_deadline
<= mdate() )
364 var_DelCallback( p_input
, "intf-event", InputEvent
, p_fetcher
);
367 vlc_object_release( p_input
);
370 static void *Thread( void *p_data
)
372 playlist_fetcher_t
*p_fetcher
= p_data
;
373 playlist_t
*p_playlist
= p_fetcher
->p_playlist
;
377 input_item_t
*p_item
;
379 /* Be sure to be cancellable before our queue is empty */
383 vlc_mutex_lock( &p_fetcher
->lock
);
384 mutex_cleanup_push( &p_fetcher
->lock
);
386 while( p_fetcher
->i_waiting
== 0 )
387 vlc_cond_wait( &p_fetcher
->wait
, &p_fetcher
->lock
);
389 p_item
= p_fetcher
->pp_waiting
[0];
390 REMOVE_ELEM( p_fetcher
->pp_waiting
, p_fetcher
->i_waiting
, 0 );
397 int canc
= vlc_savecancel();
399 /* Wait that the input item is preparsed if it is being played */
400 WaitPreparsed( p_fetcher
, p_item
);
403 if( !vlc_object_alive( p_fetcher
) )
406 /* Find art, and download it if needed */
407 int i_ret
= FindArt( p_fetcher
, p_item
);
410 if( !vlc_object_alive( p_fetcher
) )
414 i_ret
= DownloadArt( p_fetcher
, p_item
);
417 char *psz_name
= input_item_GetName( p_item
);
418 if( !i_ret
) /* Art is now in cache */
420 PL_DEBUG( "found art for %s in cache", psz_name
);
421 input_item_SetArtFetched( p_item
, true );
422 var_SetAddress( p_playlist
, "item-change", p_item
);
426 PL_DEBUG( "art not found for %s", psz_name
);
427 input_item_SetArtNotFound( p_item
, true );
432 vlc_gc_decref( p_item
);
434 vlc_restorecancel( canc
);
436 int i_activity
= var_GetInteger( p_playlist
, "activity" );
437 if( i_activity
< 0 ) i_activity
= 0;
438 /* Sleep at least 1ms */
439 msleep( (i_activity
+1) * 1000 );