Commit 1a8ead40 authored by François Cartegnie's avatar François Cartegnie 🤞

Add addons management API

parent d05bec79
/*****************************************************************************
* vlc_addons.h : addons handling and describing
*****************************************************************************
* Copyright (C) 2013 VideoLAN and authors
*
* 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.
*****************************************************************************/
#ifndef VLC_ADDONS_H
#define VLC_ADDONS_H 1
#include <vlc_arrays.h>
#include <vlc_events.h>
# ifdef __cplusplus
extern "C" {
# endif
typedef enum addon_type_t
{
ADDON_UNKNOWN = 0,
ADDON_EXTENSION,
ADDON_PLAYLIST_PARSER,
ADDON_SERVICE_DISCOVERY,
ADDON_SKIN2,
ADDON_PLUGIN,
ADDON_OTHER
} addon_type_t;
typedef enum addon_state_t
{
ADDON_NOTINSTALLED = 0,
ADDON_INSTALLING,
ADDON_INSTALLED,
ADDON_UNINSTALLING
} addon_state_t;
typedef enum addon_flags_t
{
ADDON_BROKEN = 1, /* Have install inconsistency */
ADDON_MANAGEABLE = 1 << 1, /* Have manifest, can install or uninstall files */
ADDON_UPDATABLE = 1 << 2,
} addon_flags_t;
#define ADDON_UUID_SIZE 16
#define ADDON_UUID_PSZ_SIZE (ADDON_UUID_SIZE * 2 + 4)
typedef uint8_t addon_uuid_t[ADDON_UUID_SIZE];
typedef struct addon_file_t
{
addon_type_t e_filetype;
char *psz_download_uri;
char *psz_filename;
} addon_file_t;
struct addon_entry_t
{
vlc_mutex_t lock;
addon_type_t e_type;
addon_state_t e_state;
addon_flags_t e_flags;
/* data describing addon */
addon_uuid_t uuid;
char *psz_name;
char *psz_summary;
char *psz_description;
char *psz_author;
char *psz_source_uri; /* webpage, ... */
char *psz_image_uri;
char *psz_image_data; /* base64, png */
char *psz_version;
/* stats */
long int i_downloads;
long int i_score;
/* Lister */
char *psz_source_module;
/* files list */
char *psz_archive_uri; /* Archive */
DECL_ARRAY(addon_file_t *) files;
/* custom data storage (if needed by module/source) */
void * p_custom;
};
typedef struct addon_entry_t addon_entry_t;
typedef struct addons_finder_t addons_finder_t;
typedef struct addons_finder_sys_t addons_finder_sys_t;
struct addons_finder_t
{
VLC_COMMON_MEMBERS
int ( * pf_find )( addons_finder_t * );
int ( * pf_retrieve )( addons_finder_t *, addon_entry_t * );
DECL_ARRAY( addon_entry_t * ) entries;
char *psz_uri;
addons_finder_sys_t *p_sys;
};
typedef struct addons_storage_t addons_storage_t;
typedef struct addons_storage_sys_t addons_storage_sys_t;
struct addons_storage_t
{
VLC_COMMON_MEMBERS
int ( * pf_install )( addons_storage_t *, addon_entry_t * );
int ( * pf_remove )( addons_storage_t *, addon_entry_t * );
int ( * pf_catalog ) ( addons_storage_t *, addon_entry_t **, int );
addons_storage_sys_t *p_sys;
};
typedef struct addons_manager_private_t addons_manager_private_t;
struct addons_manager_t
{
vlc_event_manager_t * p_event_manager;
addons_manager_private_t *p_priv;
};
typedef struct addons_manager_t addons_manager_t;
/**
* addon entry lifecycle
*/
VLC_API addon_entry_t *addon_entry_New( void );
VLC_API addon_entry_t *addon_entry_Hold(addon_entry_t *);
VLC_API void addon_entry_Release(addon_entry_t *);
/**
* addons manager lifecycle
*/
VLC_API addons_manager_t *addons_manager_New( vlc_object_t * );
VLC_API void addons_manager_Delete( addons_manager_t * );
/**
* Charge currently installed, usable and manageable addons
* (default "addons storage" module)
*/
VLC_API int addons_manager_LoadCatalog( addons_manager_t * );
/**
* Gather addons info from repository (default "addons finder" module)
* If psz_uri is not NULL, only gather info from the pointed package.
*/
VLC_API void addons_manager_Gather( addons_manager_t *, const char *psz_uri );
/**
* Install or Remove the addon identified by its uuid
*/
VLC_API int addons_manager_Install( addons_manager_t *p_manager, const addon_uuid_t uuid );
VLC_API int addons_manager_Remove( addons_manager_t *p_manager, const addon_uuid_t uuid );
/**
* String uuid to binary uuid helpers
*/
static inline bool addons_uuid_read( const char *psz_uuid, addon_uuid_t *p_uuid )
{
if ( !psz_uuid ) return false;
if ( strlen( psz_uuid ) < ADDON_UUID_PSZ_SIZE ) return false;
int i = 0, j = 0;
while ( i<ADDON_UUID_PSZ_SIZE )
{
if ( *( psz_uuid + i ) == '-' )
i++;
int v;
sscanf( psz_uuid + i, "%02x", &v );
(*p_uuid)[j++] = v & 0xFF;
i+=2;
}
return true;
}
static inline char * addons_uuid_to_psz( const addon_uuid_t * p_uuid )
{
char *psz = (char*) calloc( ADDON_UUID_PSZ_SIZE + 1 , sizeof(char) );
if ( psz )
{
int i=0;
char *p = psz;
while ( i < ADDON_UUID_SIZE )
{
if ( i == 4 || i== 7 || i== 9 || i== 11 )
*p++ = '-';
int v = 0xFF & (*p_uuid)[i];
sprintf( p, "%02x", v );
p += 2;
i++;
}
}
return psz;
}
# ifdef __cplusplus
}
# endif
#endif
......@@ -335,6 +335,7 @@ typedef struct vlm_message_t vlm_message_t;
/* misc */
typedef struct vlc_meta_t vlc_meta_t;
typedef struct input_stats_t input_stats_t;
typedef struct addon_entry_t addon_entry_t;
/* Update */
typedef struct update_t update_t;
......
......@@ -129,7 +129,12 @@ typedef enum vlc_event_type_t {
vlc_ServicesDiscoveryItemRemoved,
vlc_ServicesDiscoveryItemRemoveAll,
vlc_ServicesDiscoveryStarted,
vlc_ServicesDiscoveryEnded
vlc_ServicesDiscoveryEnded,
/* Addons Manager events */
vlc_AddonFound,
vlc_AddonsDiscoveryEnded,
vlc_AddonChanged
} vlc_event_type_t;
/* Event definition */
......@@ -202,6 +207,11 @@ typedef struct vlc_event_t
void * unused;
} services_discovery_ended;
/* Addons */
struct vlc_addon_generic_event
{
addon_entry_t * p_entry;
} addon_generic_event;
} u;
} vlc_event_t;
......
......@@ -475,6 +475,7 @@ SOURCES_libvlc_common = \
misc/xml.c \
extras/libc.c \
extras/tdestroy.c \
misc/addons.c \
misc/filter.c \
misc/filter_chain.c \
misc/http_auth.c \
......
......@@ -641,3 +641,12 @@ vlc_keycode2str
vlc_str2keycode
fingerprinter_Create
fingerprinter_Destroy
addons_manager_New
addons_manager_Delete
addons_manager_Gather
addons_manager_LoadCatalog
addons_manager_Install
addons_manager_Remove
addon_entry_New
addon_entry_Hold
addon_entry_Release
/*****************************************************************************
* addons.c: VLC addons manager
*****************************************************************************
* Copyright (C) 2014 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_modules.h>
#include <vlc_arrays.h>
#include <vlc_events.h>
#include "libvlc.h"
#include <vlc_addons.h>
/*****************************************************************************
* Structures/definitions
*****************************************************************************/
typedef struct addon_entry_owner
{
addon_entry_t entry;
atomic_uint refs;
} addon_entry_owner_t;
struct addons_manager_private_t
{
vlc_object_t *p_parent;
struct
{
vlc_thread_t *p_thread;
bool b_live;
vlc_mutex_t lock;
char *psz_uri_hint; /* uri hint for non repo based install */
DECL_ARRAY(addon_entry_t*) entries;
} finder;
struct
{
vlc_thread_t *p_thread;
bool b_live;
vlc_mutex_t lock;
DECL_ARRAY(addon_entry_t*) entries;
} installer;
};
static void *FinderThread( void * );
static void LoadLocalStorage( addons_manager_t *p_manager );
/*****************************************************************************
* Public functions
*****************************************************************************/
addon_entry_t * addon_entry_New()
{
addon_entry_owner_t *owner = calloc( 1, sizeof(addon_entry_owner_t) );
if( unlikely(owner == NULL) )
return NULL;
atomic_init( &owner->refs, 1 );
addon_entry_t *p_entry = &owner->entry;
vlc_mutex_init( &p_entry->lock );
ARRAY_INIT( p_entry->files );
return p_entry;
}
addon_entry_t * addon_entry_Hold( addon_entry_t * p_entry )
{
addon_entry_owner_t *owner = (addon_entry_owner_t *) p_entry;
atomic_fetch_add( &owner->refs, 1 );
return p_entry;
}
void addon_entry_Release( addon_entry_t * p_entry )
{
addon_entry_owner_t *owner = (addon_entry_owner_t *) p_entry;
if( atomic_fetch_sub(&owner->refs, 1) != 1 )
return;
free( p_entry->psz_name );
free( p_entry->psz_summary );
free( p_entry->psz_description );
free( p_entry->psz_archive_uri );
free( p_entry->psz_author );
free( p_entry->psz_source_uri );
free( p_entry->psz_image_uri );
free( p_entry->psz_image_data );
free( p_entry->psz_source_module );
free( p_entry->psz_version );
free( p_entry->p_custom );
addon_file_t *p_file;
FOREACH_ARRAY( p_file, p_entry->files )
free( p_file->psz_filename );
free( p_file->psz_download_uri );
FOREACH_END()
ARRAY_RESET( p_entry->files );
vlc_mutex_destroy( &p_entry->lock );
free( owner );
}
addons_manager_t *addons_manager_New( vlc_object_t *p_this )
{
addons_manager_t *p_manager = malloc( sizeof(addons_manager_t) );
if ( !p_manager ) return NULL;
p_manager->p_priv = malloc( sizeof(addons_manager_private_t) );
if ( !p_manager->p_priv )
{
free( p_manager );
return NULL;
}
p_manager->p_event_manager = malloc( sizeof(vlc_event_manager_t) );
if ( !p_manager->p_event_manager )
{
free( p_manager->p_priv );
free( p_manager );
return NULL;
}
p_manager->p_priv->p_parent = p_this;
#define INIT_QUEUE( name ) \
p_manager->p_priv->name.b_live = false;\
p_manager->p_priv->name.p_thread = NULL;\
vlc_mutex_init( &p_manager->p_priv->name.lock );\
ARRAY_INIT( p_manager->p_priv->name.entries );
INIT_QUEUE( finder )
INIT_QUEUE( installer )
p_manager->p_priv->finder.psz_uri_hint = NULL;
vlc_event_manager_t *em = p_manager->p_event_manager;
vlc_event_manager_init( em, p_manager );
vlc_event_manager_register_event_type(em, vlc_AddonFound);
vlc_event_manager_register_event_type(em, vlc_AddonsDiscoveryEnded);
vlc_event_manager_register_event_type(em, vlc_AddonChanged);
return p_manager;
}
void addons_manager_Delete( addons_manager_t *p_manager )
{
if ( p_manager->p_priv->finder.b_live )
vlc_cancel( *p_manager->p_priv->finder.p_thread );
if ( p_manager->p_priv->installer.b_live )
vlc_cancel( *p_manager->p_priv->installer.p_thread );
vlc_event_manager_fini( p_manager->p_event_manager );
#define FREE_QUEUE( name ) \
vlc_mutex_lock( &p_manager->p_priv->name.lock );\
FOREACH_ARRAY( addon_entry_t *p_entry, p_manager->p_priv->name.entries )\
addon_entry_Release( p_entry );\
FOREACH_END();\
ARRAY_RESET( p_manager->p_priv->name.entries );\
vlc_mutex_unlock( &p_manager->p_priv->name.lock );\
vlc_mutex_destroy( &p_manager->p_priv->name.lock );
FREE_QUEUE( finder )
FREE_QUEUE( installer )
free( p_manager->p_priv->finder.psz_uri_hint );
free( p_manager->p_priv );
free( p_manager->p_event_manager );
free( p_manager );
}
void addons_manager_Gather( addons_manager_t *p_manager, const char *psz_uri )
{
vlc_mutex_lock( &p_manager->p_priv->finder.lock );
if ( psz_uri )
{
p_manager->p_priv->finder.psz_uri_hint = strdup( psz_uri );
}
if( !p_manager->p_priv->finder.b_live )
{
if( vlc_clone_detach( p_manager->p_priv->finder.p_thread, FinderThread, p_manager,
VLC_THREAD_PRIORITY_LOW ) )
msg_Err( p_manager->p_priv->p_parent,
"cannot spawn entries provider thread" );
else
p_manager->p_priv->finder.b_live = true;
}
vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
}
/*****************************************************************************
* Private functions
*****************************************************************************/
static addon_entry_t * getHeldEntryByUUID( addons_manager_t *p_manager,
const addon_uuid_t uuid )
{
addon_entry_t *p_return = NULL;
vlc_mutex_lock( &p_manager->p_priv->finder.lock );
FOREACH_ARRAY( addon_entry_t *p_entry, p_manager->p_priv->finder.entries )
if ( !memcmp( p_entry->uuid, uuid, sizeof( addon_uuid_t ) ) )
{
p_return = p_entry;
addon_entry_Hold( p_return );
break;
}
FOREACH_END()
vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
return p_return;
}
static void MergeSources( addons_manager_t *p_manager,
addon_entry_t **pp_addons, int i_count )
{
addon_entry_t *p_entry, *p_manager_entry;
addon_uuid_t zerouuid;
memset( zerouuid, 0, sizeof( addon_uuid_t ) );
for ( int i=0; i < i_count; i++ )
{
p_entry = pp_addons[i];
vlc_mutex_lock( &p_entry->lock );
if ( memcmp( p_entry->uuid, zerouuid, sizeof( addon_uuid_t ) ) )
p_manager_entry = getHeldEntryByUUID( p_manager, p_entry->uuid );
else
p_manager_entry = NULL;
if ( !p_manager_entry )
{
ARRAY_APPEND( p_manager->p_priv->finder.entries, p_entry );
vlc_event_t event;
event.type = vlc_AddonFound;
event.u.addon_generic_event.p_entry = p_entry;
vlc_event_send( p_manager->p_event_manager, &event );
}
else
{
vlc_mutex_lock( &p_manager_entry->lock );
if ( ( p_manager_entry->psz_version && p_entry->psz_version )
&& /* FIXME: better version comparison */
strcmp( p_manager_entry->psz_version, p_entry->psz_version )
)
{
p_manager_entry->e_flags |= ADDON_UPDATABLE;
}
vlc_mutex_unlock( &p_manager_entry->lock );
addon_entry_Release( p_manager_entry );
}
vlc_mutex_unlock( &p_entry->lock );
}
}
static void LoadLocalStorage( addons_manager_t *p_manager )
{
addons_finder_t *p_finder =
vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_finder ), "entries finder" );
module_t *p_module = module_need( p_finder, "addons finder",
"addons.store.list", true );
if( p_module )
{
ARRAY_INIT( p_finder->entries );
p_finder->pf_find( p_finder );
module_unneed( p_finder, p_module );
MergeSources( p_manager, p_finder->entries.p_elems, p_finder->entries.i_size );
ARRAY_RESET( p_finder->entries );
}
vlc_object_release( p_finder );
}
static void *FinderThread( void *p_data )
{
addons_manager_t *p_manager = p_data;
int i_cancel;
p_manager->p_priv->finder.b_live = true;
addons_finder_t *p_finder =
vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_finder ), "entries finder" );
if( p_finder != NULL )
{
module_t *p_module;
ARRAY_INIT( p_finder->entries );
p_finder->psz_uri = p_manager->p_priv->finder.psz_uri_hint;
p_manager->p_priv->finder.psz_uri_hint = NULL;
p_module = module_need( p_finder, "addons finder", NULL, false );
if( p_module )
{
i_cancel = vlc_savecancel();
p_finder->pf_find( p_finder );
vlc_restorecancel( i_cancel );
module_unneed( p_finder, p_module );
MergeSources( p_manager, p_finder->entries.p_elems, p_finder->entries.i_size );
}
ARRAY_RESET( p_finder->entries );
free( p_finder->psz_uri );
vlc_object_release( p_finder );
}
p_manager->p_priv->finder.b_live = false;
vlc_event_t event;
event.type = vlc_AddonsDiscoveryEnded;
event.u.addon_generic_event.p_entry = NULL;
vlc_event_send( p_manager->p_event_manager, &event );
return NULL;
}
static int addons_manager_WriteCatalog( addons_manager_t *p_manager )
{
int i_return = VLC_EGENERIC;
addons_storage_t *p_storage =
vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_storage ), "entries storage" );
module_t *p_module = module_need( p_storage, "addons storage",
"addons.store.install", true );
if( p_module )
{
vlc_mutex_lock( &p_manager->p_priv->finder.lock );
i_return = p_storage->pf_catalog( p_storage, p_manager->p_priv->finder.entries.p_elems,
p_manager->p_priv->finder.entries.i_size );
vlc_mutex_unlock( &p_manager->p_priv->finder.lock );
module_unneed( p_storage, p_module );
}
vlc_object_release( p_storage );
return i_return;
}
int addons_manager_LoadCatalog( addons_manager_t *p_manager )
{
LoadLocalStorage( p_manager );
return VLC_SUCCESS;
}
static int installOrRemoveAddon( addons_manager_t *p_manager, addon_entry_t *p_entry, bool b_install )
{
int i_return = VLC_EGENERIC;
addons_storage_t *p_storage =
vlc_custom_create( p_manager->p_priv->p_parent, sizeof( *p_storage ), "entries storage" );
module_t *p_module = module_need( p_storage, "addons storage",
"addons.store.install", true );
if( p_module )
{
if ( b_install )
i_return = p_storage->pf_install( p_storage, p_entry );
else
i_return = p_storage->pf_remove( p_storage, p_entry );
module_unneed( p_storage, p_module );
msg_Dbg( p_manager->p_priv->p_parent, "InstallAddon returns %d", i_return );
if ( i_return == VLC_SUCCESS )
{
/* Reset flags */
vlc_mutex_lock( &p_entry->lock );
p_entry->e_flags = ADDON_MANAGEABLE;
vlc_mutex_unlock( &p_entry->lock );
}
}
vlc_object_release( p_storage );
return i_return;
}
static void *InstallerThread( void *p_data )
{
addons_manager_t *p_manager = p_data;
int i_ret, i_cancel;
vlc_event_t event;
event.type = vlc_AddonChanged;
for( ;; )
{
vlc_testcancel();
vlc_mutex_lock( &p_manager->p_priv->installer.lock );
if ( !p_manager->p_priv->installer.entries.i_size )
{
/* No queued addons */
vlc_mutex_unlock( &p_manager->p_priv->installer.lock );
p_manager->p_priv->installer.b_live = false;
break;
}
addon_entry_t *p_entry = p_manager->p_priv->installer.entries.p_elems[0];
ARRAY_REMOVE( p_manager->p_priv->installer.entries, 0 );
addon_entry_Hold( p_entry );
vlc_mutex_unlock( &p_manager->p_priv->installer.lock );
vlc_mutex_lock( &p_entry->lock );
/* DO WORK */
if ( p_entry->e_state == ADDON_INSTALLED )
{
p_entry->e_state = ADDON_UNINSTALLING;
vlc_mutex_unlock( &p_entry->lock );
/* notify */
event.u.addon_generic_event.p_entry = p_entry;
vlc_event_send( p_manager->p_event_manager, &event );
i_cancel = vlc_savecancel();
i_ret = installOrRemoveAddon( p_manager, p_entry, false );
vlc_restorecancel( i_cancel );
vlc_mutex_lock( &p_entry->lock );
p_entry->e_state = ( i_ret == VLC_SUCCESS ) ? ADDON_NOTINSTALLED
: ADDON_INSTALLED;
vlc_mutex_unlock( &p_entry->lock );
}
else if ( p_entry->e_state == ADDON_NOTINSTALLED )
{
p_entry->e_state = ADDON_INSTALLING;
vlc_mutex_unlock( &p_entry->lock );
/* notify */
event.u.addon_generic_event.p_entry = p_entry;
vlc_event_send( p_manager->p_event_manager, &event );
i_cancel = vlc_savecancel();
i_ret = installOrRemoveAddon( p_manager, p_entry, true );
vlc_restorecancel( i_cancel );
vlc_mutex_lock( &p_entry->lock );
p_entry->e_state = ( i_ret == VLC_SUCCESS ) ? ADDON_INSTALLED
: ADDON_NOTINSTALLED;
vlc_mutex_unlock( &p_entry->lock );
}
else
vlc_mutex_unlock( &p_entry->lock );
/* !DO WORK */
event.u.addon_generic_event.p_entry = p_entry;
vlc_event_send( p_manager->p_event_manager, &event );
addon_entry_Release( p_entry );
}
i_cancel = vlc_savecancel();
addons_manager_WriteCatalog( p_manager );
vlc_restorecancel( i_cancel );
return NULL;
}
static int InstallEntry( addons_manager_t *p_manager, addon_entry_t *p_entry )
{
if ( p_entry->e_type == ADDON_UNKNOWN ||
p_entry->e_type == ADDON_PLUGIN ||
p_entry->e_type == ADDON_OTHER )
return VLC_EBADVAR;