/src/vlc/src/media_source/media_tree.c
Line | Count | Source (jump to first uncovered line) |
1 | | /***************************************************************************** |
2 | | * media_tree.c |
3 | | ***************************************************************************** |
4 | | * Copyright (C) 2018 VLC authors and VideoLAN |
5 | | * |
6 | | * This program is free software; you can redistribute it and/or modify it |
7 | | * under the terms of the GNU Lesser General Public License as published by |
8 | | * the Free Software Foundation; either version 2.1 of the License, or |
9 | | * (at your option) any later version. |
10 | | * |
11 | | * This program is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | * GNU Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public License |
17 | | * along with this program; if not, write to the Free Software Foundation, |
18 | | * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. |
19 | | *****************************************************************************/ |
20 | | |
21 | | #ifdef HAVE_CONFIG_H |
22 | | # include "config.h" |
23 | | #endif |
24 | | |
25 | | #include <vlc_media_source.h> |
26 | | |
27 | | #include <assert.h> |
28 | | #include <vlc_common.h> |
29 | | #include <vlc_arrays.h> |
30 | | #include <vlc_atomic.h> |
31 | | #include <vlc_input_item.h> |
32 | | #include <vlc_threads.h> |
33 | | #include "libvlc.h" |
34 | | |
35 | | struct vlc_media_tree_listener_id |
36 | | { |
37 | | const struct vlc_media_tree_callbacks *cbs; |
38 | | void *userdata; |
39 | | struct vlc_list node; /**< node of media_tree_private_t.listeners */ |
40 | | }; |
41 | | |
42 | | typedef struct |
43 | | { |
44 | | vlc_media_tree_t public_data; |
45 | | |
46 | | struct vlc_list listeners; /**< list of vlc_media_tree_listener_id.node */ |
47 | | vlc_mutex_t lock; |
48 | | vlc_atomic_rc_t rc; |
49 | | } media_tree_private_t; |
50 | | |
51 | 0 | #define mt_priv(mt) container_of(mt, media_tree_private_t, public_data) |
52 | | |
53 | | vlc_media_tree_t * |
54 | | vlc_media_tree_New(void) |
55 | 0 | { |
56 | 0 | media_tree_private_t *priv = malloc(sizeof(*priv)); |
57 | 0 | if (unlikely(!priv)) |
58 | 0 | return NULL; |
59 | | |
60 | 0 | vlc_mutex_init(&priv->lock); |
61 | 0 | vlc_atomic_rc_init(&priv->rc); |
62 | 0 | vlc_list_init(&priv->listeners); |
63 | |
|
64 | 0 | vlc_media_tree_t *tree = &priv->public_data; |
65 | 0 | input_item_node_t *root = &tree->root; |
66 | 0 | root->p_item = NULL; |
67 | 0 | TAB_INIT(root->i_children, root->pp_children); |
68 | |
|
69 | 0 | return tree; |
70 | 0 | } |
71 | | |
72 | | static inline void |
73 | | vlc_media_tree_AssertLocked(vlc_media_tree_t *tree) |
74 | 0 | { |
75 | 0 | media_tree_private_t *priv = mt_priv(tree); |
76 | 0 | vlc_mutex_assert(&priv->lock); |
77 | 0 | } |
78 | | |
79 | | #define vlc_media_tree_listener_foreach(listener, tree) \ |
80 | 0 | vlc_list_foreach(listener, &mt_priv(tree)->listeners, node) |
81 | | |
82 | 0 | #define vlc_media_tree_NotifyListener(tree, listener, event, ...) \ |
83 | 0 | do { \ |
84 | 0 | if (listener->cbs->event) \ |
85 | 0 | listener->cbs->event(tree, ##__VA_ARGS__, listener->userdata); \ |
86 | 0 | } while(0) |
87 | | |
88 | 0 | #define vlc_media_tree_Notify(tree, event, ...) \ |
89 | 0 | do { \ |
90 | 0 | vlc_media_tree_AssertLocked(tree); \ |
91 | 0 | vlc_media_tree_listener_id *listener; \ |
92 | 0 | vlc_media_tree_listener_foreach(listener, tree) \ |
93 | 0 | vlc_media_tree_NotifyListener(tree, listener, event, ##__VA_ARGS__); \ |
94 | 0 | } while (0) |
95 | | |
96 | | static bool |
97 | | vlc_media_tree_FindNodeByMedia(input_item_node_t *parent, |
98 | | const input_item_t *media, |
99 | | input_item_node_t **result, |
100 | | input_item_node_t **result_parent) |
101 | 0 | { |
102 | 0 | for (int i = 0; i < parent->i_children; ++i) |
103 | 0 | { |
104 | 0 | input_item_node_t *child = parent->pp_children[i]; |
105 | 0 | if (child->p_item == media) |
106 | 0 | { |
107 | 0 | *result = child; |
108 | 0 | if (result_parent) |
109 | 0 | *result_parent = parent; |
110 | 0 | return true; |
111 | 0 | } |
112 | | |
113 | 0 | if (vlc_media_tree_FindNodeByMedia(child, media, result, result_parent)) |
114 | 0 | return true; |
115 | 0 | } |
116 | | |
117 | 0 | return false; |
118 | 0 | } |
119 | | |
120 | | static input_item_node_t * |
121 | | vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media); |
122 | | |
123 | | static void |
124 | | vlc_media_tree_AddSubtree(input_item_node_t *to, input_item_node_t *from) |
125 | 0 | { |
126 | 0 | for (int i = 0; i < from->i_children; ++i) |
127 | 0 | { |
128 | 0 | input_item_node_t *child = from->pp_children[i]; |
129 | 0 | input_item_node_t *node = vlc_media_tree_AddChild(to, child->p_item); |
130 | 0 | if (unlikely(!node)) |
131 | 0 | break; /* what could we do? */ |
132 | | |
133 | 0 | vlc_media_tree_AddSubtree(node, child); |
134 | 0 | } |
135 | 0 | } |
136 | | |
137 | | static void |
138 | | vlc_media_tree_ClearChildren(input_item_node_t *root) |
139 | 0 | { |
140 | 0 | for (int i = 0; i < root->i_children; ++i) |
141 | 0 | input_item_node_Delete(root->pp_children[i]); |
142 | |
|
143 | 0 | free(root->pp_children); |
144 | 0 | root->pp_children = NULL; |
145 | 0 | root->i_children = 0; |
146 | 0 | } |
147 | | |
148 | | static void |
149 | | media_subtree_changed(input_item_t *media, input_item_node_t *node, |
150 | | void *userdata) |
151 | 0 | { |
152 | 0 | vlc_media_tree_t *tree = userdata; |
153 | |
|
154 | 0 | vlc_media_tree_Lock(tree); |
155 | 0 | input_item_node_t *subtree_root; |
156 | | /* TODO retrieve the node without traversing the tree */ |
157 | 0 | bool found = vlc_media_tree_FindNodeByMedia(&tree->root, media, |
158 | 0 | &subtree_root, NULL); |
159 | 0 | if (!found) { |
160 | | /* the node probably failed to be allocated */ |
161 | 0 | vlc_media_tree_Unlock(tree); |
162 | 0 | return; |
163 | 0 | } |
164 | | |
165 | 0 | vlc_media_tree_ClearChildren(subtree_root); |
166 | 0 | vlc_media_tree_AddSubtree(subtree_root, node); |
167 | 0 | vlc_media_tree_Notify(tree, on_children_reset, subtree_root); |
168 | 0 | vlc_media_tree_Unlock(tree); |
169 | 0 | } |
170 | | |
171 | | static void |
172 | | media_subtree_preparse_ended(input_item_t *media, |
173 | | enum input_item_preparse_status status, |
174 | | void *user_data) |
175 | 0 | { |
176 | 0 | vlc_media_tree_t *tree = user_data; |
177 | |
|
178 | 0 | vlc_media_tree_Lock(tree); |
179 | 0 | input_item_node_t *subtree_root; |
180 | | /* TODO retrieve the node without traversing the tree */ |
181 | 0 | bool found = vlc_media_tree_FindNodeByMedia(&tree->root, media, |
182 | 0 | &subtree_root, NULL); |
183 | 0 | if (!found) { |
184 | | /* the node probably failed to be allocated */ |
185 | 0 | vlc_media_tree_Unlock(tree); |
186 | 0 | return; |
187 | 0 | } |
188 | 0 | vlc_media_tree_Notify(tree, on_preparse_end, subtree_root, status); |
189 | 0 | vlc_media_tree_Unlock(tree); |
190 | 0 | } |
191 | | |
192 | | static inline void |
193 | | vlc_media_tree_DestroyRootNode(vlc_media_tree_t *tree) |
194 | 0 | { |
195 | 0 | vlc_media_tree_ClearChildren(&tree->root); |
196 | 0 | } |
197 | | |
198 | | static void |
199 | | vlc_media_tree_Delete(vlc_media_tree_t *tree) |
200 | 0 | { |
201 | 0 | media_tree_private_t *priv = mt_priv(tree); |
202 | 0 | vlc_media_tree_listener_id *listener; |
203 | 0 | vlc_list_foreach(listener, &priv->listeners, node) |
204 | 0 | free(listener); |
205 | 0 | vlc_list_init(&priv->listeners); /* reset */ |
206 | 0 | vlc_media_tree_DestroyRootNode(tree); |
207 | 0 | free(tree); |
208 | 0 | } |
209 | | |
210 | | void |
211 | | vlc_media_tree_Hold(vlc_media_tree_t *tree) |
212 | 0 | { |
213 | 0 | media_tree_private_t *priv = mt_priv(tree); |
214 | 0 | vlc_atomic_rc_inc(&priv->rc); |
215 | 0 | } |
216 | | |
217 | | void |
218 | | vlc_media_tree_Release(vlc_media_tree_t *tree) |
219 | 0 | { |
220 | 0 | media_tree_private_t *priv = mt_priv(tree); |
221 | 0 | if (vlc_atomic_rc_dec(&priv->rc)) |
222 | 0 | vlc_media_tree_Delete(tree); |
223 | 0 | } |
224 | | |
225 | | void |
226 | | vlc_media_tree_Lock(vlc_media_tree_t *tree) |
227 | 0 | { |
228 | 0 | media_tree_private_t *priv = mt_priv(tree); |
229 | 0 | vlc_mutex_lock(&priv->lock); |
230 | 0 | } |
231 | | |
232 | | void |
233 | | vlc_media_tree_Unlock(vlc_media_tree_t *tree) |
234 | 0 | { |
235 | 0 | media_tree_private_t *priv = mt_priv(tree); |
236 | 0 | vlc_mutex_unlock(&priv->lock); |
237 | 0 | } |
238 | | |
239 | | static input_item_node_t * |
240 | | vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media) |
241 | 0 | { |
242 | 0 | input_item_node_t *node = input_item_node_Create(media); |
243 | 0 | if (unlikely(!node)) |
244 | 0 | return NULL; |
245 | | |
246 | 0 | input_item_node_AppendNode(parent, node); |
247 | |
|
248 | 0 | return node; |
249 | 0 | } |
250 | | |
251 | | static void |
252 | | vlc_media_tree_NotifyCurrentState(vlc_media_tree_t *tree, |
253 | | vlc_media_tree_listener_id *listener) |
254 | 0 | { |
255 | 0 | vlc_media_tree_NotifyListener(tree, listener, on_children_reset, |
256 | 0 | &tree->root); |
257 | 0 | } |
258 | | |
259 | | vlc_media_tree_listener_id * |
260 | | vlc_media_tree_AddListener(vlc_media_tree_t *tree, |
261 | | const struct vlc_media_tree_callbacks *cbs, |
262 | | void *userdata, bool notify_current_state) |
263 | 0 | { |
264 | 0 | vlc_media_tree_listener_id *listener = malloc(sizeof(*listener)); |
265 | 0 | if (unlikely(!listener)) |
266 | 0 | return NULL; |
267 | 0 | listener->cbs = cbs; |
268 | 0 | listener->userdata = userdata; |
269 | |
|
270 | 0 | media_tree_private_t *priv = mt_priv(tree); |
271 | 0 | vlc_media_tree_Lock(tree); |
272 | |
|
273 | 0 | vlc_list_append(&listener->node, &priv->listeners); |
274 | |
|
275 | 0 | if (notify_current_state) |
276 | 0 | vlc_media_tree_NotifyCurrentState(tree, listener); |
277 | |
|
278 | 0 | vlc_media_tree_Unlock(tree); |
279 | 0 | return listener; |
280 | 0 | } |
281 | | |
282 | | void |
283 | | vlc_media_tree_RemoveListener(vlc_media_tree_t *tree, |
284 | | vlc_media_tree_listener_id *listener) |
285 | 0 | { |
286 | 0 | vlc_media_tree_Lock(tree); |
287 | 0 | vlc_list_remove(&listener->node); |
288 | 0 | vlc_media_tree_Unlock(tree); |
289 | |
|
290 | 0 | free(listener); |
291 | 0 | } |
292 | | |
293 | | input_item_node_t * |
294 | | vlc_media_tree_Add(vlc_media_tree_t *tree, input_item_node_t *parent, |
295 | | input_item_t *media) |
296 | 0 | { |
297 | 0 | vlc_media_tree_AssertLocked(tree); |
298 | |
|
299 | 0 | input_item_node_t *node = vlc_media_tree_AddChild(parent, media); |
300 | 0 | if (unlikely(!node)) |
301 | 0 | return NULL; |
302 | | |
303 | 0 | vlc_media_tree_Notify(tree, on_children_added, parent, &node, 1); |
304 | |
|
305 | 0 | return node; |
306 | 0 | } |
307 | | |
308 | | bool |
309 | | vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *media, |
310 | | input_item_node_t **result, |
311 | | input_item_node_t **result_parent) |
312 | 0 | { |
313 | 0 | vlc_media_tree_AssertLocked(tree); |
314 | | |
315 | | /* quick & dirty depth-first O(n) implementation, with n the number of nodes |
316 | | * in the tree */ |
317 | 0 | return vlc_media_tree_FindNodeByMedia(&tree->root, media, result, |
318 | 0 | result_parent); |
319 | 0 | } |
320 | | |
321 | | bool |
322 | | vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *media) |
323 | 0 | { |
324 | 0 | vlc_media_tree_AssertLocked(tree); |
325 | |
|
326 | 0 | input_item_node_t *node; |
327 | 0 | input_item_node_t *parent; |
328 | 0 | if (!vlc_media_tree_FindNodeByMedia(&tree->root, media, &node, &parent)) |
329 | 0 | return false; |
330 | | |
331 | 0 | input_item_node_RemoveNode(parent, node); |
332 | 0 | vlc_media_tree_Notify(tree, on_children_removed, parent, &node, 1); |
333 | 0 | input_item_node_Delete(node); |
334 | 0 | return true; |
335 | 0 | } |
336 | | |
337 | | static const input_preparser_callbacks_t input_preparser_callbacks = { |
338 | | .on_subtree_added = media_subtree_changed, |
339 | | .on_preparse_ended = media_subtree_preparse_ended |
340 | | }; |
341 | | |
342 | | void |
343 | | vlc_media_tree_Preparse(vlc_media_tree_t *tree, libvlc_int_t *libvlc, |
344 | | input_item_t *media, void* id) |
345 | 0 | { |
346 | | #ifdef TEST_MEDIA_SOURCE |
347 | | VLC_UNUSED(tree); |
348 | | VLC_UNUSED(libvlc); |
349 | | VLC_UNUSED(media); |
350 | | VLC_UNUSED(id); |
351 | | VLC_UNUSED(input_preparser_callbacks); |
352 | | #else |
353 | 0 | media->i_preparse_depth = 1; |
354 | 0 | vlc_MetadataRequest(libvlc, media, META_REQUEST_OPTION_SCOPE_ANY | |
355 | 0 | META_REQUEST_OPTION_DO_INTERACT, |
356 | 0 | &input_preparser_callbacks, tree, 0, id); |
357 | 0 | #endif |
358 | 0 | } |
359 | | |
360 | | |
361 | | void |
362 | | vlc_media_tree_PreparseCancel(libvlc_int_t *libvlc, void* id) |
363 | 0 | { |
364 | | #ifdef TEST_MEDIA_SOURCE |
365 | | VLC_UNUSED(libvlc); |
366 | | VLC_UNUSED(id); |
367 | | #else |
368 | 0 | libvlc_MetadataCancel(libvlc, id); |
369 | 0 | #endif |
370 | 0 | } |