]> git.sesse.net Git - vlc/blob - src/playlist/tree.c
playlist: Never delete the playlist_item directly. We don't know who might need it.
[vlc] / src / playlist / tree.c
1 /*****************************************************************************
2  * tree.c : Playlist tree walking functions
3  *****************************************************************************
4  * Copyright (C) 1999-2007 the VideoLAN team
5  * $Id$
6  *
7  * Authors: ClĂ©ment Stenac <zorglub@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <vlc_common.h>
28 #include <assert.h>
29 #include "vlc_playlist.h"
30 #include "playlist_internal.h"
31
32 /************************************************************************
33  * Local prototypes
34  ************************************************************************/
35 playlist_item_t *GetNextUncle( playlist_t *p_playlist, playlist_item_t *p_item,
36                                playlist_item_t *p_root );
37 playlist_item_t *GetPrevUncle( playlist_t *p_playlist, playlist_item_t *p_item,
38                                playlist_item_t *p_root );
39
40 playlist_item_t *GetNextItem( playlist_t *p_playlist,
41                               playlist_item_t *p_root,
42                               playlist_item_t *p_item );
43 playlist_item_t *GetPrevItem( playlist_t *p_playlist,
44                               playlist_item_t *p_item,
45                               playlist_item_t *p_root );
46
47 /**
48  * Create a playlist node
49  *
50  * \param p_playlist the playlist
51  * \param psz_name the name of the node
52  * \param p_parent the parent node to attach to or NULL if no attach
53  * \param p_flags miscellaneous flags
54  * \param p_input the input_item to attach to or NULL if it has to be created
55  * \return the new node
56  */
57 playlist_item_t * playlist_NodeCreate( playlist_t *p_playlist,
58                                        const char *psz_name,
59                                        playlist_item_t *p_parent, int i_flags,
60                                        input_item_t *p_input )
61 {
62     input_item_t *p_new_input = NULL;
63     playlist_item_t *p_item;
64
65     if( !psz_name ) psz_name = _("Undefined");
66
67     if( !p_input )
68         p_new_input = input_ItemNewWithType( VLC_OBJECT(p_playlist), NULL,
69                                         psz_name, 0, NULL, -1, ITEM_TYPE_NODE );
70     p_item = playlist_ItemNewFromInput( p_playlist,
71                                         p_input ? p_input : p_new_input );
72     if( p_new_input )
73         vlc_gc_decref( p_new_input );
74
75     if( p_item == NULL )  return NULL;
76     p_item->i_children = 0;
77
78     ARRAY_APPEND(p_playlist->all_items, p_item);
79
80     if( p_parent != NULL )
81         playlist_NodeAppend( p_playlist, p_item, p_parent );
82     playlist_SendAddNotify( p_playlist, p_item->i_id,
83                             p_parent ? p_parent->i_id : -1,
84                             !( i_flags & PLAYLIST_NO_REBUILD ));
85     return p_item;
86 }
87
88 /**
89  * Remove all the children of a node
90  *
91  * This function must be entered with the playlist lock
92  *
93  * \param p_playlist the playlist
94  * \param p_root the node
95  * \param b_delete_items do we have to delete the children items ?
96  * \return VLC_SUCCESS or an error
97  */
98 int playlist_NodeEmpty( playlist_t *p_playlist, playlist_item_t *p_root,
99                         bool b_delete_items )
100 {
101     PL_ASSERT_LOCKED;
102     int i;
103     if( p_root->i_children == -1 )
104     {
105         return VLC_EGENERIC;
106     }
107
108     /* Delete the children */
109     for( i =  p_root->i_children-1 ; i >= 0 ;i-- )
110     {
111         if( p_root->pp_children[i]->i_children > -1 )
112         {
113             playlist_NodeDelete( p_playlist, p_root->pp_children[i],
114                                  b_delete_items , false );
115         }
116         else if( b_delete_items )
117         {
118             /* Delete the item here */
119             playlist_DeleteFromItemId( p_playlist,
120                                        p_root->pp_children[i]->i_id );
121         }
122     }
123     return VLC_SUCCESS;
124 }
125
126 /**
127  * Remove all the children of a node and removes the node
128  *
129  * \param p_playlist the playlist
130  * \param p_root the node
131  * \param b_delete_items do we have to delete the children items ?
132  * \return VLC_SUCCESS or an error
133  */
134 int playlist_NodeDelete( playlist_t *p_playlist, playlist_item_t *p_root,
135                          bool b_delete_items, bool b_force )
136 {
137     PL_ASSERT_LOCKED;
138     int i;
139
140     if( p_root->i_children == -1 )
141     {
142         return VLC_EGENERIC;
143     }
144
145     /* Delete the children */
146     for( i =  p_root->i_children - 1 ; i >= 0; i-- )
147     {
148         if( p_root->pp_children[i]->i_children > -1 )
149         {
150             playlist_NodeDelete( p_playlist, p_root->pp_children[i],
151                                  b_delete_items , b_force );
152         }
153         else if( b_delete_items )
154         {
155             playlist_DeleteFromItemId( p_playlist,
156                                        p_root->pp_children[i]->i_id );
157         }
158     }
159     /* Delete the node */
160     if( p_root->i_flags & PLAYLIST_RO_FLAG && !b_force )
161     {
162     }
163     else
164     {
165         int i;
166         var_SetInteger( p_playlist, "item-deleted", p_root->i_id );
167         ARRAY_BSEARCH( p_playlist->all_items, ->i_id, int,
168                        p_root->i_id, i );
169         if( i != -1 )
170             ARRAY_REMOVE( p_playlist->all_items, i );
171
172         /* Remove the item from its parent */
173         if( p_root->p_parent )
174             playlist_NodeRemoveItem( p_playlist, p_root, p_root->p_parent );
175
176         playlist_ItemRelease( p_root );
177     }
178     return VLC_SUCCESS;
179 }
180
181
182 /**
183  * Adds an item to the children of a node
184  *
185  * \param p_playlist the playlist
186  * \param p_item the item to append
187  * \param p_parent the parent node
188  * \return VLC_SUCCESS or an error
189  */
190 int playlist_NodeAppend( playlist_t *p_playlist,
191                          playlist_item_t *p_item,
192                          playlist_item_t *p_parent )
193 {
194     return playlist_NodeInsert( p_playlist, p_item, p_parent, -1 );
195 }
196
197 int playlist_NodeInsert( playlist_t *p_playlist,
198                          playlist_item_t *p_item,
199                          playlist_item_t *p_parent,
200                          int i_position )
201 {
202     PL_ASSERT_LOCKED;
203    (void)p_playlist;
204    assert( p_parent && p_parent->i_children != -1 );
205    if( i_position == -1 ) i_position = p_parent->i_children ;
206
207    INSERT_ELEM( p_parent->pp_children,
208                 p_parent->i_children,
209                 i_position,
210                 p_item );
211    p_item->p_parent = p_parent;
212    return VLC_SUCCESS;
213 }
214
215 /**
216  * Deletes an item from the children of a node
217  *
218  * \param p_playlist the playlist
219  * \param p_item the item to remove
220  * \param p_parent the parent node
221  * \return VLC_SUCCESS or an error
222  */
223 int playlist_NodeRemoveItem( playlist_t *p_playlist,
224                         playlist_item_t *p_item,
225                         playlist_item_t *p_parent )
226 {
227     PL_ASSERT_LOCKED;
228    (void)p_playlist;
229
230    for(int i= 0; i< p_parent->i_children ; i++ )
231    {
232        if( p_parent->pp_children[i] == p_item )
233        {
234            REMOVE_ELEM( p_parent->pp_children, p_parent->i_children, i );
235        }
236    }
237
238    return VLC_SUCCESS;
239 }
240
241
242 /**
243  * Count the children of a node
244  *
245  * \param p_playlist the playlist
246  * \param p_node the node
247  * \return the number of children
248  */
249 int playlist_NodeChildrenCount( playlist_t *p_playlist, playlist_item_t*p_node)
250 {
251     PL_ASSERT_LOCKED;
252     int i;
253     int i_nb = 0;
254
255     if( p_node->i_children == -1 )
256         return 0;
257
258     i_nb = p_node->i_children;
259     for( i=0 ; i< p_node->i_children;i++ )
260     {
261         if( p_node->pp_children[i]->i_children == -1 )
262             break;
263         else
264             i_nb += playlist_NodeChildrenCount( p_playlist,
265                                                 p_node->pp_children[i] );
266     }
267     return i_nb;
268 }
269
270 /**
271  * Search a child of a node by its name
272  *
273  * \param p_node the node
274  * \param psz_search the name of the child to search
275  * \return the child item or NULL if not found or error
276  */
277 playlist_item_t *playlist_ChildSearchName( playlist_item_t *p_node,
278                                            const char *psz_search )
279 {
280     playlist_t * p_playlist = p_node->p_playlist; /* For assert_locked */
281     PL_ASSERT_LOCKED;
282     int i;
283
284     if( p_node->i_children < 0 )
285     {
286          return NULL;
287     }
288     for( i = 0 ; i< p_node->i_children; i++ )
289     {
290         if( !strcmp( p_node->pp_children[i]->p_input->psz_name, psz_search ) )
291         {
292             return p_node->pp_children[i];
293         }
294     }
295     return NULL;
296 }
297
298 /**
299  * Create a pair of nodes in the category and onelevel trees.
300  * They share the same input item.
301  * \param p_playlist the playlist
302  * \param psz_name the name of the nodes
303  * \param pp_node_cat pointer to return the node in category tree
304  * \param pp_node_one pointer to return the node in onelevel tree
305  * \param b_for_sd For Services Discovery ? (make node read-only and unskipping)
306  */
307 void playlist_NodesPairCreate( playlist_t *p_playlist, const char *psz_name,
308                                playlist_item_t **pp_node_cat,
309                                playlist_item_t **pp_node_one,
310                                bool b_for_sd )
311 {
312     PL_ASSERT_LOCKED;
313     *pp_node_cat = playlist_NodeCreate( p_playlist, psz_name,
314                                         p_playlist->p_root_category, 0, NULL );
315     *pp_node_one = playlist_NodeCreate( p_playlist, psz_name,
316                                         p_playlist->p_root_onelevel, 0,
317                                         (*pp_node_cat)->p_input );
318     if( b_for_sd )
319     {
320         (*pp_node_cat)->i_flags |= PLAYLIST_RO_FLAG;
321         (*pp_node_cat)->i_flags |= PLAYLIST_SKIP_FLAG;
322         (*pp_node_one)->i_flags |= PLAYLIST_RO_FLAG;
323         (*pp_node_one)->i_flags |= PLAYLIST_SKIP_FLAG;
324     }
325 }
326
327 /**
328  * Get the node in the preferred tree from a node in one of category
329  * or onelevel tree.
330  */
331 playlist_item_t * playlist_GetPreferredNode( playlist_t *p_playlist,
332                                              playlist_item_t *p_node )
333 {
334     PL_ASSERT_LOCKED;
335     int i;
336     if( p_node->p_parent == p_playlist->p_root_category )
337     {
338         if( p_playlist->b_tree )
339             return p_node;
340         for( i = 0 ; i< p_playlist->p_root_onelevel->i_children; i++ )
341         {
342             if( p_playlist->p_root_onelevel->pp_children[i]->p_input->i_id ==
343                     p_node->p_input->i_id )
344                 return p_playlist->p_root_onelevel->pp_children[i];
345         }
346     }
347     else if( p_node->p_parent == p_playlist->p_root_onelevel )
348     {
349         if( !p_playlist->b_tree )
350             return p_node;
351         for( i = 0 ; i< p_playlist->p_root_category->i_children; i++ )
352         {
353             if( p_playlist->p_root_category->pp_children[i]->p_input->i_id ==
354                     p_node->p_input->i_id )
355                 return p_playlist->p_root_category->pp_children[i];
356         }
357     }
358     return NULL;
359 }
360
361 /**********************************************************************
362  * Tree walking functions
363  **********************************************************************/
364
365 playlist_item_t *playlist_GetLastLeaf(playlist_t *p_playlist,
366                                       playlist_item_t *p_root )
367 {
368     PL_ASSERT_LOCKED;
369     int i;
370     playlist_item_t *p_item;
371     for ( i = p_root->i_children - 1; i >= 0; i-- )
372     {
373         if( p_root->pp_children[i]->i_children == -1 )
374             return p_root->pp_children[i];
375         else if( p_root->pp_children[i]->i_children > 0)
376         {
377              p_item = playlist_GetLastLeaf( p_playlist,
378                                             p_root->pp_children[i] );
379             if ( p_item != NULL )
380                 return p_item;
381         }
382         else if( i == 0 )
383             return NULL;
384     }
385     return NULL;
386 }
387
388 /**
389  * Finds the next item to play
390  *
391  * \param p_playlist the playlist
392  * \param p_root the root node
393  * \param p_item the previous item  (NULL if none )
394  * \return the next item to play, or NULL if none found
395  */
396 playlist_item_t *playlist_GetNextLeaf( playlist_t *p_playlist,
397                                        playlist_item_t *p_root,
398                                        playlist_item_t *p_item,
399                                        bool b_ena, bool b_unplayed )
400 {
401     PL_ASSERT_LOCKED;
402     playlist_item_t *p_next;
403
404     assert( p_root && p_root->i_children != -1 );
405
406     PL_DEBUG2( "finding next of %s within %s",
407                PLI_NAME( p_item ), PLI_NAME( p_root ) );
408
409     /* Now, walk the tree until we find a suitable next item */
410     p_next = p_item;
411     while( 1 )
412     {
413         bool b_ena_ok = true, b_unplayed_ok = true;
414         p_next = GetNextItem( p_playlist, p_root, p_next );
415         if( !p_next || p_next == p_root )
416             break;
417         if( p_next->i_children == -1 )
418         {
419             if( b_ena && p_next->i_flags & PLAYLIST_DBL_FLAG )
420                 b_ena_ok = false;
421             if( b_unplayed && p_next->p_input->i_nb_played != 0 )
422                 b_unplayed_ok = false;
423             if( b_ena_ok && b_unplayed_ok ) break;
424         }
425     }
426     if( p_next == NULL ) PL_DEBUG2( "at end of node" );
427     return p_next;
428 }
429
430 /**
431  * Finds the previous item to play
432  *
433  * \param p_playlist the playlist
434  * \param p_root the root node
435  * \param p_item the previous item  (NULL if none )
436  * \return the next item to play, or NULL if none found
437  */
438 playlist_item_t *playlist_GetPrevLeaf( playlist_t *p_playlist,
439                                        playlist_item_t *p_root,
440                                        playlist_item_t *p_item,
441                                        bool b_ena, bool b_unplayed )
442 {
443     PL_ASSERT_LOCKED;
444     playlist_item_t *p_prev;
445
446     PL_DEBUG2( "finding previous os %s within %s", PLI_NAME( p_item ),
447                                                    PLI_NAME( p_root ) );
448     assert( p_root && p_root->i_children != -1 );
449
450     /* Now, walk the tree until we find a suitable previous item */
451     p_prev = p_item;
452     while( 1 )
453     {
454         bool b_ena_ok = true, b_unplayed_ok = true;
455         p_prev = GetPrevItem( p_playlist, p_root, p_prev );
456         if( !p_prev || p_prev == p_root )
457             break;
458         if( p_prev->i_children == -1 )
459         {
460             if( b_ena && p_prev->i_flags & PLAYLIST_DBL_FLAG )
461                 b_ena_ok = false;
462             if( b_unplayed && p_prev->p_input->i_nb_played != 0 )
463                 b_unplayed_ok = false;
464             if( b_ena_ok && b_unplayed_ok ) break;
465         }
466     }
467     if( p_prev == NULL ) PL_DEBUG2( "at beginning of node" );
468     return p_prev;
469 }
470
471 /************************************************************************
472  * Following functions are local
473  ***********************************************************************/
474
475 /**
476  * Get the next item in the tree
477  * If p_item is NULL, return the first child of root
478  **/
479 playlist_item_t *GetNextItem( playlist_t *p_playlist,
480                               playlist_item_t *p_root,
481                               playlist_item_t *p_item )
482 {
483     playlist_item_t *p_parent;
484     int i;
485
486     /* Node with children, get the first one */
487     if( p_item && p_item->i_children > 0 )
488         return p_item->pp_children[0];
489
490     if( p_item != NULL )
491         p_parent = p_item->p_parent;
492     else
493         p_parent = p_root;
494     for( i= 0 ; i < p_parent->i_children ; i++ )
495     {
496         if( p_item == NULL || p_parent->pp_children[i] == p_item )
497         {
498             if( p_item == NULL )
499                 i = -1;
500
501             if( i+1 >= p_parent->i_children )
502             {
503                 /* Was already the last sibling. Look for uncles */
504                 PL_DEBUG2( "Current item is the last of the node,"
505                            "looking for uncle from %s",
506                             p_parent->p_input->psz_name );
507
508                 if( p_parent == p_root )
509                 {
510                     PL_DEBUG2( "already at root" );
511                     return NULL;
512                 }
513                 return GetNextUncle( p_playlist, p_item, p_root );
514             }
515             else
516             {
517                 return  p_parent->pp_children[i+1];
518             }
519         }
520     }
521     return NULL;
522 }
523
524 playlist_item_t *GetNextUncle( playlist_t *p_playlist, playlist_item_t *p_item,
525                                playlist_item_t *p_root )
526 {
527     playlist_item_t *p_parent = p_item->p_parent;
528     playlist_item_t *p_grandparent;
529     bool b_found = false;
530
531     (void)p_playlist;
532
533     if( p_parent != NULL )
534     {
535         p_grandparent = p_parent->p_parent;
536         while( p_grandparent )
537         {
538             int i;
539             for( i = 0 ; i< p_grandparent->i_children ; i++ )
540             {
541                 if( p_parent == p_grandparent->pp_children[i] )
542                 {
543                     PL_DEBUG2( "parent %s found as child %i of grandparent %s",
544                                p_parent->p_input->psz_name, i,
545                                p_grandparent->p_input->psz_name );
546                     b_found = true;
547                     break;
548                 }
549             }
550             if( b_found && i + 1 < p_grandparent->i_children )
551             {
552                     return p_grandparent->pp_children[i+1];
553             }
554             /* Not found at root */
555             if( p_grandparent == p_root )
556             {
557                 return NULL;
558             }
559             else
560             {
561                 p_parent = p_grandparent;
562                 p_grandparent = p_parent->p_parent;
563             }
564         }
565     }
566     /* We reached root */
567     return NULL;
568 }
569
570 playlist_item_t *GetPrevUncle( playlist_t *p_playlist, playlist_item_t *p_item,
571                                playlist_item_t *p_root )
572 {
573     playlist_item_t *p_parent = p_item->p_parent;
574     playlist_item_t *p_grandparent;
575     bool b_found = false;
576
577     (void)p_playlist;
578
579     if( p_parent != NULL )
580     {
581         p_grandparent = p_parent->p_parent;
582         while( 1 )
583         {
584             int i;
585             for( i = p_grandparent->i_children -1 ; i >= 0; i-- )
586             {
587                 if( p_parent == p_grandparent->pp_children[i] )
588                 {
589                     b_found = true;
590                     break;
591                 }
592             }
593             if( b_found && i - 1 > 0 )
594             {
595                 return p_grandparent->pp_children[i-1];
596             }
597             /* Not found at root */
598             if( p_grandparent == p_root )
599             {
600                 return NULL;
601             }
602             else
603             {
604                 p_parent = p_grandparent;
605                 p_grandparent = p_parent->p_parent;
606             }
607         }
608     }
609     /* We reached root */
610     return NULL;
611 }
612
613
614 /* Recursively search the tree for previous item */
615 playlist_item_t *GetPrevItem( playlist_t *p_playlist,
616                               playlist_item_t *p_root,
617                               playlist_item_t *p_item )
618 {
619     playlist_item_t *p_parent;
620     int i;
621
622     /* Node with children, get the last one */
623     if( p_item && p_item->i_children > 0 )
624         return p_item->pp_children[p_item->i_children - 1];
625
626     /* Last child of its parent ? */
627     if( p_item != NULL )
628         p_parent = p_item->p_parent;
629     else
630     {
631         msg_Err( p_playlist, "Get the last one" );
632         abort();
633     };
634
635     for( i = p_parent->i_children -1 ; i >= 0 ;  i-- )
636     {
637         if( p_parent->pp_children[i] == p_item )
638         {
639             if( i-1 < 0 )
640             {
641                /* Was already the first sibling. Look for uncles */
642                 PL_DEBUG2( "current item is the first of its node,"
643                            "looking for uncle from %s",
644                            p_parent->p_input->psz_name );
645                 if( p_parent == p_root )
646                 {
647                     PL_DEBUG2( "already at root" );
648                     return NULL;
649                 }
650                 return GetPrevUncle( p_playlist, p_item, p_root );
651             }
652             else
653             {
654                 return p_parent->pp_children[i-1];
655             }
656         }
657     }
658     return NULL;
659 }