/*****************************************************************************
* fetcher.c
*****************************************************************************
* Copyright © 2017-2017 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <vlc_common.h>
#include <vlc_atomic.h>
#include <vlc_stream.h>
#include <vlc_modules.h>
#include <vlc_interrupt.h>
#include <vlc_arrays.h>
#include <vlc_threads.h>
#include <vlc_memstream.h>
#include <vlc_meta_fetcher.h>
#include <vlc_executor.h>
#include "art.h"
#include "../libvlc.h"
#include "fetcher.h"
#include "input/input_interface.h"
#include "misc/interrupt.h"
struct input_fetcher_t {
vlc_executor_t *executor_local;
vlc_executor_t *executor_network;
vlc_executor_t *executor_downloader;
vlc_dictionary_t album_cache;
vlc_object_t* owner;
vlc_mutex_t lock;
struct vlc_list submitted_tasks; /**< list of struct task */
};
struct task {
input_fetcher_t *fetcher;
vlc_executor_t *executor;
input_item_t* item;
int options;
const input_fetcher_callbacks_t *cbs;
void *userdata;
vlc_interrupt_t interrupt;
struct vlc_runnable runnable; /**< to be passed to the executor */
struct vlc_list node; /**< node of input_fetcher_t.submitted_tasks */
};
static void RunDownloader(void *);
static void RunSearchLocal(void *);
static void RunSearchNetwork(void *);
static struct task *
TaskNew(input_fetcher_t *fetcher, vlc_executor_t *executor, input_item_t *item,
int options,
const input_fetcher_callbacks_t *cbs, void *userdata)
{
struct task *task = malloc(sizeof(*task));
if (!task)
return NULL;
task->fetcher = fetcher;
task->executor = executor;
task->item = item;
task->options = options;
task->cbs = cbs;
task->userdata = userdata;
vlc_interrupt_init(&task->interrupt);
input_item_Hold(item);
if (executor == fetcher->executor_local)
task->runnable.run = RunSearchLocal;
else if (executor == fetcher->executor_network)
task->runnable.run = RunSearchNetwork;
else
task->runnable.run = RunDownloader;
task->runnable.userdata = task;
return task;
}
static void
TaskDelete(struct task *task)
{
input_item_Release(task->item);
vlc_interrupt_deinit(&task->interrupt);
free(task);
}
static void
FetcherAddTask(input_fetcher_t *fetcher, struct task *task)
{
vlc_mutex_lock(&fetcher->lock);
vlc_list_append(&task->node, &fetcher->submitted_tasks);
vlc_mutex_unlock(&fetcher->lock);
}
static void
FetcherRemoveTask(input_fetcher_t *fetcher, struct task *task)
{
vlc_mutex_lock(&fetcher->lock);
vlc_list_remove(&task->node);
vlc_mutex_unlock(&fetcher->lock);
}
static int
Submit(input_fetcher_t *fetcher, vlc_executor_t *executor, input_item_t *item,
int options,
const input_fetcher_callbacks_t *cbs, void *userdata)
{
struct task *task =
TaskNew(fetcher, executor, item, options, cbs, userdata);
if (!task)
return VLC_ENOMEM;
FetcherAddTask(fetcher, task);
vlc_executor_Submit(task->executor, &task->runnable);
return VLC_SUCCESS;
}
static char* CreateCacheKey( input_item_t* item )
{
vlc_mutex_lock( &item->lock );
if( !item->p_meta )
{
vlc_mutex_unlock( &item->lock );
return NULL;
}
char const* artist = vlc_meta_Get( item->p_meta, vlc_meta_Artist );
char const* album = vlc_meta_Get( item->p_meta, vlc_meta_Album );
char const *date = vlc_meta_Get( item->p_meta, vlc_meta_Date );
char* key;
/**
* Simple concatenation of artist and album can lead to the same key
* for entities that should not have such. Imagine { dogs, tick } and
* { dog, stick } */
if( !artist || !album || asprintf( &key, "%s:%zu:%s:%zu:%s",
artist, strlen( artist ), album, strlen( album ),
date ? date : "0000" ) < 0 )
{
key = NULL;
}
vlc_mutex_unlock( &item->lock );
return key;
}
static void FreeCacheEntry( void* data, void* obj )
{
free( data );
VLC_UNUSED( obj );
}
static int ReadAlbumCache( input_fetcher_t* fetcher, input_item_t* item )
{
char* key = CreateCacheKey( item );
if( key == NULL )
return VLC_EGENERIC;
vlc_mutex_lock( &fetcher->lock );
char const* art = vlc_dictionary_value_for_key( &fetcher->album_cache,
key );
if( art )
input_item_SetArtURL( item, art );
vlc_mutex_unlock( &fetcher->lock );
free( key );
return art ? VLC_SUCCESS : VLC_EGENERIC;
}
static void AddAlbumCache( input_fetcher_t* fetcher, input_item_t* item,
bool overwrite )
{
char* art = input_item_GetArtURL( item );
char* key = CreateCacheKey( item );
if( key && art && strncasecmp( art, "attachment://", 13 ) )
{
vlc_mutex_lock( &fetcher->lock );
if( overwrite || !vlc_dictionary_has_key( &fetcher->album_cache, key ) )
{
vlc_dictionary_insert( &fetcher->album_cache, key, art );
art = NULL;
}
vlc_mutex_unlock( &fetcher->lock );
}
free( art );
free( key );
}
static int InvokeModule( input_fetcher_t* fetcher, input_item_t* item,
int scope, char const* type )
{
meta_fetcher_t* mf = vlc_custom_create( fetcher->owner,
sizeof( *mf ), type );
if( unlikely( !mf ) )
return VLC_ENOMEM;
mf->e_scope = scope;
mf->p_item = item;
module_t* mf_module = module_need( mf, type, NULL, false );
if( mf_module )
module_unneed( mf, mf_module );
vlc_object_delete(mf);
return VLC_SUCCESS;
}
static int CheckMeta( input_item_t* item )
{
vlc_mutex_lock( &item->lock );
bool error = !item->p_meta ||
!vlc_meta_Get( item->p_meta, vlc_meta_Title ) ||
!vlc_meta_Get( item->p_meta, vlc_meta_Artist ) ||
!vlc_meta_Get( item->p_meta, vlc_meta_Album );
vlc_mutex_unlock( &item->lock );
return error;
}
static int CheckArt( input_item_t* item )
{
vlc_mutex_lock( &item->lock );
bool error = !item->p_meta ||
!vlc_meta_Get( item->p_meta, vlc_meta_ArtworkURL );
vlc_mutex_unlock( &item->lock );
return error;
}
static int SearchArt( input_fetcher_t* fetcher, input_item_t* item, int scope)
{
InvokeModule( fetcher, item, scope, "art finder" );
return CheckArt( item );
}
static int SearchByScope(struct task *task, int scope)
{
input_fetcher_t *fetcher = task->fetcher;
input_item_t* item = task->item;
if( CheckMeta( item ) &&
InvokeModule( fetcher, item, scope, "meta fetcher" ) )
{
return VLC_EGENERIC;
}
if( ! CheckArt( item ) ||
! ReadAlbumCache( fetcher, item ) ||
! input_FindArtInCacheUsingItemUID( item ) ||
! input_FindArtInCache( item ) ||
! SearchArt( fetcher, item, scope ) )
{
AddAlbumCache( fetcher, task->item, false );
int ret = Submit(fetcher, fetcher->executor_downloader, item,
task->options, task->cbs, task->userdata);
if (ret == VLC_SUCCESS)
return VLC_SUCCESS;
}
return VLC_EGENERIC;
}
static void NotifyArtFetchEnded(struct task *task, bool fetched)
{
if (task->cbs && task->cbs->on_art_fetch_ended)
task->cbs->on_art_fetch_ended(task->item, fetched, task->userdata);
}
static void RunDownloader(void *userdata)
{
vlc_thread_set_name("vlc-run-fetcher");
bool fetched = false;
struct task *task = userdata;
input_fetcher_t *fetcher = task->fetcher;
vlc_interrupt_set(&task->interrupt);
ReadAlbumCache( fetcher, task->item );
char *psz_arturl = input_item_GetArtURL( task->item );
if( !psz_arturl )
goto out;
if( !strncasecmp( psz_arturl, "file://", 7 ) ||
!strncasecmp( psz_arturl, "attachment://", 13 ) )
{
fetched = true;
goto out; /* no fetch required */
}
stream_t* source = vlc_stream_NewURL( fetcher->owner, psz_arturl );
if( !source )
goto out;
struct vlc_memstream output_stream;
vlc_memstream_open( &output_stream );
for( ;; )
{
char buffer[2048];
int read = vlc_stream_Read( source, buffer, sizeof( buffer ) );
if( read <= 0 )
break;
if( (int)vlc_memstream_write( &output_stream, buffer, read ) < read )
break;
}
vlc_stream_Delete( source );
if( vlc_memstream_close( &output_stream ) )
goto out;
if( vlc_killed() )
{
free( output_stream.ptr );
goto out;
}
input_SaveArt( fetcher->owner, task->item, output_stream.ptr,
output_stream.length, NULL );
free( output_stream.ptr );
AddAlbumCache( fetcher, task->item, true );
fetched = true;
out:
vlc_interrupt_set(NULL);
if( fetched )
input_item_SetArtFetched( task->item, true );
free( psz_arturl );
NotifyArtFetchEnded(task, fetched);
FetcherRemoveTask(fetcher, task);
TaskDelete(task);
}
static void RunSearchLocal(void *userdata)
{
vlc_thread_set_name("vlc-run-searchl");
struct task *task = userdata;
input_fetcher_t *fetcher = task->fetcher;
vlc_interrupt_set(&task->interrupt);
if( SearchByScope( task, FETCHER_SCOPE_LOCAL ) == VLC_SUCCESS )
goto end; /* done */
if( task->options & VLC_PREPARSER_TYPE_FETCHMETA_NET )
{
int ret = Submit(fetcher, fetcher->executor_network, task->item,
task->options, task->cbs, task->userdata);
if (ret != VLC_SUCCESS)
NotifyArtFetchEnded(task, false);
}
else
{
input_item_SetArtNotFound( task->item, true );
NotifyArtFetchEnded(task, false);
}
end:
vlc_interrupt_set(NULL);
FetcherRemoveTask(fetcher, task);
TaskDelete(task);
}
static void RunSearchNetwork(void *userdata)
{
vlc_thread_set_name("vlc-run-searchn");
struct task *task = userdata;
vlc_interrupt_set(&task->interrupt);
if( SearchByScope( task, FETCHER_SCOPE_NETWORK ) != VLC_SUCCESS )
{
input_item_SetArtNotFound( task->item, true );
NotifyArtFetchEnded(task, false);
}
vlc_interrupt_set(NULL);
input_fetcher_t *fetcher = task->fetcher;
FetcherRemoveTask(fetcher, task);
TaskDelete(task);
}
input_fetcher_t* input_fetcher_New( vlc_object_t* owner,
int request_type )
{
assert(request_type & VLC_PREPARSER_TYPE_FETCHMETA_ALL);
input_fetcher_t* fetcher = malloc( sizeof( *fetcher ) );
if( unlikely( !fetcher ) )
return NULL;
int max_threads = var_InheritInteger(owner, "fetch-art-threads");
if (max_threads < 1)
max_threads = 1;
if (request_type & VLC_PREPARSER_TYPE_FETCHMETA_LOCAL)
{
fetcher->executor_local = vlc_executor_New(max_threads);
if (!fetcher->executor_local)
{
free(fetcher);
return NULL;
}
}
else
fetcher->executor_local = NULL;
if (request_type & VLC_PREPARSER_TYPE_FETCHMETA_NET)
{
fetcher->executor_network = vlc_executor_New(max_threads);
if (!fetcher->executor_network)
{
if (fetcher->executor_local)
vlc_executor_Delete(fetcher->executor_local);
free(fetcher);
return NULL;
}
}
else
fetcher->executor_network = NULL;
fetcher->executor_downloader = vlc_executor_New(max_threads);
if (!fetcher->executor_downloader)
{
if (fetcher->executor_network)
vlc_executor_Delete(fetcher->executor_network);
if (fetcher->executor_local)
vlc_executor_Delete(fetcher->executor_local);
free(fetcher);
return NULL;
}
fetcher->owner = owner;
vlc_mutex_init(&fetcher->lock);
vlc_list_init(&fetcher->submitted_tasks);
vlc_dictionary_init( &fetcher->album_cache, 0 );
return fetcher;
}
int input_fetcher_Push(input_fetcher_t* fetcher, input_item_t* item,
int options,
const input_fetcher_callbacks_t *cbs, void *cbs_userdata)
{
assert(options & VLC_PREPARSER_TYPE_FETCHMETA_ALL);
if (options & VLC_PREPARSER_TYPE_FETCHMETA_LOCAL)
assert(fetcher->executor_local != NULL);
if (options & VLC_PREPARSER_TYPE_FETCHMETA_NET)
assert(fetcher->executor_network != NULL);
vlc_executor_t *executor = options & VLC_PREPARSER_TYPE_FETCHMETA_LOCAL
? fetcher->executor_local
: fetcher->executor_network;
return Submit(fetcher, executor, item, options, cbs, cbs_userdata);
}
static void
CancelAllTasks(input_fetcher_t *fetcher)
{
vlc_mutex_lock(&fetcher->lock);
struct task *task;
vlc_list_foreach(task, &fetcher->submitted_tasks, node)
{
bool canceled = vlc_executor_Cancel(task->executor, &task->runnable);
if (canceled)
{
NotifyArtFetchEnded(task, false);
vlc_list_remove(&task->node);
TaskDelete(task);
}
/* Otherwise, the task will be finished and destroyed after run() */
}
vlc_mutex_unlock(&fetcher->lock);
}
void input_fetcher_Delete( input_fetcher_t* fetcher )
{
CancelAllTasks(fetcher);
if (fetcher->executor_local)
vlc_executor_Delete(fetcher->executor_local);
if (fetcher->executor_network)
vlc_executor_Delete(fetcher->executor_network);
assert(fetcher->executor_downloader);
vlc_executor_Delete(fetcher->executor_downloader);
vlc_dictionary_clear( &fetcher->album_cache, FreeCacheEntry, NULL );
free( fetcher );
}
¤ Dauer der Verarbeitung: 0.7 Sekunden
¤
*© Formatika GbR, Deutschland
|
|