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

/*****************************************************************************
* 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
}