diff --git a/include/vlc_media_source.h b/include/vlc_media_source.h new file mode 100644 index 0000000000000000000000000000000000000000..ac56124d2f5185fb163720a14d63f82f892fa7d0 --- /dev/null +++ b/include/vlc_media_source.h @@ -0,0 +1,239 @@ +/***************************************************************************** + * vlc_media_source.h + ***************************************************************************** + * Copyright (C) 2018 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. + *****************************************************************************/ + +#ifndef VLC_MEDIA_SOURCE_H +#define VLC_MEDIA_SOURCE_H + +#include <vlc_common.h> +#include <vlc_input_item.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \defgroup media_source Media source + * \ingroup input + * @{ + */ + +/** + * Media source API aims to manage "services discovery" easily from UI clients. + * + * A "media source provider", associated to the libvlc instance, allows to + * retrieve media sources (each associated to a services discovery module). + * + * Requesting a services discovery that is not open will automatically open it. + * If several "clients" request the same media source (i.e. by requesting the + * same name), they will receive the same (refcounted) media source instance. + * As soon as a media source is released by all its clients, the associated + * services discovery is closed. + * + * Each media source holds a media tree, used to store both the media + * detected by the services discovery and the media detected by preparsing. + * Clients may listen to the tree to be notified of changes. + * + * To preparse a media belonging to a media tree, use vlc_media_tree_Preparse(). + * If subitems are detected during the preparsing, the media tree is updated + * accordingly. + */ + +/** + * Media tree. + * + * Nodes must be traversed with locked held (vlc_media_tree_Lock()). + */ +typedef struct vlc_media_tree { + input_item_node_t root; +} vlc_media_tree_t; + +/** + * Callbacks to receive media tree events. + */ +struct vlc_media_tree_callbacks +{ + /** + * Called when the whole content of a subtree has changed. + * + * \param playlist the playlist + * \param node the node having its children reset (may be root) + * \param userdata userdata provided to AddListener() + */ + void + (*on_children_reset)(vlc_media_tree_t *tree, input_item_node_t *node, + void *userdata); + + /** + * Called when children has been added to a node. + * + * The children may themselves contain children, which will not be notified + * separately. + * + * \param playlist the playlist + * \param node the node having children added + * \param children the children added + * \param count the number of children added + * \param userdata userdata provided to AddListener() + */ + void + (*on_children_added)(vlc_media_tree_t *tree, input_item_node_t *node, + input_item_node_t *const children[], size_t count, + void *userdata); + + /** + * Called when children has been removed from a node. + * + * \param playlist the playlist + * \param node the node having children removed + * \param children the children removed + * \param count the number of children removed + * \param userdata userdata provided to AddListener() + */ + void + (*on_children_removed)(vlc_media_tree_t *tree, input_item_node_t *node, + input_item_node_t *const children[], size_t count, + void *userdata); +}; + +/** + * Listener for media tree events. + */ +typedef struct vlc_media_tree_listener_id vlc_media_tree_listener_id; + +/** + * Add a listener. The lock must NOT be held. + * + * \param tree the media tree, unlocked + * \param cbs the callbacks (must be valid until the listener + * is removed) + * \param userdata userdata provided as a parameter in callbacks + * \param notify_current_state true to notify the current state immediately via + * callbacks + */ +VLC_API vlc_media_tree_listener_id * +vlc_media_tree_AddListener(vlc_media_tree_t *tree, + const struct vlc_media_tree_callbacks *cbs, + void *userdata, bool notify_current_state); + +/** + * Remove a listener. The lock must NOT be held. + * + * \param tree the media tree, unlocked + * \param listener the listener identifier returned by + * vlc_media_tree_AddListener() + */ +VLC_API void +vlc_media_tree_RemoveListener(vlc_media_tree_t *tree, + vlc_media_tree_listener_id *listener); + +/** + * Lock the media tree (non-recursive). + */ +VLC_API void +vlc_media_tree_Lock(vlc_media_tree_t *); + +/** + * Unlock the media tree. + */ +VLC_API void +vlc_media_tree_Unlock(vlc_media_tree_t *); + +/** + * Find the node containing the requested input item (and its parent). + * + * \param tree the media tree, locked + * \param result point to the matching node if the function returns true [OUT] + * \param result_parent if not NULL, point to the matching node parent + * if the function returns true [OUT] + * + * \retval true if item was found + * \retval false if item was not found + */ +VLC_API bool +vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *media, + input_item_node_t **result, + input_item_node_t **result_parent); + +/** + * Preparse a media, and expand it in the media tree on subitems added. + * + * \param tree the media tree (not necessarily locked) + * \param libvlc the libvlc instance + * \param media the media to preparse + */ +VLC_API void +vlc_media_tree_Preparse(vlc_media_tree_t *tree, libvlc_int_t *libvlc, + input_item_t *media); + +/** + * Media source. + * + * A media source is associated to a "service discovery". It stores the + * detected media in a media tree. + */ +typedef struct vlc_media_source_t +{ + vlc_media_tree_t *tree; + const char *description; +} vlc_media_source_t; + +/** + * Increase the media source reference count. + */ +VLC_API void +vlc_media_source_Hold(vlc_media_source_t *); + +/** + * Decrease the media source reference count. + * + * Destroy the media source and close the associated "service discovery" if it + * reaches 0. + */ +VLC_API void +vlc_media_source_Release(vlc_media_source_t *); + +/** + * Media source provider (opaque pointer), used to get media sources. + */ +typedef struct vlc_media_source_provider_t vlc_media_source_provider_t; + +/** + * Return the media source provider associated to the libvlc instance. + */ +VLC_API vlc_media_source_provider_t * +vlc_media_source_provider_Get(libvlc_int_t *); + +/** + * Return the media source identified by psz_name. + * + * The resulting media source must be released by vlc_media_source_Release(). + */ +VLC_API vlc_media_source_t * +vlc_media_source_provider_GetMediaSource(vlc_media_source_provider_t *, + const char *name); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/include/vlc_services_discovery.h b/include/vlc_services_discovery.h index 1f5305b788e521ca75b3c26f84a2b656240fbc9a..e117a1c0a527c64c93adb42022c390c31b85ec23 100644 --- a/include/vlc_services_discovery.h +++ b/include/vlc_services_discovery.h @@ -149,6 +149,8 @@ VLC_API char ** vlc_sd_GetNames( vlc_object_t *, char ***, int ** ) VLC_USED; VLC_API services_discovery_t *vlc_sd_Create(vlc_object_t *parent, const char *chain, const struct services_discovery_owner_t *owner) VLC_USED; +#define vlc_sd_Create( obj, a, b ) \ + vlc_sd_Create( VLC_OBJECT( obj ), a, b ) VLC_API void vlc_sd_Destroy( services_discovery_t * ); diff --git a/src/Makefile.am b/src/Makefile.am index d6bf427179ed71f003dfa2d348550ab571539515..aec931cdd42624c52cb6a94aa94ddf4a2d2978b9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -63,6 +63,7 @@ pluginsinclude_HEADERS = \ ../include/vlc_keystore.h \ ../include/vlc_list.h \ ../include/vlc_md5.h \ + ../include/vlc_media_source.h \ ../include/vlc_messages.h \ ../include/vlc_meta.h \ ../include/vlc_meta_fetcher.h \ @@ -207,6 +208,10 @@ libvlccore_la_SOURCES = \ config/getopt.c \ config/vlc_getopt.h \ extras/libc.c \ + media_source/media_source.c \ + media_source/media_source.h \ + media_source/media_tree.c \ + media_source/media_tree.h \ modules/modules.h \ modules/modules.c \ modules/bank.c \ @@ -561,7 +566,8 @@ check_PROGRAMS = \ test_vector \ test_shared_data_ptr \ test_playlist \ - test_randomizer + test_randomizer \ + test_media_source TESTS = $(check_PROGRAMS) check_symbols @@ -601,6 +607,11 @@ test_playlist_SOURCES = playlist/test.c \ test_playlist_CFLAGS = -DTEST_PLAYLIST test_randomizer_SOURCES = playlist/randomizer.c test_randomizer_CFLAGS = -DTEST_RANDOMIZER +test_media_source_LDADD = $(LDADD) $(LIBS_libvlccore) +test_media_source_CFLAGS = -DTEST_MEDIA_SOURCE +test_media_source_SOURCES = media_source/test.c \ + media_source/media_source.c \ + media_source/media_tree.c AM_LDFLAGS = -no-install LDADD = libvlccore.la \ diff --git a/src/input/services_discovery.c b/src/input/services_discovery.c index 12a029ef6e6e2c18dd81fea94a69a0f1d38a1144..7d43e4ee7abd6aa0f793ebc75adb493139fe8802 100644 --- a/src/input/services_discovery.c +++ b/src/input/services_discovery.c @@ -103,6 +103,7 @@ char **vlc_sd_GetNames (vlc_object_t *obj, char ***pppsz_longnames, int **pp_cat * That's how the playlist get's Service Discovery information */ +#undef vlc_sd_Create services_discovery_t *vlc_sd_Create(vlc_object_t *parent, const char *cfg, const struct services_discovery_owner_t *restrict owner) { diff --git a/src/libvlc.c b/src/libvlc.c index 93ce67a00dc1e2afdb36e0ff383b1e400b1c57f7..166bf96b94560e4569073db96d1334fc50834f8e 100644 --- a/src/libvlc.c +++ b/src/libvlc.c @@ -43,6 +43,7 @@ #include "modules/modules.h" #include "config/configuration.h" #include "preparser/preparser.h" +#include "media_source/media_source.h" #include <stdio.h> /* sprintf() */ #include <string.h> @@ -98,6 +99,7 @@ libvlc_int_t * libvlc_InternalCreate( void ) priv->playlist = NULL; priv->main_playlist = NULL; priv->p_vlm = NULL; + priv->media_source_provider = NULL; vlc_ExitInit( &priv->exit ); @@ -291,6 +293,10 @@ int libvlc_InternalInit( libvlc_int_t *p_libvlc, int i_argc, if( !priv->parser ) goto error; + priv->media_source_provider = vlc_media_source_provider_New( VLC_OBJECT( p_libvlc ) ); + if( !priv->media_source_provider ) + goto error; + /* variables for signalling creation of new files */ var_Create( p_libvlc, "snapshot-file", VLC_VAR_STRING ); var_Create( p_libvlc, "record-file", VLC_VAR_STRING ); @@ -436,6 +442,9 @@ void libvlc_InternalCleanup( libvlc_int_t *p_libvlc ) if ( priv->p_media_library ) libvlc_MlRelease( priv->p_media_library ); + if( priv->media_source_provider ) + vlc_media_source_provider_Delete( priv->media_source_provider ); + libvlc_InternalDialogClean( p_libvlc ); libvlc_InternalKeystoreClean( p_libvlc ); diff --git a/src/libvlc.h b/src/libvlc.h index 7fc6934406a76e29ce4271c20aad3c700dfb721a..a0adeeb26adfd61d9d3f1ee697ec9fc79531c447 100644 --- a/src/libvlc.h +++ b/src/libvlc.h @@ -184,6 +184,7 @@ typedef struct vlc_dialog_provider vlc_dialog_provider; typedef struct vlc_keystore vlc_keystore; typedef struct vlc_actions_t vlc_actions_t; typedef struct vlc_playlist vlc_playlist_t; +typedef struct vlc_media_source_provider_t vlc_media_source_provider_t; typedef struct libvlc_priv_t { @@ -197,6 +198,7 @@ typedef struct libvlc_priv_t struct playlist_t *playlist; ///< Playlist for interfaces vlc_playlist_t *main_playlist; struct input_preparser_t *parser; ///< Input item meta data handler + vlc_media_source_provider_t *media_source_provider; vlc_actions_t *actions; ///< Hotkeys handler struct vlc_medialibrary_t *p_media_library; ///< Media library instance struct vlc_thumbnailer_t *p_thumbnailer; ///< Lazily instantiated media thumbnailer diff --git a/src/libvlccore.sym b/src/libvlccore.sym index 0b00e960cc10345a91ab78b4ad1cb8c89b8f9ade..9c4a7eab49a932681f025a4379dca84d8c569fb6 100644 --- a/src/libvlccore.sym +++ b/src/libvlccore.sym @@ -946,3 +946,13 @@ vlc_playlist_Pause vlc_playlist_Resume vlc_playlist_Preparse vlc_intf_GetMainPlaylist +vlc_media_source_Hold +vlc_media_source_Release +vlc_media_source_provider_Get +vlc_media_source_provider_GetMediaSource +vlc_media_tree_AddListener +vlc_media_tree_RemoveListener +vlc_media_tree_Lock +vlc_media_tree_Unlock +vlc_media_tree_Find +vlc_media_tree_Preparse diff --git a/src/media_source/media_source.c b/src/media_source/media_source.c new file mode 100644 index 0000000000000000000000000000000000000000..6f952cd1716391b01a3019da6ef2a7eb452bb244 --- /dev/null +++ b/src/media_source/media_source.c @@ -0,0 +1,274 @@ +/***************************************************************************** + * media_source.c + ***************************************************************************** + * Copyright (C) 2018 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 "media_source.h" + +#include <assert.h> +#include <vlc_atomic.h> +#include <vlc_playlist.h> +#include <vlc_services_discovery.h> +#include "libvlc.h" +#include "media_tree.h" + +#ifdef TEST_MEDIA_SOURCE +#define vlc_module_name "test" +#endif /* TEST_MEDIA_SOURCE */ + +typedef struct +{ + vlc_media_source_t public_data; + + services_discovery_t *sd; + vlc_atomic_rc_t rc; + vlc_media_source_provider_t *owner; + struct vlc_list node; + char name[]; +} media_source_private_t; + +#define ms_priv(ms) container_of(ms, media_source_private_t, public_data) + +struct vlc_media_source_provider_t +{ + struct vlc_common_members obj; + vlc_mutex_t lock; + struct vlc_list media_sources; +}; + +/* A new item has been added to a certain services discovery */ +static void +services_discovery_item_added(services_discovery_t *sd, + input_item_t *parent, input_item_t *media, + const char *cat) +{ + assert(!parent || !cat); + VLC_UNUSED(cat); + + vlc_media_source_t *ms = sd->owner.sys; + vlc_media_tree_t *tree = ms->tree; + + msg_Dbg(sd, "adding: %s", media->psz_name ? media->psz_name : "(null)"); + + vlc_media_tree_Lock(tree); + + input_item_node_t *parent_node; + if (parent) + vlc_media_tree_Find(tree, parent, &parent_node, NULL); + else + parent_node = &tree->root; + + bool added = vlc_media_tree_Add(tree, parent_node, media) != NULL; + if (unlikely(!added)) + msg_Err(sd, "could not allocate media tree node"); + + vlc_media_tree_Unlock(tree); +} + +static void +services_discovery_item_removed(services_discovery_t *sd, input_item_t *media) +{ + vlc_media_source_t *ms = sd->owner.sys; + vlc_media_tree_t *tree = ms->tree; + + msg_Dbg(sd, "removing: %s", media->psz_name ? media->psz_name : "(null)"); + + vlc_media_tree_Lock(tree); + bool removed = vlc_media_tree_Remove(tree, media); + vlc_media_tree_Unlock(tree); + + if (unlikely(!removed)) + { + msg_Err(sd, "removing item not added"); /* SD plugin bug */ + return; + } +} + +static const struct services_discovery_callbacks sd_cbs = { + .item_added = services_discovery_item_added, + .item_removed = services_discovery_item_removed, +}; + +static vlc_media_source_t * +vlc_media_source_New(vlc_media_source_provider_t *provider, const char *name) +{ + media_source_private_t *priv = malloc(sizeof(*priv) + strlen(name) + 1); + if (unlikely(!priv)) + return NULL; + + vlc_atomic_rc_init(&priv->rc); + + vlc_media_source_t *ms = &priv->public_data; + + /* vlc_sd_Create() may call services_discovery_item_added(), which will read + * its tree, so it must be initialized first */ + ms->tree = vlc_media_tree_New(); + if (unlikely(!ms->tree)) + { + free(priv); + return NULL; + } + + strcpy(priv->name, name); + + struct services_discovery_owner_t owner = { + .cbs = &sd_cbs, + .sys = ms, + }; + + priv->sd = vlc_sd_Create(provider, name, &owner); + if (unlikely(!priv->sd)) + { + vlc_media_tree_Release(ms->tree); + free(priv); + return NULL; + } + + /* sd->description is set during vlc_sd_Create() */ + ms->description = priv->sd->description; + + priv->owner = provider; + + return ms; +} + +static void +vlc_media_source_provider_Remove(vlc_media_source_provider_t *provider, + vlc_media_source_t *ms) +{ + vlc_mutex_lock(&provider->lock); + vlc_list_remove(&ms_priv(ms)->node); + vlc_mutex_unlock(&provider->lock); +} + +static void +vlc_media_source_Delete(vlc_media_source_t *ms) +{ + media_source_private_t *priv = ms_priv(ms); + vlc_media_source_provider_Remove(priv->owner, ms); + vlc_sd_Destroy(priv->sd); + vlc_media_tree_Release(ms->tree); + free(priv); +} + +void +vlc_media_source_Hold(vlc_media_source_t *ms) +{ + media_source_private_t *priv = ms_priv(ms); + vlc_atomic_rc_inc(&priv->rc); +} + +void +vlc_media_source_Release(vlc_media_source_t *ms) +{ + media_source_private_t *priv = ms_priv(ms); + if (vlc_atomic_rc_dec(&priv->rc)) + vlc_media_source_Delete(ms); +} + +static vlc_media_source_t * +vlc_media_source_provider_Find(vlc_media_source_provider_t *provider, + const char *name) +{ + vlc_mutex_assert(&provider->lock); + media_source_private_t *entry; + vlc_list_foreach(entry, &provider->media_sources, node) + if (!strcmp(name, entry->name)) + return &entry->public_data; + return NULL; +} + +vlc_media_source_provider_t * +vlc_media_source_provider_Get(libvlc_int_t *libvlc) +{ + return libvlc_priv(libvlc)->media_source_provider; +} + +static void * +CreateObject(vlc_object_t *parent, size_t length, const char *typename) +{ +#ifdef TEST_MEDIA_SOURCE + VLC_UNUSED(parent); + VLC_UNUSED(typename); + return malloc(length); +#else + return vlc_custom_create(parent, length, typename); +#endif +} + +static void +ReleaseObject(void *obj) +{ +#ifdef TEST_MEDIA_SOURCE + free(obj); +#else + vlc_object_release((vlc_object_t *) obj); +#endif +} + +#undef vlc_media_source_provider_New +vlc_media_source_provider_t * +vlc_media_source_provider_New(vlc_object_t *parent) +{ + vlc_media_source_provider_t *provider = + CreateObject(parent, sizeof(*provider), "media-source-provider"); + if (unlikely(!provider)) + return NULL; + + vlc_mutex_init(&provider->lock); + vlc_list_init(&provider->media_sources); + return provider; +} + +void +vlc_media_source_provider_Delete(vlc_media_source_provider_t *provider) +{ + vlc_mutex_destroy(&provider->lock); + ReleaseObject(provider); +} + +static vlc_media_source_t * +vlc_media_source_provider_Add(vlc_media_source_provider_t *provider, + const char *name) +{ + vlc_mutex_assert(&provider->lock); + + vlc_media_source_t *ms = vlc_media_source_New(provider, name); + if (unlikely(!ms)) + return NULL; + + vlc_list_append(&ms_priv(ms)->node, &provider->media_sources); + return ms; +} + +vlc_media_source_t * +vlc_media_source_provider_GetMediaSource(vlc_media_source_provider_t *provider, + const char *name) +{ + vlc_mutex_lock(&provider->lock); + vlc_media_source_t *ms = vlc_media_source_provider_Find(provider, name); + if (!ms) + ms = vlc_media_source_provider_Add(provider, name); + vlc_mutex_unlock(&provider->lock); + + return ms; +} diff --git a/src/media_source/media_source.h b/src/media_source/media_source.h new file mode 100644 index 0000000000000000000000000000000000000000..bf187c4b38d0e6cfe44d6324f9d1b342a3df0cc4 --- /dev/null +++ b/src/media_source/media_source.h @@ -0,0 +1,33 @@ +/***************************************************************************** + * media_source.h + ***************************************************************************** + * Copyright (C) 2018 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. + *****************************************************************************/ + +#ifndef MEDIA_SOURCE_H +#define MEDIA_SOURCE_H + +#include <vlc_media_source.h> + +vlc_media_source_provider_t * +vlc_media_source_provider_New(vlc_object_t *parent); +#define vlc_media_source_provider_New(obj) \ + vlc_media_source_provider_New(VLC_OBJECT(obj)) + +void vlc_media_source_provider_Delete(vlc_media_source_provider_t *msp); + +#endif diff --git a/src/media_source/media_tree.c b/src/media_source/media_tree.c new file mode 100644 index 0000000000000000000000000000000000000000..6dd7055bccc98493e97ed0c2eaaa8ec87b49928e --- /dev/null +++ b/src/media_source/media_tree.c @@ -0,0 +1,326 @@ +/***************************************************************************** + * media_tree.c + ***************************************************************************** + * Copyright (C) 2018 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 "media_tree.h" + +#include <assert.h> +#include <vlc_common.h> +#include <vlc_arrays.h> +#include <vlc_atomic.h> +#include <vlc_input_item.h> +#include <vlc_threads.h> +#include "libvlc.h" + +struct vlc_media_tree_listener_id +{ + const struct vlc_media_tree_callbacks *cbs; + void *userdata; + struct vlc_list node; /**< node of media_tree_private_t.listeners */ +}; + +typedef struct +{ + vlc_media_tree_t public_data; + + struct vlc_list listeners; /**< list of vlc_media_tree_listener_id.node */ + vlc_mutex_t lock; + vlc_atomic_rc_t rc; +} media_tree_private_t; + +#define mt_priv(mt) container_of(mt, media_tree_private_t, public_data) + +vlc_media_tree_t * +vlc_media_tree_New(void) +{ + media_tree_private_t *priv = malloc(sizeof(*priv)); + if (unlikely(!priv)) + return NULL; + + vlc_mutex_init(&priv->lock); + vlc_atomic_rc_init(&priv->rc); + vlc_list_init(&priv->listeners); + + vlc_media_tree_t *tree = &priv->public_data; + input_item_node_t *root = &tree->root; + root->p_item = NULL; + TAB_INIT(root->i_children, root->pp_children); + + return tree; +} + +static inline void +vlc_media_tree_AssertLocked(vlc_media_tree_t *tree) +{ + media_tree_private_t *priv = mt_priv(tree); + vlc_mutex_assert(&priv->lock); +} + +#define vlc_media_tree_listener_foreach(listener, tree) \ + vlc_list_foreach(listener, &mt_priv(tree)->listeners, node) + +#define vlc_media_tree_NotifyListener(tree, listener, event, ...) \ +do { \ + if (listener->cbs->event) \ + listener->cbs->event(tree, ##__VA_ARGS__, listener->userdata); \ +} while(0) + +#define vlc_media_tree_Notify(tree, event, ...) \ +do { \ + vlc_media_tree_AssertLocked(tree); \ + vlc_media_tree_listener_id *listener; \ + vlc_media_tree_listener_foreach(listener, tree) \ + vlc_media_tree_NotifyListener(tree, listener, event, ##__VA_ARGS__); \ +} while (0) + +static bool +vlc_media_tree_FindNodeByMedia(input_item_node_t *parent, + const input_item_t *media, + input_item_node_t **result, + input_item_node_t **result_parent) +{ + for (int i = 0; i < parent->i_children; ++i) + { + input_item_node_t *child = parent->pp_children[i]; + if (child->p_item == media) + { + *result = child; + if (result_parent) + *result_parent = parent; + return true; + } + + if (vlc_media_tree_FindNodeByMedia(child, media, result, result_parent)) + return true; + } + + return false; +} + +static input_item_node_t * +vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media); + +static void +vlc_media_tree_AddSubtree(input_item_node_t *to, input_item_node_t *from) +{ + for (int i = 0; i < from->i_children; ++i) + { + input_item_node_t *child = from->pp_children[i]; + input_item_node_t *node = vlc_media_tree_AddChild(to, child->p_item); + if (unlikely(!node)) + break; /* what could we do? */ + + vlc_media_tree_AddSubtree(node, child); + } +} + +static void +media_subtree_changed(input_item_t *media, input_item_node_t *node, + void *userdata) +{ + vlc_media_tree_t *tree = userdata; + + vlc_media_tree_Lock(tree); + input_item_node_t *subtree_root; + /* TODO retrieve the node without traversing the tree */ + bool found = vlc_media_tree_FindNodeByMedia(&tree->root, media, + &subtree_root, NULL); + if (!found) { + /* the node probably failed to be allocated */ + vlc_media_tree_Unlock(tree); + return; + } + + vlc_media_tree_AddSubtree(subtree_root, node); + vlc_media_tree_Notify(tree, on_children_reset, subtree_root); + vlc_media_tree_Unlock(tree); +} + +static void +vlc_media_tree_DestroyRootNode(vlc_media_tree_t *tree) +{ + input_item_node_t *root = &tree->root; + for (int i = 0; i < root->i_children; ++i) + input_item_node_Delete(root->pp_children[i]); + + free(root->pp_children); +} + +static void +vlc_media_tree_Delete(vlc_media_tree_t *tree) +{ + media_tree_private_t *priv = mt_priv(tree); + vlc_media_tree_listener_id *listener; + vlc_list_foreach(listener, &priv->listeners, node) + free(listener); + vlc_list_init(&priv->listeners); /* reset */ + vlc_media_tree_DestroyRootNode(tree); + vlc_mutex_destroy(&priv->lock); + free(tree); +} + +void +vlc_media_tree_Hold(vlc_media_tree_t *tree) +{ + media_tree_private_t *priv = mt_priv(tree); + vlc_atomic_rc_inc(&priv->rc); +} + +void +vlc_media_tree_Release(vlc_media_tree_t *tree) +{ + media_tree_private_t *priv = mt_priv(tree); + if (vlc_atomic_rc_dec(&priv->rc)) + vlc_media_tree_Delete(tree); +} + +void +vlc_media_tree_Lock(vlc_media_tree_t *tree) +{ + media_tree_private_t *priv = mt_priv(tree); + vlc_mutex_lock(&priv->lock); +} + +void +vlc_media_tree_Unlock(vlc_media_tree_t *tree) +{ + media_tree_private_t *priv = mt_priv(tree); + vlc_mutex_unlock(&priv->lock); +} + +static input_item_node_t * +vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media) +{ + input_item_node_t *node = input_item_node_Create(media); + if (unlikely(!node)) + return NULL; + + input_item_node_AppendNode(parent, node); + + return node; +} + +static void +vlc_media_tree_NotifyCurrentState(vlc_media_tree_t *tree, + vlc_media_tree_listener_id *listener) +{ + vlc_media_tree_NotifyListener(tree, listener, on_children_reset, + &tree->root); +} + +vlc_media_tree_listener_id * +vlc_media_tree_AddListener(vlc_media_tree_t *tree, + const struct vlc_media_tree_callbacks *cbs, + void *userdata, bool notify_current_state) +{ + vlc_media_tree_listener_id *listener = malloc(sizeof(*listener)); + if (unlikely(!listener)) + return NULL; + listener->cbs = cbs; + listener->userdata = userdata; + + media_tree_private_t *priv = mt_priv(tree); + vlc_media_tree_Lock(tree); + + vlc_list_append(&listener->node, &priv->listeners); + + if (notify_current_state) + vlc_media_tree_NotifyCurrentState(tree, listener); + + vlc_media_tree_Unlock(tree); + return listener; +} + +void +vlc_media_tree_RemoveListener(vlc_media_tree_t *tree, + vlc_media_tree_listener_id *listener) +{ + vlc_media_tree_Lock(tree); + vlc_list_remove(&listener->node); + vlc_media_tree_Unlock(tree); + + free(listener); +} + +input_item_node_t * +vlc_media_tree_Add(vlc_media_tree_t *tree, input_item_node_t *parent, + input_item_t *media) +{ + vlc_media_tree_AssertLocked(tree); + + input_item_node_t *node = vlc_media_tree_AddChild(parent, media); + if (unlikely(!node)) + return NULL; + + vlc_media_tree_Notify(tree, on_children_added, parent, &node, 1); + + return node; +} + +bool +vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *media, + input_item_node_t **result, + input_item_node_t **result_parent) +{ + vlc_media_tree_AssertLocked(tree); + + /* quick & dirty depth-first O(n) implementation, with n the number of nodes + * in the tree */ + return vlc_media_tree_FindNodeByMedia(&tree->root, media, result, + result_parent); +} + +bool +vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *media) +{ + vlc_media_tree_AssertLocked(tree); + + input_item_node_t *node; + input_item_node_t *parent; + if (!vlc_media_tree_FindNodeByMedia(&tree->root, media, &node, &parent)) + return false; + + input_item_node_RemoveNode(parent, node); + vlc_media_tree_Notify(tree, on_children_removed, parent, &node, 1); + input_item_node_Delete(node); + return true; +} + +static const input_preparser_callbacks_t input_preparser_callbacks = { + .on_subtree_added = media_subtree_changed, +}; + +void +vlc_media_tree_Preparse(vlc_media_tree_t *tree, libvlc_int_t *libvlc, + input_item_t *media) +{ +#ifdef TEST_MEDIA_SOURCE + VLC_UNUSED(tree); + VLC_UNUSED(libvlc); + VLC_UNUSED(media); + VLC_UNUSED(input_preparser_callbacks); +#else + vlc_MetadataRequest(libvlc, media, META_REQUEST_OPTION_NONE, + &input_preparser_callbacks, tree, -1, NULL); +#endif +} diff --git a/src/media_source/media_tree.h b/src/media_source/media_tree.h new file mode 100644 index 0000000000000000000000000000000000000000..4614e2b824ce9388e31a4e5dce965dc37a6422c1 --- /dev/null +++ b/src/media_source/media_tree.h @@ -0,0 +1,42 @@ +/***************************************************************************** + * media_tree.h + ***************************************************************************** + * Copyright (C) 2018 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. + *****************************************************************************/ + +#ifndef MEDIA_TREE_H +#define MEDIA_TREE_H + +#include <vlc_media_source.h> + +vlc_media_tree_t * +vlc_media_tree_New(void); + +void +vlc_media_tree_Hold(vlc_media_tree_t *tree); + +void +vlc_media_tree_Release(vlc_media_tree_t *tree); + +input_item_node_t * +vlc_media_tree_Add(vlc_media_tree_t *tree, input_item_node_t *parent, + input_item_t *media); + +bool +vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *media); + +#endif diff --git a/src/media_source/test.c b/src/media_source/test.c new file mode 100644 index 0000000000000000000000000000000000000000..24ee580b3625dba1419f4a30104f510da4b6b0ae --- /dev/null +++ b/src/media_source/test.c @@ -0,0 +1,316 @@ +/***************************************************************************** + * media_source/test.c + ***************************************************************************** + * Copyright (C) 2018 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 <assert.h> +#include <vlc_common.h> +#include <vlc_vector.h> +#include "media_source.h" +#include "media_tree.h" + +static void +test_media_tree(void) +{ + vlc_media_tree_t *tree = vlc_media_tree_New(); + vlc_media_tree_Lock(tree); + + assert(!tree->root.p_item); + assert(tree->root.i_children == 0); + + input_item_t *media = input_item_New("vlc://item", "aaa"); + assert(media); + input_item_node_t *node = vlc_media_tree_Add(tree, &tree->root, media); + assert(node); + input_item_Release(media); /* there is still 1 ref after that */ + + assert(tree->root.i_children == 1); + assert(tree->root.pp_children[0] == node); + assert(node->p_item == media); + assert(node->i_children == 0); + + input_item_t *media2 = input_item_New("vlc://child", "bbb"); + assert(media2); + input_item_node_t *node2 = vlc_media_tree_Add(tree, node, media2); + assert(node2); + input_item_Release(media2); + + assert(node->i_children == 1); + assert(node->pp_children[0] == node2); + assert(node2->p_item == media2); + assert(node2->i_children == 0); + + input_item_t *media3 = input_item_New("vlc://child2", "ccc"); + assert(media3); + input_item_node_t *node3 = vlc_media_tree_Add(tree, node, media3); + assert(node3); + input_item_Release(media3); + + assert(node->i_children == 2); + assert(node->pp_children[0] == node2); + assert(node->pp_children[1] == node3); + assert(node3->p_item == media3); + assert(node3->i_children == 0); + + bool removed = vlc_media_tree_Remove(tree, media2); + assert(removed); + assert(node->i_children == 1); + assert(node->pp_children[0] == node3); + + vlc_media_tree_Unlock(tree); + vlc_media_tree_Release(tree); +} + +struct children_reset_report +{ + input_item_node_t *node; +}; + +struct children_added_report +{ + input_item_node_t *node; + input_item_t *first_media; + size_t count; +}; + +struct children_removed_report +{ + input_item_node_t *node; + input_item_t *first_media; + size_t count; +}; + +struct callback_ctx +{ + struct VLC_VECTOR(struct children_reset_report) vec_children_reset; + struct VLC_VECTOR(struct children_added_report) vec_children_added; + struct VLC_VECTOR(struct children_removed_report) vec_children_removed; +}; + +#define CALLBACK_CTX_INITIALIZER \ +{ \ + VLC_VECTOR_INITIALIZER, \ + VLC_VECTOR_INITIALIZER, \ + VLC_VECTOR_INITIALIZER, \ +} + +static inline void +callback_ctx_destroy_reports(struct callback_ctx *ctx) +{ + for (size_t i = 0; i < ctx->vec_children_added.size; ++i) + input_item_Release(ctx->vec_children_added.data[i].first_media); + for (size_t i = 0; i < ctx->vec_children_removed.size; ++i) + input_item_Release(ctx->vec_children_removed.data[i].first_media); +} + +static inline void +callback_ctx_reset(struct callback_ctx *ctx) +{ + callback_ctx_destroy_reports(ctx); + vlc_vector_clear(&ctx->vec_children_reset); + vlc_vector_clear(&ctx->vec_children_added); + vlc_vector_clear(&ctx->vec_children_removed); +} + +static inline void +callback_ctx_destroy(struct callback_ctx *ctx) +{ + callback_ctx_destroy_reports(ctx); + vlc_vector_destroy(&ctx->vec_children_reset); + vlc_vector_destroy(&ctx->vec_children_added); + vlc_vector_destroy(&ctx->vec_children_removed); +} + +static void +on_children_reset(vlc_media_tree_t *tree, input_item_node_t *node, + void *userdata) +{ + VLC_UNUSED(tree); + + struct callback_ctx *ctx = userdata; + + struct children_reset_report report; + report.node = node; + bool ok = vlc_vector_push(&ctx->vec_children_reset, report); + assert(ok); +} + +static void +on_children_added(vlc_media_tree_t *tree, input_item_node_t *node, + input_item_node_t *const children[], size_t count, + void *userdata) +{ + VLC_UNUSED(tree); + + struct callback_ctx *ctx = userdata; + + struct children_added_report report; + report.node = node; + report.first_media = input_item_Hold(children[0]->p_item); + report.count = count; + bool ok = vlc_vector_push(&ctx->vec_children_added, report); + assert(ok); +} + +static void +on_children_removed(vlc_media_tree_t *tree, input_item_node_t *node, + input_item_node_t *const children[], size_t count, + void *userdata) +{ + VLC_UNUSED(tree); + + struct callback_ctx *ctx = userdata; + + struct children_removed_report report; + report.node = node; + report.first_media = input_item_Hold(children[0]->p_item); + report.count = count; + bool ok = vlc_vector_push(&ctx->vec_children_removed, report); + assert(ok); +} + +static void test_media_tree_callbacks(void) +{ + struct vlc_media_tree_callbacks cbs = { + .on_children_reset = on_children_reset, + .on_children_added = on_children_added, + .on_children_removed = on_children_removed, + }; + + vlc_media_tree_t *tree = vlc_media_tree_New(); + struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER; + vlc_media_tree_listener_id *listener = + vlc_media_tree_AddListener(tree, &cbs, &ctx, false); + assert(listener); + + vlc_media_tree_Lock(tree); + + input_item_t *media = input_item_New("vlc://item", "aaa"); + assert(media); + input_item_node_t *node = vlc_media_tree_Add(tree, &tree->root, media); + assert(node); + input_item_Release(media); /* there is still 1 ref after that */ + + assert(ctx.vec_children_reset.size == 0); + assert(ctx.vec_children_added.size == 1); + assert(ctx.vec_children_added.data[0].node == &tree->root); + assert(ctx.vec_children_added.data[0].first_media == media); + assert(ctx.vec_children_removed.size == 0); + + callback_ctx_reset(&ctx); + + input_item_t *media2 = input_item_New("vlc://child", "bbb"); + assert(media2); + input_item_node_t *node2 = vlc_media_tree_Add(tree, node, media2); + assert(node2); + input_item_Release(media2); + + assert(ctx.vec_children_reset.size == 0); + assert(ctx.vec_children_added.size == 1); + assert(ctx.vec_children_added.data[0].node == node); + assert(ctx.vec_children_added.data[0].first_media == media2); + assert(ctx.vec_children_removed.size == 0); + + callback_ctx_reset(&ctx); + + input_item_t *media3 = input_item_New("vlc://child2", "ccc"); + assert(media3); + input_item_node_t *node3 = vlc_media_tree_Add(tree, node, media3); + assert(node3); + input_item_Release(media3); + + assert(ctx.vec_children_reset.size == 0); + assert(ctx.vec_children_added.size == 1); + assert(ctx.vec_children_added.data[0].node == node); + assert(ctx.vec_children_added.data[0].first_media == media3); + assert(ctx.vec_children_removed.size == 0); + + callback_ctx_reset(&ctx); + + bool removed = vlc_media_tree_Remove(tree, media2); + assert(removed); + assert(node->i_children == 1); + assert(node->pp_children[0] == node3); + + assert(ctx.vec_children_reset.size == 0); + assert(ctx.vec_children_added.size == 0); + assert(ctx.vec_children_removed.size == 1); + assert(ctx.vec_children_removed.data[0].node == node); + assert(ctx.vec_children_removed.data[0].first_media == media2); + + vlc_media_tree_Unlock(tree); + + vlc_media_tree_RemoveListener(tree, listener); + callback_ctx_destroy(&ctx); + + vlc_media_tree_Release(tree); +} + +static void +test_media_tree_callbacks_on_add_listener(void) +{ + struct vlc_media_tree_callbacks cbs = { + .on_children_reset = on_children_reset, + }; + + + vlc_media_tree_t *tree = vlc_media_tree_New(); + + vlc_media_tree_Lock(tree); + + input_item_t *media = input_item_New("vlc://item", "aaa"); + assert(media); + input_item_node_t *node = vlc_media_tree_Add(tree, &tree->root, media); + assert(node); + input_item_Release(media); + + input_item_t *media2 = input_item_New("vlc://child", "bbb"); + assert(media2); + input_item_node_t *node2 = vlc_media_tree_Add(tree, node, media2); + assert(node2); + input_item_Release(media2); + + vlc_media_tree_Unlock(tree); + + struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER; + vlc_media_tree_listener_id *listener = + vlc_media_tree_AddListener(tree, &cbs, &ctx, true); + assert(listener); + + assert(ctx.vec_children_reset.size == 1); + assert(ctx.vec_children_reset.data[0].node == &tree->root); + assert(ctx.vec_children_reset.data[0].node->i_children == 1); + assert(ctx.vec_children_reset.data[0].node->pp_children[0] == node); + + vlc_media_tree_RemoveListener(tree, listener); + callback_ctx_destroy(&ctx); + + vlc_media_tree_Release(tree); +} + +int main(void) +{ + test_media_tree(); + test_media_tree_callbacks(); + test_media_tree_callbacks_on_add_listener(); + return 0; +}