You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
10 KiB
349 lines
10 KiB
/*****************************************************************************
|
|
* media_tree.c
|
|
*****************************************************************************
|
|
* Copyright (C) 2018 VLC authors and VideoLAN
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU Lesser General Public License as published by
|
|
* the Free Software Foundation; either version 2.1 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <vlc_media_source.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 <vlc_preparser.h>
|
|
#include "../libvlc.h"
|
|
|
|
struct vlc_media_tree_listener_id
|
|
{
|
|
const struct vlc_media_tree_callbacks *cbs;
|
|
void *userdata;
|
|
struct vlc_list node; /**< node of media_tree_private_t.listeners */
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
vlc_media_tree_t public_data;
|
|
|
|
struct vlc_list listeners; /**< list of vlc_media_tree_listener_id.node */
|
|
vlc_mutex_t lock;
|
|
vlc_atomic_rc_t rc;
|
|
} media_tree_private_t;
|
|
|
|
#define mt_priv(mt) container_of(mt, media_tree_private_t, public_data)
|
|
|
|
vlc_media_tree_t *
|
|
vlc_media_tree_New(void)
|
|
{
|
|
media_tree_private_t *priv = malloc(sizeof(*priv));
|
|
if (unlikely(!priv))
|
|
return NULL;
|
|
|
|
vlc_mutex_init(&priv->lock);
|
|
vlc_atomic_rc_init(&priv->rc);
|
|
vlc_list_init(&priv->listeners);
|
|
|
|
vlc_media_tree_t *tree = &priv->public_data;
|
|
input_item_node_t *root = &tree->root;
|
|
root->p_item = NULL;
|
|
TAB_INIT(root->i_children, root->pp_children);
|
|
|
|
return tree;
|
|
}
|
|
|
|
static inline void
|
|
vlc_media_tree_AssertLocked(vlc_media_tree_t *tree)
|
|
{
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
vlc_mutex_assert(&priv->lock);
|
|
}
|
|
|
|
#define vlc_media_tree_listener_foreach(listener, tree) \
|
|
vlc_list_foreach(listener, &mt_priv(tree)->listeners, node)
|
|
|
|
#define vlc_media_tree_NotifyListener(tree, listener, event, ...) \
|
|
do { \
|
|
if (listener->cbs->event) \
|
|
listener->cbs->event(tree, ##__VA_ARGS__, listener->userdata); \
|
|
} while(0)
|
|
|
|
#define vlc_media_tree_Notify(tree, event, ...) \
|
|
do { \
|
|
vlc_media_tree_AssertLocked(tree); \
|
|
vlc_media_tree_listener_id *listener; \
|
|
vlc_media_tree_listener_foreach(listener, tree) \
|
|
vlc_media_tree_NotifyListener(tree, listener, event, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
static bool
|
|
vlc_media_tree_FindNodeByMedia(input_item_node_t *parent,
|
|
const input_item_t *media,
|
|
input_item_node_t **result,
|
|
input_item_node_t **result_parent)
|
|
{
|
|
for (int i = 0; i < parent->i_children; ++i)
|
|
{
|
|
input_item_node_t *child = parent->pp_children[i];
|
|
if (child->p_item == media)
|
|
{
|
|
*result = child;
|
|
if (result_parent)
|
|
*result_parent = parent;
|
|
return true;
|
|
}
|
|
|
|
if (vlc_media_tree_FindNodeByMedia(child, media, result, result_parent))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void
|
|
vlc_media_tree_ClearChildren(input_item_node_t *root)
|
|
{
|
|
for (int i = 0; i < root->i_children; ++i)
|
|
input_item_node_Delete(root->pp_children[i]);
|
|
|
|
free(root->pp_children);
|
|
root->pp_children = NULL;
|
|
root->i_children = 0;
|
|
}
|
|
|
|
static void
|
|
media_subtree_changed(vlc_preparser_req *req, input_item_node_t *node,
|
|
void *userdata)
|
|
{
|
|
input_item_t *media = vlc_preparser_req_GetItem(req);
|
|
vlc_media_tree_t *tree = userdata;
|
|
|
|
vlc_media_tree_Lock(tree);
|
|
input_item_node_t *subtree_root;
|
|
/* TODO retrieve the node without traversing the tree */
|
|
bool found = vlc_media_tree_FindNodeByMedia(&tree->root, media,
|
|
&subtree_root, NULL);
|
|
if (!found) {
|
|
/* the node probably failed to be allocated */
|
|
vlc_media_tree_Unlock(tree);
|
|
return;
|
|
}
|
|
|
|
vlc_media_tree_ClearChildren(subtree_root);
|
|
/* The nodes can be directly used, as the subtree callback is given
|
|
* ownership of the input item node. */
|
|
subtree_root->pp_children = node->pp_children;
|
|
subtree_root->i_children = node->i_children;
|
|
node->pp_children = NULL;
|
|
node->i_children = 0;
|
|
input_item_node_Delete(node);
|
|
vlc_media_tree_Notify(tree, on_children_reset, subtree_root);
|
|
vlc_media_tree_Unlock(tree);
|
|
}
|
|
|
|
static void
|
|
media_subtree_preparse_ended(vlc_preparser_req *req, int status, void *user_data)
|
|
{
|
|
input_item_t *media = vlc_preparser_req_GetItem(req);
|
|
vlc_media_tree_t *tree = user_data;
|
|
|
|
vlc_media_tree_Lock(tree);
|
|
input_item_node_t *subtree_root;
|
|
/* TODO retrieve the node without traversing the tree */
|
|
bool found = vlc_media_tree_FindNodeByMedia(&tree->root, media,
|
|
&subtree_root, NULL);
|
|
if (!found) {
|
|
/* the node probably failed to be allocated */
|
|
vlc_media_tree_Unlock(tree);
|
|
return;
|
|
}
|
|
vlc_media_tree_Notify(tree, on_preparse_end, subtree_root, status);
|
|
vlc_media_tree_Unlock(tree);
|
|
vlc_preparser_req_Release(req);
|
|
}
|
|
|
|
static inline void
|
|
vlc_media_tree_DestroyRootNode(vlc_media_tree_t *tree)
|
|
{
|
|
vlc_media_tree_ClearChildren(&tree->root);
|
|
}
|
|
|
|
static void
|
|
vlc_media_tree_Delete(vlc_media_tree_t *tree)
|
|
{
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
vlc_media_tree_listener_id *listener;
|
|
vlc_list_foreach(listener, &priv->listeners, node)
|
|
free(listener);
|
|
vlc_list_init(&priv->listeners); /* reset */
|
|
vlc_media_tree_DestroyRootNode(tree);
|
|
free(tree);
|
|
}
|
|
|
|
void
|
|
vlc_media_tree_Hold(vlc_media_tree_t *tree)
|
|
{
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
vlc_atomic_rc_inc(&priv->rc);
|
|
}
|
|
|
|
void
|
|
vlc_media_tree_Release(vlc_media_tree_t *tree)
|
|
{
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
if (vlc_atomic_rc_dec(&priv->rc))
|
|
vlc_media_tree_Delete(tree);
|
|
}
|
|
|
|
void
|
|
vlc_media_tree_Lock(vlc_media_tree_t *tree)
|
|
{
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
vlc_mutex_lock(&priv->lock);
|
|
}
|
|
|
|
void
|
|
vlc_media_tree_Unlock(vlc_media_tree_t *tree)
|
|
{
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
vlc_mutex_unlock(&priv->lock);
|
|
}
|
|
|
|
static input_item_node_t *
|
|
vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media)
|
|
{
|
|
input_item_node_t *node = input_item_node_Create(media);
|
|
if (unlikely(!node))
|
|
return NULL;
|
|
|
|
input_item_node_AppendNode(parent, node);
|
|
|
|
return node;
|
|
}
|
|
|
|
static void
|
|
vlc_media_tree_NotifyCurrentState(vlc_media_tree_t *tree,
|
|
vlc_media_tree_listener_id *listener)
|
|
{
|
|
vlc_media_tree_NotifyListener(tree, listener, on_children_reset,
|
|
&tree->root);
|
|
}
|
|
|
|
vlc_media_tree_listener_id *
|
|
vlc_media_tree_AddListener(vlc_media_tree_t *tree,
|
|
const struct vlc_media_tree_callbacks *cbs,
|
|
void *userdata, bool notify_current_state)
|
|
{
|
|
vlc_media_tree_listener_id *listener = malloc(sizeof(*listener));
|
|
if (unlikely(!listener))
|
|
return NULL;
|
|
listener->cbs = cbs;
|
|
listener->userdata = userdata;
|
|
|
|
media_tree_private_t *priv = mt_priv(tree);
|
|
vlc_media_tree_Lock(tree);
|
|
|
|
vlc_list_append(&listener->node, &priv->listeners);
|
|
|
|
if (notify_current_state)
|
|
vlc_media_tree_NotifyCurrentState(tree, listener);
|
|
|
|
vlc_media_tree_Unlock(tree);
|
|
return listener;
|
|
}
|
|
|
|
void
|
|
vlc_media_tree_RemoveListener(vlc_media_tree_t *tree,
|
|
vlc_media_tree_listener_id *listener)
|
|
{
|
|
vlc_media_tree_Lock(tree);
|
|
vlc_list_remove(&listener->node);
|
|
vlc_media_tree_Unlock(tree);
|
|
|
|
free(listener);
|
|
}
|
|
|
|
input_item_node_t *
|
|
vlc_media_tree_Add(vlc_media_tree_t *tree, input_item_node_t *parent,
|
|
input_item_t *media)
|
|
{
|
|
vlc_media_tree_AssertLocked(tree);
|
|
|
|
input_item_node_t *node = vlc_media_tree_AddChild(parent, media);
|
|
if (unlikely(!node))
|
|
return NULL;
|
|
|
|
vlc_media_tree_Notify(tree, on_children_added, parent, &node, 1);
|
|
|
|
return node;
|
|
}
|
|
|
|
bool
|
|
vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *media,
|
|
input_item_node_t **result,
|
|
input_item_node_t **result_parent)
|
|
{
|
|
vlc_media_tree_AssertLocked(tree);
|
|
|
|
/* quick & dirty depth-first O(n) implementation, with n the number of nodes
|
|
* in the tree */
|
|
return vlc_media_tree_FindNodeByMedia(&tree->root, media, result,
|
|
result_parent);
|
|
}
|
|
|
|
bool
|
|
vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *media)
|
|
{
|
|
vlc_media_tree_AssertLocked(tree);
|
|
|
|
input_item_node_t *node;
|
|
input_item_node_t *parent;
|
|
if (!vlc_media_tree_FindNodeByMedia(&tree->root, media, &node, &parent))
|
|
return false;
|
|
|
|
input_item_node_RemoveNode(parent, node);
|
|
vlc_media_tree_Notify(tree, on_children_removed, parent, &node, 1);
|
|
input_item_node_Delete(node);
|
|
return true;
|
|
}
|
|
|
|
static const struct vlc_preparser_cbs preparser_callbacks = {
|
|
.on_ended = media_subtree_preparse_ended,
|
|
.on_subtree_added = media_subtree_changed,
|
|
};
|
|
|
|
vlc_preparser_req *
|
|
vlc_media_tree_Preparse(vlc_media_tree_t *tree, vlc_preparser_t *parser,
|
|
input_item_t *media)
|
|
{
|
|
#ifdef TEST_MEDIA_SOURCE
|
|
VLC_UNUSED(tree);
|
|
VLC_UNUSED(parser);
|
|
VLC_UNUSED(media);
|
|
VLC_UNUSED(preparser_callbacks);
|
|
return NULL;
|
|
#else
|
|
return vlc_preparser_Push(parser, media, VLC_PREPARSER_TYPE_PARSE |
|
|
VLC_PREPARSER_OPTION_INTERACT |
|
|
VLC_PREPARSER_OPTION_SUBITEMS,
|
|
&preparser_callbacks, tree);
|
|
#endif
|
|
}
|
|
|