[vlc-devel] [PATCH v4 1/3] Introduce media source and media tree API

Romain Vimont rom1v at videolabs.io
Mon Jul 9 12:13:52 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       |  93 +++++++++++
 include/vlc_media_tree.h         | 126 +++++++++++++++
 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  | 248 +++++++++++++++++++++++++++++
 src/media_source/media_source.h  |  29 ++++
 src/media_tree/media_tree.c      | 261 +++++++++++++++++++++++++++++++
 src/media_tree/media_tree.h      |  34 ++++
 12 files changed, 821 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 00000000000..963f80c5921
--- /dev/null
+++ b/include/vlc_media_source.h
@@ -0,0 +1,93 @@
+/*****************************************************************************
+ * 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, used to get media sources.
+ *
+ * It's typically used as an opaque pointer.
+ */
+typedef struct vlc_media_source_provider_t
+{
+    struct vlc_common_members obj;
+    /* all other fields are private */
+} 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 00000000000..b9d2c6f194b
--- /dev/null
+++ b/include/vlc_media_tree.h
@@ -0,0 +1,126 @@
+/*****************************************************************************
+ * 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_arrays.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 on vlc_media_tree_AddListener(), with lock held.
+     *
+     * 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_t
+{
+    const vlc_media_tree_callbacks_t *cbs;
+    void *userdata;
+} vlc_media_tree_listener_t;
+
+/**
+ * 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 listener. The lock must NOT be held.
+ */
+VLC_API void vlc_media_tree_AddListener(vlc_media_tree_t *, vlc_media_tree_listener_t *);
+
+/**
+ * Remove listener. The lock must NOT be held.
+ */
+VLC_API void vlc_media_tree_RemoveListener(vlc_media_tree_t *, vlc_media_tree_listener_t *);
+
+/**
+ * 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]
+ *
+ * \return true if found, false otherwise.
+ */
+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 1f5305b788e..e117a1c0a52 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 f9106a17c54..05866910842 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -62,10 +62,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 \
@@ -203,6 +205,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 12a029ef6e6..7d43e4ee7ab 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 169bc37a7de..5e172b9fc9a 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>
@@ -93,6 +94,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 );
 
@@ -230,6 +232,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 );
@@ -363,6 +369,9 @@ void libvlc_InternalCleanup( libvlc_int_t *p_libvlc )
     msg_Dbg( p_libvlc, "removing all interfaces" );
     intf_DestroyAll( p_libvlc );
 
