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

Romain Vimont rom1v at videolabs.io
Thu Jun 28 10:55:30 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
connect callbacks to the tree to be notified of changes.
---
 include/vlc_media_source.h       |  85 ++++++++++
 include/vlc_media_tree.h         | 129 +++++++++++++++
 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               |  12 ++
 src/media_source/media_source.c  | 257 +++++++++++++++++++++++++++++
 src/media_source/media_source.h  |  29 ++++
 src/media_tree/media_tree.c      | 272 +++++++++++++++++++++++++++++++
 src/media_tree/media_tree.h      |  30 ++++
 12 files changed, 834 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..d554fed41bf
--- /dev/null
+++ b/include/vlc_media_source.h
@@ -0,0 +1,85 @@
+/*****************************************************************************
+ * 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
+
+/**
+ * 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 *p_tree;
+    const char *psz_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 *psz_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..b265060abb5
--- /dev/null
+++ b/include/vlc_media_tree.h
@@ -0,0 +1,129 @@
+/*****************************************************************************
+ * 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
+
+/**
+ * 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;
+
+/**
+ * Opaque type to identify a "listener" connection.
+ */
+typedef struct vlc_media_tree_connection_t vlc_media_tree_connection_t;
+
+/**
+ * Callbacks to listen to media tree events.
+ */
+typedef struct vlc_media_tree_callbacks_t
+{
+    /**
+     * Called on vlc_media_tree_Connect(), with lock held.
+     *
+     * Use vlc_media_tree_connect_default implementation to call pf_node_added()
+     * for every node.
+     */
+    void ( *pf_tree_connected )( vlc_media_tree_t *, void *userdata );
+
+    /**
+     * Called when a new node is added to the media tree, with lock held.
+     */
+    void ( *pf_node_added )( vlc_media_tree_t *, const input_item_node_t *p_parent,
+                             const input_item_node_t *, void *userdata );
+
+    /**
+     * Called when a node is removed from the media tree, with lock held.
+     */
+    void ( *pf_node_removed )( vlc_media_tree_t *, const input_item_node_t *p_parent,
+                               const input_item_node_t *, void *userdata );
+} vlc_media_tree_callbacks_t;
+
+/**
+ * Default implementation for pf_tree_connected(), which calls pf_node_added()
+ * for every existing node.
+ **/
+VLC_API void vlc_media_tree_connected_default( vlc_media_tree_t *, void *userdata );
+
+/**
+ * Increase the media tree reference count.
+ */
+VLC_API void vlc_media_tree_Hold( vlc_media_tree_t * );
+
+/**
+ * Decrease the media tree reference count.
+ *
+ * Destroy the media tree if it reaches 0.
+ */
+VLC_API void vlc_media_tree_Release( vlc_media_tree_t * );
+
+/**
+ * Connect callbacks. The lock must NOT be held.
+ *
+ * \return a connection to be used in vlc_media_tree_Disconnect()
+ */
+VLC_API vlc_media_tree_connection_t *vlc_media_tree_Connect( vlc_media_tree_t *, const vlc_media_tree_callbacks_t *, void *userdata );
+
+/**
+ * Disconnect callbacks. The lock must NOT be held.
+ */
+VLC_API void vlc_media_tree_Disconnect( vlc_media_tree_t *, vlc_media_tree_connection_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 pp_result point to the matching node if the function returns true
+ *                  [OUT]
+ * \param pp_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 **pp_result, input_item_node_t **pp_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 897c557e55d..76fe6dff0a9 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 cbb4cd5a65c..483827f5a39 100644
--- a/src/libvlc.c
+++ b/src/libvlc.c
@@ -43,6 +43,7 @@
 #include "modules/modules.h"
 #include "config/configuration.h"
 #include "playlist/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->p_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->p_media_source_provider = vlc_media_source_provider_Create( VLC_OBJECT( p_libvlc ) );
+    if( !priv->p_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->p_media_source_provider )
+        vlc_media_source_provider_Destroy( priv->p_media_source_provider );
+
     libvlc_InternalDialogClean( p_libvlc );
     libvlc_InternalKeystoreClean( p_libvlc );
 
diff --git a/src/libvlc.h b/src/libvlc.h
index a8f50404b25..b1f29b72786 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 playlist_preparser_t *parser; ///< Input item meta data handler
+    vlc_media_source_provider_t *p_media_source_provider;
     vlc_actions_t *actions; ///< Hotkeys handler
 
     /* Exit callback */
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 0378c96e1bf..0a658ef22b2 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -752,3 +752,15 @@ 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_Hold
+vlc_media_tree_Release
+vlc_media_tree_Connect
+vlc_media_tree_Disconnect
+vlc_media_tree_Lock
+vlc_media_tree_Unlock
+vlc_media_tree_Find
+vlc_media_tree_connected_default
diff --git a/src/media_source/media_source.c b/src/media_source/media_source.c
new file mode 100644
index 00000000000..14a9e16dddb
--- /dev/null
+++ b/src/media_source/media_source.c
@@ -0,0 +1,257 @@
+/*****************************************************************************
+ * 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 <stdatomic.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 *p_sd;
+    atomic_uint refs;
+    vlc_media_source_provider_t *p_owner;
+    struct vlc_list siblings;
+    char psz_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 *p_sd,
+                                           input_item_t *p_parent, input_item_t *p_input,
+                                           const char *psz_cat )
+{
+    assert( !p_parent || !psz_cat );
+    VLC_UNUSED( psz_cat );
+
+    vlc_media_source_t *p_ms = p_sd->owner.sys;
+    vlc_media_tree_t *p_tree = p_ms->p_tree;
+
+    msg_Dbg( p_sd, "adding: %s", p_input->psz_name ? p_input->psz_name : "(null)" );
+
+    vlc_media_tree_Lock( p_tree );
+
+    input_item_node_t *p_parent_node;
+    if( p_parent )
+        vlc_media_tree_Find( p_tree, p_parent, &p_parent_node, NULL );
+    else
+        p_parent_node = &p_tree->root;
+
+    vlc_media_tree_Add( p_tree, p_parent_node, p_input );
+
+    vlc_media_tree_Unlock( p_tree );
+}
+
+static void services_discovery_item_removed( services_discovery_t *p_sd, input_item_t *p_input )
+{
+    vlc_media_source_t *p_ms = p_sd->owner.sys;
+    vlc_media_tree_t *p_tree = p_ms->p_tree;
+
+    msg_Dbg( p_sd, "removing: %s", p_input->psz_name ? p_input->psz_name : "(null)" );
+
+    vlc_media_tree_Lock( p_tree );
+    bool removed = vlc_media_tree_Remove( p_tree, p_input );
+    vlc_media_tree_Unlock( p_tree );
+
+    if( unlikely( !removed) )
+    {
+        msg_Err( p_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 *p_msp )
+{
+    media_source_provider_private_t *p_priv = msp_priv( p_msp );
+    vlc_assert_locked( &p_priv->lock );
+}
+
+static vlc_media_source_t *MediaSourceCreate( vlc_media_source_provider_t *p_msp, const char *psz_name )
+{
+    media_source_private_t *p_priv = malloc( sizeof( *p_priv ) + strlen( psz_name ) + 1 );
+    if( unlikely( !p_priv ) )
+        return NULL;
+
+    atomic_init( &p_priv->refs, 1 );
+
+    vlc_media_source_t *p_ms = &p_priv->public_data;
+
+    /* vlc_sd_Create() may call services_discovery_item_added(), which will read its
+     * p_tree, so it must be initialized first */
+    p_ms->p_tree = vlc_media_tree_Create();
+    if( unlikely( !p_ms->p_tree ) )
+    {
+        free( p_ms );
+        return NULL;
+    }
+
+    strcpy( p_priv->psz_name, psz_name );
+
+    struct services_discovery_owner_t owner = {
+        .cbs = &media_source_provider_sd_cbs,
+        .sys = p_ms,
+    };
+
+    p_priv->p_sd = vlc_sd_Create( p_msp, psz_name, &owner );
+    if( unlikely( !p_priv->p_sd ) )
+    {
+        vlc_media_tree_Release( p_ms->p_tree );
+        free( p_ms );
+        return NULL;
+    }
+
+    /* p_sd->description is set during vlc_sd_Create() */
+    p_ms->psz_description = p_priv->p_sd->description;
+
+    p_priv->p_owner = p_msp;
+
+    return p_ms;
+}
+
+static void Remove( vlc_media_source_provider_t *, vlc_media_source_t * );
+
+static void MediaSourceDestroy( vlc_media_source_t *p_ms )
+{
+    media_source_private_t *p_priv = ms_priv( p_ms );
+    Remove( p_priv->p_owner, p_ms );
+    vlc_sd_Destroy( p_priv->p_sd );
+    vlc_media_tree_Release( p_ms->p_tree );
+    free( p_priv );
+}
+
+void vlc_media_source_Hold( vlc_media_source_t *p_ms )
+{
+    media_source_private_t *p_priv = ms_priv( p_ms );
+    atomic_fetch_add( &p_priv->refs, 1 );
+}
+
+void vlc_media_source_Release( vlc_media_source_t *p_ms )
+{
+    media_source_private_t *p_priv = ms_priv( p_ms );
+    if( atomic_fetch_sub( &p_priv->refs, 1 ) == 1 )
+        MediaSourceDestroy( p_ms );
+}
+
+static vlc_media_source_t *FindByName( media_source_provider_private_t *p_priv, const char *psz_name )
+{
+    vlc_assert_locked( &p_priv->lock );
+    media_source_private_t *p_entry;
+    vlc_list_foreach( p_entry, &p_priv->media_sources, siblings )
+        if( !strcmp( psz_name, p_entry->psz_name ) )
+            return &p_entry->public_data;
+    return NULL;
+}
+
+vlc_media_source_provider_t *vlc_media_source_provider_Get( libvlc_int_t *libvlc )
+{
+    return libvlc_priv( libvlc )->p_media_source_provider;
+}
+
+vlc_media_source_provider_t *vlc_media_source_provider_Create( vlc_object_t *p_parent )
+{
+    media_source_provider_private_t *p_priv = vlc_custom_create( p_parent, sizeof( *p_priv ), "media-source-provider" );
+    if( unlikely( !p_priv ) )
+        return NULL;
+
+    vlc_mutex_init( &p_priv->lock );
+    vlc_list_init( &p_priv->media_sources );
+    return &p_priv->public_data;
+}
+
+void vlc_media_source_provider_Destroy( vlc_media_source_provider_t *p_msp )
+{
+    media_source_provider_private_t *p_priv = msp_priv( p_msp );
+
+    vlc_mutex_destroy( &p_priv->lock );
+    vlc_object_release( p_msp );
+}
+
+static vlc_media_source_t *AddServiceDiscovery( vlc_media_source_provider_t *p_msp, const char *psz_name )
+{
+    AssertLocked( p_msp );
+
+    vlc_media_source_t *p_ms = MediaSourceCreate( p_msp, psz_name );
+    if( unlikely( !p_ms ) )
+        return NULL;
+
+    media_source_provider_private_t *p_priv = msp_priv( p_msp );
+
+    vlc_list_append( &ms_priv( p_ms )->siblings, &p_priv->media_sources );
+    return p_ms;
+}
+
+vlc_media_source_t *vlc_media_source_provider_GetMediaSource( vlc_media_source_provider_t *p_msp, const char *psz_name )
+{
+    media_source_provider_private_t *p_priv = msp_priv( p_msp );
+
+    vlc_mutex_lock( &p_priv->lock );
+
+    vlc_media_source_t *p_ms = FindByName( p_priv, psz_name );
+    if( !p_ms )
+    {
+        p_ms = AddServiceDiscovery( p_msp, psz_name );
+        if( unlikely( !p_ms ) )
+        {
+            vlc_mutex_unlock( &p_priv->lock );
+            return NULL;
+        }
+    }
+
+    vlc_mutex_unlock( &p_priv->lock );
+
+    return p_ms;
+}
+
+static void Remove( vlc_media_source_provider_t *p_msp, vlc_media_source_t *p_ms )
+{
+    media_source_provider_private_t *p_priv = msp_priv( p_msp );
+
+    vlc_mutex_lock( &p_priv->lock );
+    vlc_list_remove( &ms_priv( p_ms )->siblings );
+    vlc_mutex_unlock( &p_priv->lock );
+}
diff --git a/src/media_source/media_source.h b/src/media_source/media_source.h
new file mode 100644
index 00000000000..821d0a8bd9c
--- /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 *p_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..3cedddb8085
--- /dev/null
+++ b/src/media_tree/media_tree.c
@@ -0,0 +1,272 @@
+/*****************************************************************************
+ * 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 <stdatomic.h>
+#include <vlc_common.h>
+#include <vlc_input_item.h>
+#include <vlc_threads.h>
+#include "libvlc.h"
+
+struct vlc_media_tree_connection_t
+{
+    const vlc_media_tree_callbacks_t *cbs;
+    void *userdata;
+    struct vlc_list siblings;
+};
+
+typedef struct
+{
+    vlc_media_tree_t public_data;
+
+    struct vlc_list connections;
+    vlc_mutex_t lock;
+    atomic_uint refs;
+} 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 *p_priv = malloc( sizeof( *p_priv ) );
+    if( unlikely( !p_priv ) )
+        return NULL;
+
+    vlc_mutex_init( &p_priv->lock );
+    atomic_init( &p_priv->refs, 1 );
+    vlc_list_init( &p_priv->connections );
+
+    vlc_media_tree_t *p_tree = &p_priv->public_data;
+    input_item_node_t *p_root = &p_tree->root;
+    p_root->p_item = NULL;
+    TAB_INIT( p_root->i_children, p_root->pp_children );
+
+    return p_tree;
+}
+
+static inline void AssertLocked( vlc_media_tree_t *p_tree )
+{
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_assert_locked( &p_priv->lock );
+}
+
+static void NotifyTreeConnected( vlc_media_tree_t *p_tree )
+{
+    AssertLocked( p_tree );
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_media_tree_connection_t *p_conn;
+    vlc_list_foreach( p_conn, &p_priv->connections, siblings )
+        if( p_conn->cbs->pf_tree_connected )
+            p_conn->cbs->pf_tree_connected( p_tree, p_conn->userdata );
+}
+
+static void NotifyNodeAdded( vlc_media_tree_t *p_tree, const input_item_node_t *p_parent,
+                             const input_item_node_t *p_node )
+{
+    AssertLocked( p_tree );
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_media_tree_connection_t *p_conn;
+    vlc_list_foreach( p_conn, &p_priv->connections, siblings )
+        if( p_conn->cbs->pf_node_added )
+            p_conn->cbs->pf_node_added( p_tree, p_parent, p_node, p_conn->userdata );
+}
+
+static void NotifyNodeRemoved( vlc_media_tree_t *p_tree, const input_item_node_t *p_parent,
+                               const input_item_node_t *p_node )
+{
+    AssertLocked( p_tree );
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_media_tree_connection_t *p_conn;
+    vlc_list_foreach( p_conn, &p_priv->connections, siblings )
+        if( p_conn->cbs->pf_node_removed )
+            p_conn->cbs->pf_node_removed( p_tree, p_parent, p_node, p_conn->userdata );
+}
+
+static bool FindNodeByInput( input_item_node_t *p_parent, const input_item_t *p_input,
+                             input_item_node_t **pp_result, input_item_node_t **pp_result_parent )
+{
+    for( int i = 0; i < p_parent->i_children; ++i )
+    {
+        input_item_node_t *p_child = p_parent->pp_children[i];
+        if( p_child->p_item == p_input )
+        {
+            *pp_result = p_child;
+            if( pp_result_parent )
+                *pp_result_parent = p_parent;
+            return true;
+        }
+
+        if( FindNodeByInput( p_child, p_input, pp_result, pp_result_parent ) )
+            return true;
+    }
+
+    return false;
+}
+
+static void DestroyRootNode( vlc_media_tree_t *p_tree )
+{
+    input_item_node_t *p_root = &p_tree->root;
+    for( int i = 0; i < p_root->i_children; ++i )
+        input_item_node_Delete( p_root->pp_children[i] );
+
+    free( p_root->pp_children );
+}
+
+static void Destroy( vlc_media_tree_t *p_tree )
+{
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_media_tree_connection_t *p_conn;
+    vlc_list_foreach( p_conn, &p_priv->connections, siblings )
+        free( p_conn );
+    vlc_list_init( &p_priv->connections ); /* reset */
+    DestroyRootNode( p_tree );
+    vlc_mutex_destroy( &p_priv->lock );
+    free( p_tree );
+}
+
+void vlc_media_tree_Hold( vlc_media_tree_t *p_tree )
+{
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    atomic_fetch_add( &p_priv->refs, 1 );
+}
+
+void vlc_media_tree_Release( vlc_media_tree_t *p_tree )
+{
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    if( atomic_fetch_sub( &p_priv->refs, 1 ) == 1 )
+        Destroy( p_tree );
+}
+
+void vlc_media_tree_Lock( vlc_media_tree_t *p_tree )
+{
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_mutex_lock( &p_priv->lock );
+}
+
+void vlc_media_tree_Unlock( vlc_media_tree_t *p_tree )
+{
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_mutex_unlock( &p_priv->lock );
+}
+
+static input_item_node_t *AddChild( input_item_node_t *p_parent, input_item_t *p_input )
+{
+    input_item_node_t *p_node = input_item_node_Create( p_input );
+    if( unlikely( !p_node ) )
+        return NULL;
+
+    input_item_node_AppendNode( p_parent, p_node );
+
+    return p_node;
+}
+
+static void NotifyChildren( vlc_media_tree_t *p_tree, const input_item_node_t *p_node,
+                            const vlc_media_tree_connection_t *p_conn )
+{
+    AssertLocked( p_tree );
+    for( int i = 0; i < p_node->i_children; ++i )
+    {
+        input_item_node_t *p_child = p_node->pp_children[i];
+        p_conn->cbs->pf_node_added( p_tree, p_node, p_child, p_conn->userdata );
+        NotifyChildren( p_tree, p_child, p_conn );
+    }
+}
+
+void vlc_media_tree_connected_default( vlc_media_tree_t *p_tree, void *userdata )
+{
+    VLC_UNUSED( userdata );
+    AssertLocked( p_tree );
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_media_tree_connection_t *p_conn;
+    vlc_list_foreach( p_conn, &p_priv->connections, siblings )
+    {
+        if( !p_conn->cbs->pf_node_added)
+            break; /* nothing to do for this listener */
+        /* notify "node added" for every node */
+        NotifyChildren( p_tree, &p_tree->root, p_conn );
+    }
+}
+
+vlc_media_tree_connection_t *vlc_media_tree_Connect( vlc_media_tree_t *p_tree, const vlc_media_tree_callbacks_t *p_callbacks, void *userdata )
+{
+    vlc_media_tree_connection_t *p_conn = malloc( sizeof( *p_conn ) );
+    if( !p_conn )
+        return NULL;
+    p_conn->cbs = p_callbacks;
+    p_conn->userdata = userdata;
+
+    media_tree_private_t *p_priv = mt_priv( p_tree );
+    vlc_media_tree_Lock( p_tree );
+    vlc_list_append( &p_conn->siblings, &p_priv->connections );
+    NotifyTreeConnected( p_tree );
+    vlc_media_tree_Unlock( p_tree );
+
+    return p_conn;
+}
+
+void vlc_media_tree_Disconnect( vlc_media_tree_t *p_tree, vlc_media_tree_connection_t *p_connection )
+{
+    vlc_media_tree_Lock( p_tree );
+    vlc_list_remove( &p_connection->siblings );
+    vlc_media_tree_Unlock( p_tree );
+
+    free( p_connection );
+}
+
+input_item_node_t *vlc_media_tree_Add( vlc_media_tree_t *p_tree,
+                                       input_item_node_t *p_parent,
+                                       input_item_t *p_input )
+{
+    AssertLocked( p_tree );
+
+    input_item_node_t *p_node = AddChild( p_parent, p_input );
+    if( unlikely( !p_node ) )
+        return NULL;
+
+    NotifyNodeAdded( p_tree, p_parent, p_node );
+
+    return p_node;
+}
+
+bool vlc_media_tree_Find( vlc_media_tree_t *p_tree, const input_item_t *p_input,
+                      input_item_node_t **pp_result, input_item_node_t **pp_result_parent )
+{
+    AssertLocked( p_tree );
+
+    /* quick & dirty depth-first O(n) implementation, with n the number of nodes in the tree */
+    return FindNodeByInput( &p_tree->root, p_input, pp_result, pp_result_parent );
+}
+
+bool vlc_media_tree_Remove( vlc_media_tree_t *p_tree, input_item_t *p_input )
+{
+    AssertLocked( p_tree );
+
+    input_item_node_t *p_node;
+    input_item_node_t *p_parent;
+    if( !FindNodeByInput( &p_tree->root, p_input, &p_node, &p_parent ) )
+        return false;
+
+    input_item_node_RemoveNode( p_parent, p_node );
+    NotifyNodeRemoved( p_tree, p_parent, p_node );
+    input_item_node_Delete( p_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..8f827350453
--- /dev/null
+++ b/src/media_tree/media_tree.h
@@ -0,0 +1,30 @@
+/*****************************************************************************
+ * 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 );
+input_item_node_t *vlc_media_tree_Add( vlc_media_tree_t *, input_item_node_t *p_parent, input_item_t * );
+bool vlc_media_tree_Remove( vlc_media_tree_t *, input_item_t *p_input );
+
+#endif
-- 
2.18.0



More information about the vlc-devel mailing list