]> git.sesse.net Git - vlc/blob - src/playlist/tree.c
Fix crash
[vlc] / src / playlist / tree.c
1 /*****************************************************************************
2  * tree.c : Playlist tree walking functions
3  *****************************************************************************
4  * Copyright (C) 1999-2004 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 #include <vlc/vlc.h>
24 #include <assert.h>
25 #include <vlc/input.h>
26 #include "vlc_playlist.h"
27 #include "playlist_internal.h"
28
29 /************************************************************************
30  * Local prototypes
31  ************************************************************************/
32 playlist_item_t *GetNextUncle( playlist_t *p_playlist, playlist_item_t *p_item,
33                                playlist_item_t *p_root );
34 playlist_item_t *GetPrevUncle( playlist_t *p_playlist, playlist_item_t *p_item,
35                                playlist_item_t *p_root );
36
37 playlist_item_t *GetNextItem( playlist_t *p_playlist,
38                               playlist_item_t *p_root,
39                               playlist_item_t *p_item );
40 playlist_item_t *GetPrevItem( playlist_t *p_playlist,
41                               playlist_item_t *p_item,
42                               playlist_item_t *p_root );
43
44 /**
45  * Create a playlist node
46  *
47  * \param p_playlist the playlist
48  * \paam psz_name the name of the node
49  * \param p_parent the parent node to attach to or NULL if no attach
50  * \return the new node
51  */
52 playlist_item_t * playlist_NodeCreate( playlist_t *p_playlist, char *psz_name,
53                                        playlist_item_t *p_parent )
54 {
55     input_item_t *p_input;
56     playlist_item_t *p_item;
57
58     if( !psz_name ) psz_name = _("Undefined");
59     p_input = input_ItemNewWithType( VLC_OBJECT(p_playlist), NULL, psz_name,
60                                      0, NULL, -1, ITEM_TYPE_NODE );
61     p_input->i_id = ++p_playlist->i_last_input_id;
62     p_item = playlist_ItemNewFromInput( VLC_OBJECT(p_playlist), p_input );
63     p_item->i_children = 0;
64
65     if( p_item == NULL )
66     {
67         return NULL;
68     }
69     INSERT_ELEM( p_playlist->pp_all_items,
70                  p_playlist->i_all_size,
71                  p_playlist->i_all_size,
72                  p_item );
73
74     if( p_parent != NULL )
75     {
76         playlist_NodeAppend( p_playlist, p_item, p_parent );
77     }
78
79     playlist_SendAddNotify( p_playlist, p_item->i_id,
80                             p_parent ? p_parent->i_id : -1 );
81     return p_item;
82 }
83
84 /**
85  * Remove all the children of a node
86  *
87  * This function must be entered with the playlist lock
88  *
89  * \param p_playlist the playlist
90  * \param p_root the node
91  * \param b_delete_items do we have to delete the children items ?
92  * \return VLC_SUCCESS or an error
93  */
94 int playlist_NodeEmpty( playlist_t *p_playlist, playlist_item_t *p_root,
95                         vlc_bool_t b_delete_items )
96 {
97     int i;
98     if( p_root->i_children == -1 )
99     {
100         return VLC_EGENERIC;
101     }
102
103     /* Delete the children */
104     for( i =  p_root->i_children-1 ; i >= 0 ;i-- )
105     {
106         if( p_root->pp_children[i]->i_children > -1 )
107         {
108             playlist_NodeDelete( p_playlist, p_root->pp_children[i],
109                                  b_delete_items , VLC_FALSE );
110         }
111         else if( b_delete_items )
112         {
113             /* Delete the item here */
114             playlist_DeleteFromItemId( p_playlist,
115                                        p_root->pp_children[i]->i_id );
116         }
117     }
118     return VLC_SUCCESS;
119 }
120
121 /**
122  * Remove all the children of a node and removes the node
123  *
124  * \param p_playlist the playlist
125  * \param p_root the node
126  * \param b_delete_items do we have to delete the children items ?
127  * \return VLC_SUCCESS or an error
128  */
129 int playlist_NodeDelete( playlist_t *p_playlist, playlist_item_t *p_root,
130                          vlc_bool_t b_delete_items, vlc_bool_t b_force )
131 {
132     int i;
133
134     if( p_root->i_children == -1 )
135     {
136         return VLC_EGENERIC;
137     }
138
139     /* Delete the children */
140     for( i =  p_root->i_children - 1 ; i >= 0; i-- )
141     {
142         if( p_root->pp_children[i]->i_children > -1 )
143         {
144             playlist_NodeDelete( p_playlist, p_root->pp_children[i],
145                                  b_delete_items , b_force );
146         }
147         else if( b_delete_items )
148         {
149             playlist_DeleteFromItemId( p_playlist,
150                                        p_root->pp_children[i]->i_id );
151         }
152     }
153     /* Delete the node */
154     if( p_root->i_flags & PLAYLIST_RO_FLAG && !b_force )
155     {
156     }
157     else
158     {
159         var_SetInteger( p_playlist, "item-deleted", p_root->p_input->i_id );
160         for( i = 0 ; i< p_playlist->i_all_size; i ++ )
161         {
162             if( p_playlist->pp_all_items[i]->p_input->i_id ==
163                                p_root->p_input->i_id )
164             {
165                 REMOVE_ELEM( p_playlist->pp_all_items, p_playlist->i_all_size,
166                              i );
167                 break;
168             }
169         }
170
171         /* Remove the item from its parent */
172         if( p_root->p_parent )
173         {
174             playlist_NodeRemoveItem( p_playlist, p_root, p_root->p_parent );
175         }
176
177         playlist_ItemDelete( p_root );
178     }
179     return VLC_SUCCESS;
180 }
181
182
183 /**
184  * Adds an item to the children of a node
185  *
186  * \param p_playlist the playlist
187  * \param p_item the item to append
188  * \param p_parent the parent node
189  * \return VLC_SUCCESS or an error
190  */
191 int playlist_NodeAppend( playlist_t *p_playlist,
192                          playlist_item_t *p_item,
193                          playlist_item_t *p_parent )
194 {
195     return playlist_NodeInsert( p_playlist, p_item, p_parent, -1 );
196 }
197
198 int playlist_NodeInsert( playlist_t *p_playlist,
199                          playlist_item_t *p_item,
200                          playlist_item_t *p_parent,
201                          int i_position )
202 {
203    assert( p_parent && p_parent->i_children != -1 );
204    if( i_position == -1 ) i_position = p_parent->i_children ;
205
206    INSERT_ELEM( p_parent->pp_children,
207                 p_parent->i_children,
208                 i_position,
209                 p_item );
210    p_item->p_parent = p_parent;
211    return VLC_SUCCESS;
212 }
213
214 /**
215  * Deletes an item from the children of a node
216  *
217  * \param p_playlist the playlist
218  * \param p_item the item to remove
219  * \param p_parent the parent node
220  * \return VLC_SUCCESS or an error
221  */
222 int playlist_NodeRemoveItem( playlist_t *p_playlist,
223                         playlist_item_t *p_item,
224                         playlist_item_t *p_parent )
225 {
226    int i;
227    for( i= 0; i< p_parent->i_children ; i++ )
228    {
229        if( p_parent->pp_children[i] == p_item )
230        {
231            REMOVE_ELEM( p_parent->pp_children, p_parent->i_children, i );
232        }
233    }
234
235    return VLC_SUCCESS;
236 }
237
238
239 /**
240  * Count the children of a node
241  *
242  * \param p_playlist the playlist
243  * \param p_node the node
244  * \return the number of children
245  */
246 int playlist_NodeChildrenCount( playlist_t *p_playlist, playlist_item_t*p_node)
247 {
248     int i;
249     int i_nb = 0;
250     if( p_node->i_children == -1 )
251         return 0;
252
253     for( i=0 ; i< p_node->i_children;i++ )
254     {
255         if( p_node->pp_children[i]->i_children == -1 )
256             i_nb++;
257         else
258             i_nb += playlist_NodeChildrenCount( p_playlist,
259                                                 p_node->pp_children[i] );
260     }
261     return i_nb;
262 }
263
264 /**
265  * Search a child of a node by its name
266  *
267  * \param p_node the node
268  * \param psz_search the name of the child to search
269  * \return the child item or NULL if not found or error
270  */
271 playlist_item_t *playlist_ChildSearchName( playlist_item_t *p_node,
272                                            const char *psz_search )
273 {
274     int i;
275
276     if( p_node->i_children < 0 )
277     {
278          return NULL;
279     }
280     for( i = 0 ; i< p_node->i_children; i++ )
281     {
282         if( !strcmp( p_node->pp_children[i]->p_input->psz_name, psz_search ) )
283         {
284             return p_node->pp_children[i];
285         }
286     }
287     return NULL;
288 }
289
290 /**
291  * Create a pair of nodes in the category and onelevel trees.
292  * They share the same input ID.
293  * \todo really share the input item
294  * \param p_playlist the playlist
295  * \param psz_name the name of the nodes
296  * \param pp_node_cat pointer to return the node in category tree
297  * \param pp_node_one pointer to return the node in onelevel tree
298  * \param b_for_sd For Services Discovery ? (make node read-only and unskipping)
299  */
300 void playlist_NodesPairCreate( playlist_t *p_playlist, char *psz_name,
301                                playlist_item_t **pp_node_cat,
302                                playlist_item_t **pp_node_one,
303                                vlc_bool_t b_for_sd )
304 {
305     *pp_node_cat = playlist_NodeCreate( p_playlist, psz_name,
306                                         p_playlist->p_root_category );
307     *pp_node_one = playlist_NodeCreate( p_playlist, psz_name,
308                                         p_playlist->p_root_onelevel );
309     (*pp_node_one)->p_input->i_id = (*pp_node_cat)->p_input->i_id;
310     if( b_for_sd )
311     {
312         (*pp_node_cat)->i_flags |= PLAYLIST_RO_FLAG;
313         (*pp_node_cat)->i_flags |= PLAYLIST_SKIP_FLAG;
314         (*pp_node_one)->i_flags |= PLAYLIST_RO_FLAG;
315         (*pp_node_one)->i_flags |= PLAYLIST_SKIP_FLAG;
316     }
317 }
318
319 /**
320  * Get the node in the preferred tree from a node in one of category
321  * or onelevel tree. 
322  * For example, for the SAP node, it will return the node in the category
323  * tree if --playlist-tree is not set to never, because the SAP node prefers
324  * category
325  */
326 playlist_item_t * playlist_GetPreferredNode( playlist_t *p_playlist,
327                                              playlist_item_t *p_node )
328 {
329     int i;
330     if( p_node->p_parent == p_playlist->p_root_category )
331     {
332         if( p_playlist->b_always_tree ||
333             p_node->p_input->b_prefers_tree ) return p_node;
334         for( i = 0 ; i< p_playlist->p_root_onelevel->i_children; i++ )
335         {
336             if( p_playlist->p_root_onelevel->pp_children[i]->p_input->i_id ==
337                     p_node->p_input->i_id )
338                 return p_playlist->p_root_onelevel->pp_children[i];
339         }
340     }
341     else if( p_node->p_parent == p_playlist->p_root_onelevel )
342     {
343         if( p_playlist->b_never_tree || !p_node->p_input->b_prefers_tree )
344             return p_node;
345         for( i = 0 ; i< p_playlist->p_root_category->i_children; i++ )
346         {
347             if( p_playlist->p_root_category->pp_children[i]->p_input->i_id ==
348                     p_node->p_input->i_id )
349                 return p_playlist->p_root_category->pp_children[i];
350         }
351     }
352     return NULL;
353 }
354
355 /**********************************************************************
356  * Tree walking functions
357  **********************************************************************/
358
359 playlist_item_t *playlist_GetLastLeaf(playlist_t *p_playlist,
360                                       playlist_item_t *p_root )
361 {
362     int i;
363     playlist_item_t *p_item;
364     for ( i = p_root->i_children - 1; i >= 0; i-- )
365     {
366         if( p_root->pp_children[i]->i_children == -1 )
367             return p_root->pp_children[i];
368         else if( p_root->pp_children[i]->i_children > 0)
369         {
370              p_item = playlist_GetLastLeaf( p_playlist,
371                                             p_root->pp_children[i] );
372             if ( p_item != NULL )
373                 return p_item;
374         }
375         else if( i == 0 )
376             return NULL;
377     }
378     return NULL;
379 }
380
381 int playlist_GetAllEnabledChildren( playlist_t *p_playlist,
382                                     playlist_item_t *p_node,
383                                     playlist_item_t ***ppp_items )
384 {
385     int i_count = 0;
386     playlist_item_t *p_next = NULL;
387     while( 1 )
388     {
389         p_next = playlist_GetNextLeaf( p_playlist, p_node,
390                                        p_next, VLC_TRUE, VLC_FALSE );
391         if( p_next )
392             INSERT_ELEM( *ppp_items, i_count, i_count, p_next );
393         else
394             break;
395     }
396     return i_count;
397 }
398
399 /**
400  * Finds the next item to play
401  *
402  * \param p_playlist the playlist
403  * \param p_root the root node
404  * \param p_item the previous item  (NULL if none )
405  * \return the next item to play, or NULL if none found
406  */
407 playlist_item_t *playlist_GetNextLeaf( playlist_t *p_playlist,
408                                        playlist_item_t *p_root,
409                                        playlist_item_t *p_item,
410                                        vlc_bool_t b_ena, vlc_bool_t b_unplayed )
411 {
412     playlist_item_t *p_next;
413
414     assert( p_root && p_root->i_children != -1 );
415
416 #ifdef PLAYLIST_DEBUG
417     if( p_item != NULL )
418         msg_Dbg( p_playlist, "finding next of %s within %s",
419                         p_item->p_input->psz_name,  p_root->p_input->psz_name );
420     else
421         msg_Dbg( p_playlist, "finding something to play within %s",
422                          p_root->p_input->psz_name );
423 #endif
424
425     /* Now, walk the tree until we find a suitable next item */
426     p_next = p_item;
427     while( 1 )
428     {
429         vlc_bool_t b_ena_ok = VLC_TRUE, b_unplayed_ok = VLC_TRUE;
430         p_next = GetNextItem( p_playlist, p_root, p_next );
431         if( !p_next || p_next == p_root )
432             break;
433         if( p_next->i_children == -1 )
434         {
435             if( b_ena && p_next->i_flags & PLAYLIST_DBL_FLAG )
436                 b_ena_ok = VLC_FALSE;
437             if( b_unplayed && p_next->p_input->i_nb_played != 0 )
438                 b_unplayed_ok = VLC_FALSE;
439             if( b_ena_ok && b_unplayed_ok ) break;
440         }
441     }
442 #ifdef PLAYLIST_DEBUG
443     if( p_next == NULL )
444         msg_Dbg( p_playlist, "At end of node" );
445 #endif
446     return p_next;
447 }
448
449 /**
450  * Finds the previous item to play
451  *
452  * \param p_playlist the playlist
453  * \param p_root the root node
454  * \param p_item the previous item  (NULL if none )
455  * \return the next item to play, or NULL if none found
456  */
457 playlist_item_t *playlist_GetPrevLeaf( playlist_t *p_playlist,
458                                        playlist_item_t *p_root,
459                                        playlist_item_t *p_item,
460                                        vlc_bool_t b_ena, vlc_bool_t b_unplayed )
461 {
462     playlist_item_t *p_prev;
463
464 #ifdef PLAYLIST_DEBUG
465     if( p_item != NULL )
466         msg_Dbg( p_playlist, "finding previous of %s within %s",
467                         p_item->p_input->psz_name,  p_root->p_input->psz_name );
468     else
469         msg_Dbg( p_playlist, "finding previous to play within %s",
470                          p_root->p_input->psz_name );
471 #endif
472     assert( p_root && p_root->i_children != -1 );
473
474     /* Now, walk the tree until we find a suitable previous item */
475     p_prev = p_item;
476     while( 1 )
477     {
478         vlc_bool_t b_ena_ok = VLC_TRUE, b_unplayed_ok = VLC_TRUE;
479         p_prev = GetPrevItem( p_playlist, p_root, p_prev );
480         if( !p_prev || p_prev == p_root )
481             break;
482         if( p_prev->i_children == -1 )
483         {
484             if( b_ena && p_prev->i_flags & PLAYLIST_DBL_FLAG )
485                 b_ena_ok = VLC_FALSE;
486             if( b_unplayed && p_prev->p_input->i_nb_played != 0 )
487                 b_unplayed_ok = VLC_FALSE;
488             if( b_ena_ok && b_unplayed_ok ) break;
489         }
490     }
491 #ifdef PLAYLIST_DEBUG
492     if( p_prev == NULL )
493         msg_Dbg( p_playlist, "At beginning of node" );
494 #endif
495     return p_prev;
496 }
497
498 /************************************************************************
499  * Following functions are local
500  ***********************************************************************/
501
502 /**
503  * Get the next item in the tree
504  * If p_item is NULL, return the first child of root
505  **/
506 playlist_item_t *GetNextItem( playlist_t *p_playlist,
507                               playlist_item_t *p_root,
508                               playlist_item_t *p_item )
509 {
510     playlist_item_t *p_parent;
511     int i;
512
513     /* Node with children, get the first one */
514     if( p_item && p_item->i_children > 0 )
515         return p_item->pp_children[0];
516
517     if( p_item != NULL )
518         p_parent = p_item->p_parent;
519     else
520         p_parent = p_root;
521
522     for( i= 0 ; i < p_parent->i_children ; i++ )
523     {
524         if( p_item == NULL || p_parent->pp_children[i] == p_item )
525         {
526             if( p_item == NULL )
527                 i = -1;
528
529             if( i+1 >= p_parent->i_children )
530             {
531                 /* Was already the last sibling. Look for uncles */
532                 PL_DEBUG( "Current item is the last of the node,"
533                           "looking for uncle from %s",
534                            p_parent->p_input->psz_name );
535
536                 if( p_parent == p_root )
537                 {
538                     PL_DEBUG( "already at root" );
539                     return NULL;
540                 }
541                 return GetNextUncle( p_playlist, p_item, p_root );
542             }
543             else
544             {
545                 return  p_parent->pp_children[i+1];
546             }
547         }
548     }
549     msg_Err( p_playlist, "I should not be here" );
550     return NULL;
551 }
552
553 playlist_item_t *GetNextUncle( playlist_t *p_playlist, playlist_item_t *p_item,
554                                playlist_item_t *p_root )
555 {
556     playlist_item_t *p_parent = p_item->p_parent;
557     playlist_item_t *p_grandparent;
558     vlc_bool_t b_found = VLC_FALSE;
559
560     if( p_parent != NULL )
561     {
562         p_grandparent = p_parent->p_parent;
563         while( 1 )
564         {
565             int i;
566             for( i = 0 ; i< p_grandparent->i_children ; i++ )
567             {
568                 if( p_parent == p_grandparent->pp_children[i] )
569                 {
570                     PL_DEBUG( "parent %s found as child %i of grandparent %s",
571                               p_parent->p_input->psz_name, i,
572                               p_grandparent->p_input->psz_name );
573                     b_found = VLC_TRUE;
574                     break;
575                 }
576             }
577             if( b_found && i + 1 < p_grandparent->i_children )
578             {
579                     return p_grandparent->pp_children[i+1];
580             }
581             /* Not found at root */
582             if( p_grandparent == p_root )
583             {
584                 return NULL;
585             }
586             else
587             {
588                 p_parent = p_grandparent;
589                 p_grandparent = p_parent->p_parent;
590             }
591         }
592     }
593     /* We reached root */
594     return NULL;
595 }
596
597 playlist_item_t *GetPrevUncle( playlist_t *p_playlist, playlist_item_t *p_item,
598                                playlist_item_t *p_root )
599 {
600     playlist_item_t *p_parent = p_item->p_parent;
601     playlist_item_t *p_grandparent;
602     vlc_bool_t b_found = VLC_FALSE;
603
604     if( p_parent != NULL )
605     {
606         p_grandparent = p_parent->p_parent;
607         while( 1 )
608         {
609             int i;
610             for( i = p_grandparent->i_children -1 ; i >= 0; i-- )
611             {
612                 if( p_parent == p_grandparent->pp_children[i] )
613                 {
614                     b_found = VLC_TRUE;
615                     break;
616                 }
617             }
618             if( b_found && i - 1 > 0 )
619             {
620                 return p_grandparent->pp_children[i-1];
621             }
622             /* Not found at root */
623             if( p_grandparent == p_root )
624             {
625                 return NULL;
626             }
627             else
628             {
629                 p_parent = p_grandparent;
630                 p_grandparent = p_parent->p_parent;
631             }
632         }
633     }
634     /* We reached root */
635     return NULL;
636 }
637
638
639 /* Recursively search the tree for previous item */
640 playlist_item_t *GetPrevItem( playlist_t *p_playlist,
641                               playlist_item_t *p_root,
642                               playlist_item_t *p_item )
643 {
644     playlist_item_t *p_parent;
645     int i;
646
647     /* Node with children, get the last one */
648     if( p_item && p_item->i_children > 0 )
649         return p_item->pp_children[p_item->i_children - 1];
650
651     /* Last child of its parent ? */
652     if( p_item != NULL )
653         p_parent = p_item->p_parent;
654     else
655     {
656         msg_Err( p_playlist, "Get the last one" );
657         abort();
658     };
659
660     for( i = p_parent->i_children -1 ; i >= 0 ;  i-- )
661     {
662         if( p_parent->pp_children[i] == p_item )
663         {
664             if( i-1 < 0 )
665             {
666                /* Was already the first sibling. Look for uncles */
667                 PL_DEBUG( "current item is the first of its node,"
668                           "looking for uncle from %s",
669                           p_parent->p_input->psz_name );
670                 if( p_parent == p_root )
671                 {
672                     PL_DEBUG( "already at root" );
673                     return NULL;
674                 }
675                 return GetPrevUncle( p_playlist, p_item, p_root );
676             }
677             else
678             {
679                 return p_parent->pp_children[i-1];
680             }
681         }
682     }
683     msg_Err( p_playlist, "I should not be here" );
684     return NULL;
685 }
686
687 /* Dump the contents of a node */
688 void playlist_NodeDump( playlist_t *p_playlist, playlist_item_t *p_item,
689                         int i_level )
690 {
691     char str[512];
692     int i;
693
694     if( i_level == 1 )
695     {
696         msg_Dbg( p_playlist, "%s (%i)",
697                         p_item->p_input->psz_name, p_item->i_children );
698     }
699
700     if( p_item->i_children == -1 )
701     {
702         return;
703     }
704
705     for( i = 0; i< p_item->i_children; i++ )
706     {
707         memset( str, 32, 512 );
708         sprintf( str + 2 * i_level , "%s (%i)",
709                                 p_item->pp_children[i]->p_input->psz_name,
710                                 p_item->pp_children[i]->i_children );
711         msg_Dbg( p_playlist, "%s",str );
712         if( p_item->pp_children[i]->i_children >= 0 )
713         {
714             playlist_NodeDump( p_playlist, p_item->pp_children[i],
715                               i_level + 1 );
716         }
717     }
718     return;
719 }