rss: use vlc_alloc helper
[vlc.git] / src / playlist / fetcher.c
blob44c13a55cafb821a99e6e93749ba2a11317eda17
1 /*****************************************************************************
2 * fetcher.c
3 *****************************************************************************
4 * Copyright © 2017-2017 VLC authors and VideoLAN
5 * $Id$
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 *****************************************************************************/
22 #ifdef HAVE_CONFIG_H
23 # include "config.h"
24 #endif
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>
36 #include "art.h"
37 #include "libvlc.h"
38 #include "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;
49 vlc_object_t* owner;
50 vlc_mutex_t lock;
53 struct fetcher_request {
54 input_item_t* item;
55 atomic_uint refs;
56 int preparse_status;
57 int options;
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;
68 vlc_thread_t thread;
69 atomic_bool active;
72 static char* CreateCacheKey( input_item_t* item )
74 vlc_mutex_lock( &item->lock );
76 if( !item->p_meta )
78 vlc_mutex_unlock( &item->lock );
79 return NULL;
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 );
84 char* key;
86 /**
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
89 * { dog, stick } */
90 if( !artist || !album || asprintf( &key, "%s:%zu:%s:%zu",
91 artist, strlen( artist ), album, strlen( album ) ) < 0 )
93 key = NULL;
95 vlc_mutex_unlock( &item->lock );
97 return key;
100 static void FreeCacheEntry( void* data, void* obj )
102 free( data );
103 VLC_UNUSED( obj );
106 static int ReadAlbumCache( playlist_fetcher_t* fetcher, input_item_t* item )
108 char* key = CreateCacheKey( item );
110 if( key == NULL )
111 return VLC_EGENERIC;
113 vlc_mutex_lock( &fetcher->lock );
114 char const* art = vlc_dictionary_value_for_key( &fetcher->album_cache,
115 key );
116 if( art )
117 input_item_SetArtURL( item, art );
118 vlc_mutex_unlock( &fetcher->lock );
120 free( key );
121 return art ? VLC_SUCCESS : VLC_EGENERIC;
124 static void AddAlbumCache( playlist_fetcher_t* fetcher, input_item_t* item,
125 bool overwrite )
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 );
136 art = NULL;
138 vlc_mutex_unlock( &fetcher->lock );
141 free( art );
142 free( key );
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 ) )
151 return VLC_ENOMEM;
153 mf->e_scope = scope;
154 mf->p_item = item;
156 module_t* mf_module = module_need( mf, type, NULL, false );
158 if( mf_module )
159 module_unneed( mf, mf_module );
161 vlc_object_release( mf );
163 return VLC_SUCCESS;
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 );
174 return error;
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 );
183 return error;
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" ) )
200 return VLC_EGENERIC;
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 ) )
211 return VLC_SUCCESS;
214 return VLC_EGENERIC;
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 );
232 if( !psz_arturl )
233 goto error;
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 );
241 if( !source )
242 goto error;
244 struct vlc_memstream output_stream;
245 vlc_memstream_open( &output_stream );
247 for( ;; )
249 char buffer[2048];
251 int read = vlc_stream_Read( source, buffer, sizeof( buffer ) );
252 if( read <= 0 )
253 break;
255 if( (int)vlc_memstream_write( &output_stream, buffer, read ) < read )
256 break;
259 vlc_stream_Delete( source );
261 if( vlc_memstream_close( &output_stream ) )
262 goto error;
264 if( vlc_killed() )
266 free( output_stream.ptr );
267 goto error;
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 );
276 out:
277 if( psz_arturl )
279 var_SetAddress( fetcher->owner, "item-change", req->item );
280 input_item_SetArtFetched( req->item, true );
283 free( psz_arturl );
284 SetPreparsed( req );
285 return;
287 error:
288 FREENULL( psz_arturl );
289 goto out;
292 static void SearchLocal( playlist_fetcher_t* fetcher, struct fetcher_request* req )
294 if( SearchByScope( fetcher, req, FETCHER_SCOPE_LOCAL ) == VLC_SUCCESS )
295 return; /* done */
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 ) )
301 SetPreparsed( req );
303 else
305 input_item_SetArtNotFound( req->item, true );
306 SetPreparsed( req );
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 );
315 SetPreparsed( req );
319 static void RequestRelease( void* req_ )
321 struct fetcher_request* req = req_;
323 if( atomic_fetch_sub( &req->refs, 1 ) != 1 )
324 return;
326 input_item_Release( req->item );
327 free( req );
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 );
345 return NULL;
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 ) )
355 return VLC_ENOMEM;
357 th->req = req;
358 th->worker = bg;
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 ) )
367 *handle = th;
368 return VLC_SUCCESS;
371 vlc_interrupt_deinit( &th->interrupt );
372 free( th );
373 return VLC_EGENERIC;
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 );
390 free( th );
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,
407 .pf_start = starter,
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 ) )
421 return NULL;
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 ) )
431 if( fetcher->local )
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 );
440 free( fetcher );
441 return NULL;
444 vlc_mutex_init( &fetcher->lock );
445 vlc_dictionary_init( &fetcher->album_cache, 0 );
447 return fetcher;
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 ) )
456 return VLC_ENOMEM;
458 req->item = item;
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 ) )
466 SetPreparsed( req );
468 RequestRelease( req );
469 return VLC_SUCCESS;
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 );
481 free( fetcher );