1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright © 2017-2017 VLC authors and VideoLAN
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
20 *****************************************************************************/
26 #include <vlc_common.h>
27 #include <vlc_stream.h>
28 #include <vlc_modules.h>
29 #include <vlc_interrupt.h>
30 #include <vlc_arrays.h>
31 #include <vlc_atomic.h>
32 #include <vlc_threads.h>
33 #include <vlc_memstream.h>
34 #include <vlc_meta_fetcher.h>
39 #include "input/input_interface.h"
40 #include "misc/background_worker.h"
41 #include "misc/interrupt.h"
43 struct playlist_fetcher_t
{
44 struct background_worker
* local
;
45 struct background_worker
* network
;
46 struct background_worker
* downloader
;
48 vlc_dictionary_t album_cache
;
53 struct fetcher_request
{
60 struct fetcher_thread
{
61 void (*pf_worker
)( playlist_fetcher_t
*, struct fetcher_request
* );
63 struct background_worker
* worker
;
64 struct fetcher_request
* req
;
65 playlist_fetcher_t
* fetcher
;
67 vlc_interrupt_t interrupt
;
72 static char* CreateCacheKey( input_item_t
* item
)
74 vlc_mutex_lock( &item
->lock
);
78 vlc_mutex_unlock( &item
->lock
);
82 char const* artist
= vlc_meta_Get( item
->p_meta
, vlc_meta_Artist
);
83 char const* album
= vlc_meta_Get( item
->p_meta
, vlc_meta_Album
);
87 * Simple concatenation of artist and album can lead to the same key
88 * for entities that should not have such. Imagine { dogs, tick } and
90 if( !artist
|| !album
|| asprintf( &key
, "%s:%zu:%s:%zu",
91 artist
, strlen( artist
), album
, strlen( album
) ) < 0 )
95 vlc_mutex_unlock( &item
->lock
);
100 static void FreeCacheEntry( void* data
, void* obj
)
106 static int ReadAlbumCache( playlist_fetcher_t
* fetcher
, input_item_t
* item
)
108 char* key
= CreateCacheKey( item
);
113 vlc_mutex_lock( &fetcher
->lock
);
114 char const* art
= vlc_dictionary_value_for_key( &fetcher
->album_cache
,
117 input_item_SetArtURL( item
, art
);
118 vlc_mutex_unlock( &fetcher
->lock
);
121 return art
? VLC_SUCCESS
: VLC_EGENERIC
;
124 static void AddAlbumCache( playlist_fetcher_t
* fetcher
, input_item_t
* item
,
127 char* art
= input_item_GetArtURL( item
);
128 char* key
= CreateCacheKey( item
);
130 if( key
&& art
&& strncasecmp( art
, "attachment://", 13 ) )
132 vlc_mutex_lock( &fetcher
->lock
);
133 if( overwrite
|| !vlc_dictionary_has_key( &fetcher
->album_cache
, key
) )
135 vlc_dictionary_insert( &fetcher
->album_cache
, key
, art
);
138 vlc_mutex_unlock( &fetcher
->lock
);
145 static int InvokeModule( playlist_fetcher_t
* fetcher
, input_item_t
* item
,
146 int scope
, char const* type
)
148 meta_fetcher_t
* mf
= vlc_custom_create( fetcher
->owner
,
149 sizeof( *mf
), type
);
150 if( unlikely( !mf
) )
156 module_t
* mf_module
= module_need( mf
, type
, NULL
, false );
159 module_unneed( mf
, mf_module
);
161 vlc_object_release( mf
);
166 static int CheckMeta( input_item_t
* item
)
168 vlc_mutex_lock( &item
->lock
);
169 bool error
= !item
->p_meta
||
170 !vlc_meta_Get( item
->p_meta
, vlc_meta_Title
) ||
171 !vlc_meta_Get( item
->p_meta
, vlc_meta_Artist
) ||
172 !vlc_meta_Get( item
->p_meta
, vlc_meta_Album
);
173 vlc_mutex_unlock( &item
->lock
);
177 static int CheckArt( input_item_t
* item
)
179 vlc_mutex_lock( &item
->lock
);
180 bool error
= !item
->p_meta
||
181 !vlc_meta_Get( item
->p_meta
, vlc_meta_ArtworkURL
);
182 vlc_mutex_unlock( &item
->lock
);
186 static int SearchArt( playlist_fetcher_t
* fetcher
, input_item_t
* item
, int scope
)
188 InvokeModule( fetcher
, item
, scope
, "art finder" );
189 return CheckArt( item
);
192 static int SearchByScope( playlist_fetcher_t
* fetcher
,
193 struct fetcher_request
* req
, int scope
)
195 input_item_t
* item
= req
->item
;
197 if( CheckMeta( item
) &&
198 InvokeModule( fetcher
, req
->item
, scope
, "meta fetcher" ) )
203 if( ! CheckArt( item
) ||
204 ! ReadAlbumCache( fetcher
, item
) ||
205 ! playlist_FindArtInCacheUsingItemUID( item
) ||
206 ! playlist_FindArtInCache( item
) ||
207 ! SearchArt( fetcher
, item
, scope
) )
209 AddAlbumCache( fetcher
, req
->item
, false );
210 if( !background_worker_Push( fetcher
->downloader
, req
, NULL
, 0 ) )
217 static void SetPreparsed( struct fetcher_request
* req
)
219 if( req
->preparse_status
!= -1 )
221 input_item_SetPreparsed( req
->item
, true );
222 input_item_SignalPreparseEnded( req
->item
, req
->preparse_status
);
226 static void Downloader( playlist_fetcher_t
* fetcher
,
227 struct fetcher_request
* req
)
229 ReadAlbumCache( fetcher
, req
->item
);
231 char *psz_arturl
= input_item_GetArtURL( req
->item
);
235 if( !strncasecmp( psz_arturl
, "file://", 7 ) ||
236 !strncasecmp( psz_arturl
, "attachment://", 13 ) )
237 goto out
; /* no fetch required */
239 stream_t
* source
= vlc_stream_NewURL( fetcher
->owner
, psz_arturl
);
244 struct vlc_memstream output_stream
;
245 vlc_memstream_open( &output_stream
);
251 int read
= vlc_stream_Read( source
, buffer
, sizeof( buffer
) );
255 if( (int)vlc_memstream_write( &output_stream
, buffer
, read
) < read
)
259 vlc_stream_Delete( source
);
261 if( vlc_memstream_close( &output_stream
) )
266 free( output_stream
.ptr
);
270 playlist_SaveArt( fetcher
->owner
, req
->item
, output_stream
.ptr
,
271 output_stream
.length
, NULL
);
273 free( output_stream
.ptr
);
274 AddAlbumCache( fetcher
, req
->item
, true );
279 var_SetAddress( fetcher
->owner
, "item-change", req
->item
);
280 input_item_SetArtFetched( req
->item
, true );
288 FREENULL( psz_arturl
);
292 static void SearchLocal( playlist_fetcher_t
* fetcher
, struct fetcher_request
* req
)
294 if( SearchByScope( fetcher
, req
, FETCHER_SCOPE_LOCAL
) == VLC_SUCCESS
)
297 if( var_InheritBool( fetcher
->owner
, "metadata-network-access" ) ||
298 req
->options
& META_REQUEST_OPTION_SCOPE_NETWORK
)
300 if( background_worker_Push( fetcher
->network
, req
, NULL
, 0 ) )
305 input_item_SetArtNotFound( req
->item
, true );
310 static void SearchNetwork( playlist_fetcher_t
* fetcher
, struct fetcher_request
* req
)
312 if( SearchByScope( fetcher
, req
, FETCHER_SCOPE_NETWORK
) )
314 input_item_SetArtNotFound( req
->item
, true );
319 static void RequestRelease( void* req_
)
321 struct fetcher_request
* req
= req_
;
323 if( atomic_fetch_sub( &req
->refs
, 1 ) != 1 )
326 input_item_Release( req
->item
);
330 static void RequestHold( void* req_
)
332 struct fetcher_request
* req
= req_
;
333 atomic_fetch_add_explicit( &req
->refs
, 1, memory_order_relaxed
);
336 static void* FetcherThread( void* handle
)
338 struct fetcher_thread
* th
= handle
;
339 vlc_interrupt_set( &th
->interrupt
);
341 th
->pf_worker( th
->fetcher
, th
->req
);
343 atomic_store( &th
->active
, false );
344 background_worker_RequestProbe( th
->worker
);
348 static int StartWorker( playlist_fetcher_t
* fetcher
,
349 void( *pf_worker
)( playlist_fetcher_t
*, struct fetcher_request
* ),
350 struct background_worker
* bg
, struct fetcher_request
* req
, void** handle
)
352 struct fetcher_thread
* th
= malloc( sizeof *th
);
354 if( unlikely( !th
) )
359 th
->fetcher
= fetcher
;
360 th
->pf_worker
= pf_worker
;
362 vlc_interrupt_init( &th
->interrupt
);
363 atomic_init( &th
->active
, true );
365 if( !vlc_clone( &th
->thread
, FetcherThread
, th
, VLC_THREAD_PRIORITY_LOW
) )
371 vlc_interrupt_deinit( &th
->interrupt
);
376 static int ProbeWorker( void* fetcher_
, void* th_
)
378 return !atomic_load( &((struct fetcher_thread
*)th_
)->active
);
379 VLC_UNUSED( fetcher_
);
382 static void CloseWorker( void* fetcher_
, void* th_
)
384 struct fetcher_thread
* th
= th_
;
385 VLC_UNUSED( fetcher_
);
387 vlc_interrupt_kill( &th
->interrupt
);
388 vlc_join( th
->thread
, NULL
);
389 vlc_interrupt_deinit( &th
->interrupt
);
393 #define DEF_STARTER(name, worker) \
394 static int Start ## name( void* fetcher_, void* req_, void** out ) { \
395 playlist_fetcher_t* fetcher = fetcher_; \
396 return StartWorker( fetcher, name, worker, req_, out ); }
398 DEF_STARTER( SearchLocal
, fetcher
->local
)
399 DEF_STARTER(SearchNetwork
, fetcher
->network
)
400 DEF_STARTER( Downloader
, fetcher
->downloader
)
402 static void WorkerInit( playlist_fetcher_t
* fetcher
,
403 struct background_worker
** worker
, int( *starter
)( void*, void*, void** ) )
405 struct background_worker_config conf
= {
406 .default_timeout
= 0,
408 .pf_probe
= ProbeWorker
,
409 .pf_stop
= CloseWorker
,
410 .pf_release
= RequestRelease
,
411 .pf_hold
= RequestHold
};
413 *worker
= background_worker_New( fetcher
, &conf
);
416 playlist_fetcher_t
* playlist_fetcher_New( vlc_object_t
* owner
)
418 playlist_fetcher_t
* fetcher
= malloc( sizeof( *fetcher
) );
420 if( unlikely( !fetcher
) )
423 fetcher
->owner
= owner
;
425 WorkerInit( fetcher
, &fetcher
->local
, StartSearchLocal
);
426 WorkerInit( fetcher
, &fetcher
->network
, StartSearchNetwork
);
427 WorkerInit( fetcher
, &fetcher
->downloader
, StartDownloader
);
429 if( unlikely( !fetcher
->local
|| !fetcher
->network
|| !fetcher
->downloader
) )
432 background_worker_Delete( fetcher
->local
);
434 if( fetcher
->network
)
435 background_worker_Delete( fetcher
->network
);
437 if( fetcher
->downloader
)
438 background_worker_Delete( fetcher
->downloader
);
444 vlc_mutex_init( &fetcher
->lock
);
445 vlc_dictionary_init( &fetcher
->album_cache
, 0 );
450 int playlist_fetcher_Push( playlist_fetcher_t
* fetcher
, input_item_t
* item
,
451 input_item_meta_request_option_t options
, int preparse_status
)
453 struct fetcher_request
* req
= malloc( sizeof *req
);
455 if( unlikely( !req
) )
459 req
->options
= options
;
460 req
->preparse_status
= preparse_status
;
462 atomic_init( &req
->refs
, 1 );
463 input_item_Hold( item
);
465 if( background_worker_Push( fetcher
->local
, req
, NULL
, 0 ) )
468 RequestRelease( req
);
472 void playlist_fetcher_Delete( playlist_fetcher_t
* fetcher
)
474 background_worker_Delete( fetcher
->local
);
475 background_worker_Delete( fetcher
->network
);
476 background_worker_Delete( fetcher
->downloader
);
478 vlc_dictionary_clear( &fetcher
->album_cache
, FreeCacheEntry
, NULL
);
479 vlc_mutex_destroy( &fetcher
->lock
);