+    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 6d807cd8a8b..745fc33c502 100644
--- a/src/libvlc.h
+++ b/src/libvlc.h
@@ -172,6 +172,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
 {
@@ -184,6 +185,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
 
     /* Exit callback */
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 4c0145d98b8..fd2d4bbe69c 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -755,3 +755,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 00000000000..e91bafd89e0
--- /dev/null
+++ b/src/media_source/media_source.c
@@ -0,0 +1,248 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#include "media_source.h"
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#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 siblings;
+    char name[];
+} media_source_private_t;
+
+#define ms_priv(ms) container_of(ms, media_source_private_t, public_data)
+
+typedef struct
+{
+    vlc_media_source_provider_t public_data;
+
+    vlc_mutex_t lock;
+    struct vlc_list media_sources;
+} media_source_provider_private_t;
+
+#define msp_priv(msp) container_of(msp, media_source_provider_private_t, public_data)
+
+/* 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;
+
+    vlc_media_tree_Add(tree, parent_node, input);
+
+    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 inline void AssertLocked(vlc_media_source_provider_t *msp)
+{
+    media_source_provider_private_t *priv = msp_priv(msp);
+    vlc_assert_locked(&priv->lock);
+}
+
+static vlc_media_source_t *MediaSourceCreate(vlc_media_source_provider_t *msp, 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(ms);
+        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(msp, name, &owner);
+    if (unlikely(!priv->sd))
+    {
+        vlc_media_tree_Release(ms->tree);
+        free(ms);
+        return NULL;
+    }
+
+    /* sd->description is set during vlc_sd_Create() */
+    ms->description = priv->sd->description;
+
+    priv->owner = msp;
+
+    return ms;
+}
+
+static void Remove(vlc_media_source_provider_t *, vlc_media_source_t *);
+
+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(media_source_provider_private_t *priv, const char *name)
+{
+    vlc_assert_locked(&priv->lock);
+    media_source_private_t *entry;
+    vlc_list_foreach(entry, &priv->media_sources, siblings)
+        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;
+}
+
+vlc_media_source_provider_t *vlc_media_source_provider_Create(vlc_object_t *parent)
+{
+    media_source_provider_private_t *priv = vlc_custom_create(parent, sizeof(*priv), "media-source-provider");
+    if (unlikely(!priv))
+        return NULL;
+
+    vlc_mutex_init(&priv->lock);
+    vlc_list_init(&priv->media_sources);
+    return &priv->public_data;
+}
+
+void vlc_media_source_provider_Destroy(vlc_media_source_provider_t *msp)
+{
+    media_source_provider_private_t *priv = msp_priv(msp);
+
+    vlc_mutex_destroy(&priv->lock);
+    vlc_object_release(msp);
+}
+
+static vlc_media_source_t *AddServiceDiscovery(vlc_media_source_provider_t *msp, const char *name)
+{
+    AssertLocked(msp);
+
+    vlc_media_source_t *ms = MediaSourceCreate(msp, name);
+    if (unlikely(!ms))
+        return NULL;
+
+    media_source_provider_private_t *priv = msp_priv(msp);
+
+    vlc_list_append(&ms_priv(ms)->siblings, &priv->media_sources);
+    return ms;
+}
+
+vlc_media_source_t *vlc_media_source_provider_GetMediaSource(vlc_media_source_provider_t *msp, const char *name)
+{
+    media_source_provider_private_t *priv = msp_priv(msp);
+
+    vlc_mutex_lock(&priv->lock);
+    vlc_media_source_t *ms = FindByName(priv, name);
+    if (!ms)
+        ms = AddServiceDiscovery(msp, name);
+    vlc_mutex_unlock(&priv->lock);
+
+    return ms;
+}
+
+static void Remove(vlc_media_source_provider_t *msp, vlc_media_source_t *ms)
+{
+    media_source_provider_private_t *priv = msp_priv(msp);
+
+    vlc_mutex_lock(&priv->lock);
+    vlc_list_remove(&ms_priv(ms)->siblings);
+    vlc_mutex_unlock(&priv->lock);
+}
diff --git a/src/media_source/media_source.h b/src/media_source/media_source.h
new file mode 100644
index 00000000000..4a4672a7be0
--- /dev/null
+++ b/src/media_source/media_source.h
@@ -0,0 +1,29 @@
+/*****************************************************************************
+ * 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);
+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 00000000000..dec2418efa6
--- /dev/null
+++ b/src/media_tree/media_tree.c
@@ -0,0 +1,261 @@
+/*****************************************************************************
+ * 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.
+ *****************************************************************************/
+
+#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"
+
+TYPEDEF_ARRAY(vlc_media_tree_listener_t *, listener_array_t)
+
+typedef struct
+{
+    vlc_media_tree_t public_data;
+
+    listener_array_t listeners;
+    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);
+    ARRAY_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 NotifyListenerAdded(vlc_media_tree_t *tree)
+{
+    AssertLocked(tree);
+    media_tree_private_t *priv = mt_priv(tree);
+
+    FOREACH_ARRAY(vlc_media_tree_listener_t *listener, priv->listeners)
+        if (listener->cbs->listener_added)
+            listener->cbs->listener_added(tree, listener->userdata);
+    FOREACH_END();
+}
+
+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);
+
+    FOREACH_ARRAY(vlc_media_tree_listener_t *listener, priv->listeners)
+        if (listener->cbs->node_added)
+            listener->cbs->node_added(tree, parent, node, listener->userdata);
+    FOREACH_END();
+}
+
+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);
+
+    FOREACH_ARRAY(vlc_media_tree_listener_t *listener, priv->listeners)
+        if (listener->cbs->node_removed)
+            listener->cbs->node_removed(tree, parent, node, listener->userdata);
+    FOREACH_END();
+}
+
+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 *p_child = parent->pp_children[i];
+        if (p_child->p_item == input)
+        {
+            *result = p_child;
+            if (result_parent)
+                *result_parent = parent;
+            return true;
+        }
+
+        if (FindNodeByInput(p_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);
+    ARRAY_RESET(priv->listeners);
+    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,
+                           const vlc_media_tree_listener_t *listener)
+{
+    AssertLocked(tree);
+    for (int i = 0; i < node->i_children; ++i)
+    {
+        input_item_node_t *p_child = node->pp_children[i];
+        listener->cbs->node_added(tree, node, p_child, listener->userdata);
+        NotifyChildren(tree, p_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);
+    FOREACH_ARRAY(vlc_media_tree_listener_t *listener, priv->listeners)
+        /* notify "node added" for every node */
+        if (listener->cbs->node_added)
+            NotifyChildren(tree, &tree->root, listener);
+    FOREACH_END();
+}
+
+void vlc_media_tree_AddListener(vlc_media_tree_t *tree, vlc_media_tree_listener_t *listener)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_media_tree_Lock(tree);
+    ARRAY_APPEND(priv->listeners, listener);
+    NotifyListenerAdded(tree);
+    vlc_media_tree_Unlock(tree);
+}
+
+void vlc_media_tree_RemoveListener(vlc_media_tree_t *tree, vlc_media_tree_listener_t *listener)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_media_tree_Lock(tree);
+    for (int i = 0; i < priv->listeners.i_size; ++i)
+    {
+        if (ARRAY_VAL(priv->listeners, i) == listener)
+        {
+            ARRAY_REMOVE(priv->listeners, i);
+            break;
+        }
+    }
+    vlc_media_tree_Unlock(tree);
+}
+
+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 00000000000..bda25f58910
--- /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