[vlc-devel] [PATCH v5 1/3] Introduce media source and media tree API
Romain Vimont
rom1v at videolabs.io
Tue Aug 21 16:29:28 CEST 2018
Add an API to manage "services discovery" out of the playlist.
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, independant of the playlist, used
to store the input items detected by the services discovery. Clients may
listen to the tree to be notified of changes.
---
include/vlc_media_source.h | 87 ++++++++++
include/vlc_media_tree.h | 127 +++++++++++++++
include/vlc_services_discovery.h | 2 +
src/Makefile.am | 6 +
src/input/services_discovery.c | 1 +
src/libvlc.c | 9 ++
src/libvlc.h | 2 +
src/libvlccore.sym | 10 ++
src/media_source/media_source.c | 232 +++++++++++++++++++++++++++
src/media_source/media_source.h | 31 ++++
src/media_tree/media_tree.c | 267 +++++++++++++++++++++++++++++++
src/media_tree/media_tree.h | 34 ++++
12 files changed, 808 insertions(+)
create mode 100644 include/vlc_media_source.h
create mode 100644 include/vlc_media_tree.h
create mode 100644 src/media_source/media_source.c
create mode 100644 src/media_source/media_source.h
create mode 100644 src/media_tree/media_tree.c
create mode 100644 src/media_tree/media_tree.h
diff --git a/include/vlc_media_source.h b/include/vlc_media_source.h
new file mode 100644
index 0000000000..c75838f329
--- /dev/null
+++ b/include/vlc_media_source.h
@@ -0,0 +1,87 @@
+/*****************************************************************************
+ * vlc_media_source.h : Media source
+ *****************************************************************************
+ * 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>
+
+typedef struct vlc_media_tree_t vlc_media_tree_t;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \defgroup media_source Media source
+ * \ingroup input
+ * @{
+ */
+
+/**
+ * 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_media_tree.h b/include/vlc_media_tree.h
new file mode 100644
index 0000000000..779b095fd3
--- /dev/null
+++ b/include/vlc_media_tree.h
@@ -0,0 +1,127 @@
+/*****************************************************************************
+ * vlc_media_tree.h : Media tree
+ *****************************************************************************
+ * 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_TREE_H
+#define VLC_MEDIA_TREE_H
+
+#include <vlc_common.h>
+#include <vlc_input_item.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \defgroup media_tree Media tree
+ * \ingroup input
+ * @{
+ */
+
+/**
+ * Media tree.
+ *
+ * Nodes must be traversed with locked held (vlc_media_tree_Lock()).
+ */
+typedef struct vlc_media_tree_t {
+ input_item_node_t root;
+} vlc_media_tree_t;
+
+/**
+ * Callbacks to receive media tree events.
+ */
+typedef struct vlc_media_tree_callbacks_t
+{
+ /**
+ * Called when this listener is added by vlc_media_tree_AddListener(), with
+ * lock held.
+ *
+ * This allows to get the tree initial state.
+ *
+ * Use vlc_media_tree_listener_added_default implementation to call
+ * node_added() for every node.
+ */
+ void (*listener_added)(vlc_media_tree_t *, void *userdata);
+
+ /**
+ * Called after a new node has been added to the media tree, with lock held.
+ */
+ void (*node_added)(vlc_media_tree_t *, const input_item_node_t *parent,
+ const input_item_node_t *, void *userdata);
+
+ /**
+ * Called after a node has been removed from the media tree, with lock held.
+ */
+ void (*node_removed)(vlc_media_tree_t *, const input_item_node_t *parent,
+ const input_item_node_t *, void *userdata);
+} vlc_media_tree_callbacks_t;
+
+/**
+ * Listener for media tree events.
+ */
+typedef struct vlc_media_tree_listener_id vlc_media_tree_listener_id;
+
+/**
+ * Default implementation for listener_added(), which calls node_added() for
+ * every existing node.
+ **/
+VLC_API void vlc_media_tree_listener_added_default(vlc_media_tree_t *, void *userdata);
+
+/**
+ * Add a listener. The lock must NOT be held.
+ */
+VLC_API vlc_media_tree_listener_id *vlc_media_tree_AddListener(vlc_media_tree_t *,
+ const vlc_media_tree_callbacks_t *,
+ void *userdata);
+
+/**
+ * Remove a listener. The lock must NOT be held.
+ */
+VLC_API void vlc_media_tree_RemoveListener(vlc_media_tree_t *, vlc_media_tree_listener_id *);
+
+/**
+ * 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 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 *, const input_item_t *,
+ input_item_node_t **result, input_item_node_t **result_parent);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/vlc_services_discovery.h b/include/vlc_services_discovery.h
index 1f5305b788..e117a1c0a5 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 1802dedf58..2a8e975cf6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -63,10 +63,12 @@ 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 \
../include/vlc_media_library.h \
+ ../include/vlc_media_tree.h \
../include/vlc_memstream.h \
../include/vlc_mime.h \
../include/vlc_modules.h \
@@ -204,6 +206,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_tree/media_tree.c \
+ media_tree/media_tree.h \
modules/modules.h \
modules/modules.c \
modules/bank.c \
diff --git a/src/input/services_discovery.c b/src/input/services_discovery.c
index 12a029ef6e..7d43e4ee7a 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 f12f980936..d8998bf64d 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>
@@ -94,6 +95,7 @@ libvlc_int_t * libvlc_InternalCreate( void )
priv = libvlc_priv (p_libvlc);
priv->playlist = NULL;
priv->p_vlm = NULL;
+ priv->media_source_provider = NULL;
vlc_ExitInit( &priv->exit );
@@ -238,6 +240,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_Create( 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 );
@@ -374,6 +380,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_Destroy( priv->media_source_provider );
+
libvlc_InternalDialogClean( p_libvlc );
libvlc_InternalKeystoreClean( p_libvlc );
diff --git a/src/libvlc.h b/src/libvlc.h
index 6c0381122b..61a72b8741 100644
--- a/src/libvlc.h
+++ b/src/libvlc.h
@@ -174,6 +174,7 @@ void vlc_objres_remove(vlc_object_t *obj, void *data,
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_media_source_provider_t vlc_media_source_provider_t;
typedef struct libvlc_priv_t
{
@@ -186,6 +187,7 @@ typedef struct libvlc_priv_t
vlc_keystore *p_memory_keystore; ///< memory keystore
struct playlist_t *playlist; ///< Playlist for interfaces
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
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 19fb6d492e..cb0958565e 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -779,3 +779,13 @@ vlc_rd_get_names
vlc_rd_new
vlc_rd_release
vlc_rd_probe_add
+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_listener_added_default
diff --git a/src/media_source/media_source.c b/src/media_source/media_source.c
new file mode 100644
index 0000000000..d87f369eab
--- /dev/null
+++ b/src/media_source/media_source.c
@@ -0,0 +1,232 @@
+/*****************************************************************************
+ * media_source.c : Media source
+ *****************************************************************************
+ * 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_media_tree.h>
+#include <vlc_playlist.h>
+#include <vlc_services_discovery.h>
+#include "libvlc.h"
+#include "playlist/playlist_internal.h"
+#include "media_tree/media_tree.h"
+
+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 *input,
+ 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", input->psz_name ? input->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, input) != 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 *input)
+{
+ vlc_media_source_t *ms = sd->owner.sys;
+ vlc_media_tree_t *tree = ms->tree;
+
+ msg_Dbg(sd, "removing: %s", input->psz_name ? input->psz_name : "(null)");
+
+ vlc_media_tree_Lock(tree);
+ bool removed = vlc_media_tree_Remove(tree, input);
+ 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 media_source_provider_sd_cbs = {
+ .item_added = services_discovery_item_added,
+ .item_removed = services_discovery_item_removed,
+};
+
+static vlc_media_source_t *MediaSourceCreate(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_Create();
+ if (unlikely(!ms->tree))
+ {
+ free(priv);
+ return NULL;
+ }
+
+ strcpy(priv->name, name);
+
+ struct services_discovery_owner_t owner = {
+ .cbs = &media_source_provider_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 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 MediaSourceDestroy(vlc_media_source_t *ms)
+{
+ media_source_private_t *priv = ms_priv(ms);
+ 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))
+ MediaSourceDestroy(ms);
+}
+
+static vlc_media_source_t *FindByName(vlc_media_source_provider_t *provider, const char *name)
+{
+ vlc_assert_locked(&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;
+}
+
+#undef vlc_media_source_provider_Create
+vlc_media_source_provider_t *vlc_media_source_provider_Create(vlc_object_t *parent)
+{
+ vlc_media_source_provider_t *provider = vlc_custom_create(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_Destroy(vlc_media_source_provider_t *provider)
+{
+ vlc_mutex_destroy(&provider->lock);
+ vlc_object_release(provider);
+}
+
+static vlc_media_source_t *AddServiceDiscovery(vlc_media_source_provider_t *provider, const char *name)
+{
+ vlc_assert_locked(&provider->lock);
+
+ vlc_media_source_t *ms = MediaSourceCreate(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 = FindByName(provider, name);
+ if (!ms)
+ ms = AddServiceDiscovery(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 0000000000..242c7bace3
--- /dev/null
+++ b/src/media_source/media_source.h
@@ -0,0 +1,31 @@
+/*****************************************************************************
+ * media_source.h : Media source
+ *****************************************************************************
+ * 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_Create(vlc_object_t *parent);
+#define vlc_media_source_provider_Create(obj) vlc_media_source_provider_Create(VLC_OBJECT(obj))
+
+void vlc_media_source_provider_Destroy(vlc_media_source_provider_t *);
+
+#endif
diff --git a/src/media_tree/media_tree.c b/src/media_tree/media_tree.c
new file mode 100644
index 0000000000..5a58771507
--- /dev/null
+++ b/src/media_tree/media_tree.c
@@ -0,0 +1,267 @@
+/*****************************************************************************
+ * media_tree.c : Media tree
+ *****************************************************************************
+ * 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 vlc_media_tree_callbacks_t *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_Create(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 AssertLocked(vlc_media_tree_t *tree)
+{
+ media_tree_private_t *priv = mt_priv(tree);
+ vlc_assert_locked(&priv->lock);
+}
+
+static void NotifyNodeAdded(vlc_media_tree_t *tree, const input_item_node_t *parent,
+ const input_item_node_t *node)
+{
+ AssertLocked(tree);
+ media_tree_private_t *priv = mt_priv(tree);
+
+ vlc_media_tree_listener_id *listener;
+ vlc_list_foreach(listener, &priv->listeners, node)
+ if (listener->cbs->node_added)
+ listener->cbs->node_added(tree, parent, node, listener->userdata);
+}
+
+static void NotifyNodeRemoved(vlc_media_tree_t *tree, const input_item_node_t *parent,
+ const input_item_node_t *node)
+{
+ AssertLocked(tree);
+ media_tree_private_t *priv = mt_priv(tree);
+
+ vlc_media_tree_listener_id *listener;
+ vlc_list_foreach(listener, &priv->listeners, node)
+ if (listener->cbs->node_removed)
+ listener->cbs->node_removed(tree, parent, node, listener->userdata);
+}
+
+static bool FindNodeByInput(input_item_node_t *parent, const input_item_t *input,
+ 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 == input)
+ {
+ *result = child;
+ if (result_parent)
+ *result_parent = parent;
+ return true;
+ }
+
+ if (FindNodeByInput(child, input, result, result_parent))
+ return true;
+ }
+
+ return false;
+}
+
+static void 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 Destroy(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 */
+ 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))
+ Destroy(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 *AddChild(input_item_node_t *parent, input_item_t *input)
+{
+ input_item_node_t *node = input_item_node_Create(input);
+ if (unlikely(!node))
+ return NULL;
+
+ input_item_node_AppendNode(parent, node);
+
+ return node;
+}
+
+static void NotifyChildren(vlc_media_tree_t *tree, const input_item_node_t *node,
+ vlc_media_tree_listener_id *listener)
+{
+ AssertLocked(tree);
+ assert(listener->cbs->node_added);
+ for (int i = 0; i < node->i_children; ++i)
+ {
+ input_item_node_t *child = node->pp_children[i];
+ listener->cbs->node_added(tree, node, child, listener->userdata);
+ NotifyChildren(tree, child, listener);
+ }
+}
+
+void vlc_media_tree_listener_added_default(vlc_media_tree_t *tree, void *userdata)
+{
+ VLC_UNUSED(userdata);
+ AssertLocked(tree);
+ media_tree_private_t *priv = mt_priv(tree);
+ vlc_media_tree_listener_id *listener;
+ vlc_list_foreach(listener, &priv->listeners, node)
+ /* notify "node added" for every node */
+ if (listener->cbs->node_added)
+ NotifyChildren(tree, &tree->root, listener);
+}
+
+vlc_media_tree_listener_id *vlc_media_tree_AddListener(vlc_media_tree_t *tree,
+ const vlc_media_tree_callbacks_t *cbs,
+ void *userdata)
+{
+ 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 (listener->cbs->listener_added)
+ listener->cbs->listener_added(tree, listener->userdata);
+ 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 *input)
+{
+ AssertLocked(tree);
+
+ input_item_node_t *node = AddChild(parent, input);
+ if (unlikely(!node))
+ return NULL;
+
+ NotifyNodeAdded(tree, parent, node);
+
+ return node;
+}
+
+bool vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *input,
+ input_item_node_t **result, input_item_node_t **result_parent)
+{
+ AssertLocked(tree);
+
+ /* quick & dirty depth-first O(n) implementation, with n the number of nodes in the tree */
+ return FindNodeByInput(&tree->root, input, result, result_parent);
+}
+
+bool vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *input)
+{
+ AssertLocked(tree);
+
+ input_item_node_t *node;
+ input_item_node_t *parent;
+ if (!FindNodeByInput(&tree->root, input, &node, &parent))
+ return false;
+
+ input_item_node_RemoveNode(parent, node);
+ NotifyNodeRemoved(tree, parent, node);
+ input_item_node_Delete(node);
+ return true;
+}
diff --git a/src/media_tree/media_tree.h b/src/media_tree/media_tree.h
new file mode 100644
index 0000000000..61140c9d18
--- /dev/null
+++ b/src/media_tree/media_tree.h
@@ -0,0 +1,34 @@
+/*****************************************************************************
+ * media_tree.h : Media tree
+ *****************************************************************************
+ * 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_tree.h>
+
+vlc_media_tree_t *vlc_media_tree_Create(void);
+
+void vlc_media_tree_Hold(vlc_media_tree_t *);
+void vlc_media_tree_Release(vlc_media_tree_t *);
+
+input_item_node_t *vlc_media_tree_Add(vlc_media_tree_t *, input_item_node_t *parent, input_item_t *);
+bool vlc_media_tree_Remove(vlc_media_tree_t *, input_item_t *input);
+
+#endif
--
2.18.0
More information about the vlc-devel
mailing list