]> git.sesse.net Git - vlc/blob - src/playlist/tree.c
Split playlist include file in public/private
[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 void playlist_NodesCreateForSD( playlist_t *p_playlist, char *psz_name,
292                                 playlist_item_t **pp_node_cat,
293                                 playlist_item_t **pp_node_one )
294 {
295     *pp_node_cat = playlist_NodeCreate( p_playlist, psz_name,
296                                         p_playlist->p_root_category );
297     (*pp_node_cat)->i_flags |= PLAYLIST_RO_FLAG;
298     (*pp_node_cat)->i_flags |= PLAYLIST_SKIP_FLAG;
299
300     *pp_node_one = playlist_NodeCreate( p_playlist, psz_name,
301                                         p_playlist->p_root_onelevel );
302     (*pp_node_one)->i_flags |= PLAYLIST_RO_FLAG;
303     (*pp_node_one)->i_flags |= PLAYLIST_SKIP_FLAG;
304
305     (*pp_node_one)->p_input->i_id = (*pp_node_cat)->p_input->i_id;
306 }
307
308 playlist_item_t * playlist_GetPreferredNode( playlist_t *p_playlist,
309                                              playlist_item_t *p_node )
310 {
311     int i;
312     if( p_node->p_parent == p_playlist->p_root_category )
313     {
314         if( p_playlist->b_always_tree ||
315             p_node->p_input->b_prefers_tree ) return p_node;
316         for( i = 0 ; i< p_playlist->p_root_onelevel->i_children; i++ )
317         {
318             if( p_playlist->p_root_onelevel->pp_children[i]->p_input->i_id ==
319                     p_node->p_input->i_id )
320                 return p_playlist->p_root_onelevel->pp_children[i];
321         }
322     }
323     else if( p_node->p_parent == p_playlist->p_root_onelevel )
324     {
325         if( p_playlist->b_never_tree || !p_node->p_input->b_prefers_tree )
326             return p_node;
327         for( i = 0 ; i< p_playlist->p_root_category->i_children; i++ )
328         {
329             if( p_playlist->p_root_category->pp_children[i]->p_input->i_id ==
330                     p_node->p_input->i_id )
331                 return p_playlist->p_root_category->pp_children[i];
332         }
333     }
334     return NULL;
335 }
336
337 /**********************************************************************
338  * Tree walking functions
339  **********************************************************************/
340
341 playlist_item_t *playlist_GetLastLeaf(playlist_t *p_playlist,
342                                       playlist_item_t *p_root )
343 {
344     int i;
345     playlist_item_t *p_item;
346     for ( i = p_root->i_children - 1; i >= 0; i-- )
347     {
348         if( p_root->pp_children[i]->i_children == -1 )
349             return p_root->pp_children[i];
350         else if( p_root->pp_children[i]->i_children > 0)
351         {
352              p_item = playlist_GetLastLeaf( p_playlist,
353                                             p_root->pp_children[i] );
354             if ( p_item != NULL )
355                 return p_item;
356         }
357         else if( i == 0 )
358             return NULL;
359     }
360     return NULL;
361 }
362
363 int playlist_GetAllEnabledChildren( playlist_t *p_playlist,
364                                     playlist_item_t *p_node,
365                                     playlist_item_t ***ppp_items )
366 {
367     int i_count = 0;
368     playlist_item_t *p_next = NULL;
369     while( 1 )
370     {
371         p_next = playlist_GetNextLeaf( p_playlist, p_node,
372                                        p_next, VLC_TRUE, VLC_FALSE );
373         if( p_next )
374             INSERT_ELEM( *ppp_items, i_count, i_count, p_next );
375         else
376             break;
377     }
378     return i_count;
379 }
380
381 /**
382  * Finds the next item to play
383  *
384  * \param p_playlist the playlist
385  * \param p_root the root node
386  * \param p_item the previous item  (NULL if none )
387  * \return the next item to play, or NULL if none found
388  */
389 playlist_item_t *playlist_GetNextLeaf( playlist_t *p_playlist,
390                                        playlist_item_t *p_root,
391                                        playlist_item_t *p_item,
392                                        vlc_bool_t b_ena, vlc_bool_t b_unplayed )
393 {
394     playlist_item_t *p_next;
395
396     assert( p_root && p_root->i_children != -1 );
397
398 #ifdef PLAYLIST_DEBUG
399     if( p_item != NULL )
400         msg_Dbg( p_playlist, "finding next of %s within %s",
401                         p_item->p_input->psz_name,  p_root->p_input->psz_name );
402     else
403         msg_Dbg( p_playlist, "finding something to play within %s",
404                          p_root->p_input->psz_name );
405 #endif
406
407     /* Now, walk the tree until we find a suitable next item */
408     p_next = p_item;
409     while( 1 )
410     {
411         vlc_bool_t b_ena_ok = VLC_TRUE, b_unplayed_ok = VLC_TRUE;
412         p_next = GetNextItem( p_playlist, p_root, p_next );
413         if( !p_next || p_next == p_root )
414             break;
415         if( p_next->i_children == -1 )
416         {
417             if( b_ena && p_next->i_flags & PLAYLIST_DBL_FLAG )
418                 b_ena_ok = VLC_FALSE;
419             if( b_unplayed && p_next->p_input->i_nb_played != 0 )
420                 b_unplayed_ok = VLC_FALSE;
421             if( b_ena_ok && b_unplayed_ok ) break;
422         }
423     }
424 #ifdef PLAYLIST_DEBUG
425     if( p_next == NULL )
426         msg_Dbg( p_playlist, "At end of node" );
427 #endif
428     return p_next;
429 }
430
431 /**
432  * Finds the previous item to play
433  *
434  * \param p_playlist the playlist
435  * \param p_root the root node
436  * \param p_item the previous item  (NULL if none )
437  * \return the next item to play, or NULL if none found
438  */
439 playlist_item_t *playlist_GetPrevLeaf( playlist_t *p_playlist,
440                                        playlist_item_t *p_root,
441                                        playlist_item_t *p_item,
442                                        vlc_bool_t b_ena, vlc_bool_t b_unplayed )
443 {
444     playlist_item_t *p_prev;
445
446 #ifdef PLAYLIST_DEBUG
447     if( p_item != NULL )
448         msg_Dbg( p_playlist, "finding previous of %s within %s",
449                         p_item->p_input->psz_name,  p_root->p_input->psz_name );
450     else
451         msg_Dbg( p_playlist, "finding previous to play within %s",
452                          p_root->p_input->psz_name );
453 #endif
454     assert( p_root && p_root->i_children != -1 );
455
456     /* Now, walk the tree until we find a suitable previous item */
457     p_prev = p_item;
458     while( 1 )
459     {
460         vlc_bool_t b_ena_ok = VLC_TRUE, b_unplayed_ok = VLC_TRUE;
461         p_prev = GetPrevItem( p_playlist, p_root, p_prev );
462         if( !p_prev || p_prev == p_root )
463             break;
464         if( p_prev->i_children == -1 )
465         {
466             if( b_ena && p_prev->i_flags & PLAYLIST_DBL_FLAG )
467                 b_ena_ok = VLC_FALSE;
468             if( b_unplayed && p_prev->p_input->i_nb_played != 0 )
469                 b_unplayed_ok = VLC_FALSE;
470             if( b_ena_ok && b_unplayed_ok ) break;
471         }
472     }
473 #ifdef PLAYLIST_DEBUG
474     if( p_prev == NULL )
475         msg_Dbg( p_playlist, "At beginning of node" );
476 #endif
477     return p_prev;
478 }
479
480 /************************************************************************
481  * Following functions are local
482  ***********************************************************************/
483
484 /**
485  * Get the next item in the tree
486  * If p_item is NULL, return the first child of root
487  **/
488 playlist_item_t *GetNextItem( playlist_t *p_playlist,
489                               playlist_item_t *p_root,
490                               playlist_item_t *p_item )
491 {
492     playlist_item_t *p_parent;
493     int i;
494
495     /* Node with children, get the first one */
496     if( p_item && p_item->i_children > 0 )
497         return p_item->pp_children[0];
498
499     if( p_item != NULL )
500         p_parent = p_item->p_parent;
501     else
502         p_parent = p_root;
503
504     for( i= 0 ; i < p_parent->i_children ; i++ )
505     {
506         if( p_item == NULL || p_parent->pp_children[i] == p_item )
507         {
508             if( p_item == NULL )
509                 i = -1;
510
511             if( i+1 >= p_parent->i_children )
512             {
513                 /* Was already the last sibling. Look for uncles */
514                 PL_DEBUG( "Current item is the last of the node,"
515                           "looking for uncle from %s",
516                            p_parent->p_input->psz_name );
517
518                 if( p_parent == p_root )
519                 {
520                     PL_DEBUG( "already at root" );
521                     return NULL;
522                 }
523                 return GetNextUncle( p_playlist, p_item, p_root );
524             }
525             else
526             {
527                 return  p_parent->pp_children[i+1];
528             }
529         }
530     }
531     msg_Err( p_playlist, "I should not be here" );
532     return NULL;
533 }
534
535 playlist_item_t *GetNextUncle( playlist_t *p_playlist, playlist_item_t *p_item,
536                                playlist_item_t *p_root )
537 {
538     playlist_item_t *p_parent = p_item->p_parent;
539     playlist_item_t *p_grandparent;
540     vlc_bool_t b_found = VLC_FALSE;
541
542     if( p_parent != NULL )
543     {
544         p_grandparent = p_parent->p_parent;
545         while( 1 )
546         {
547             int i;
548             for( i = 0 ; i< p_grandparent->i_children ; i++ )
549             {
550                 if( p_parent == p_grandparent->pp_children[i] )
551                 {
552                     PL_DEBUG( "parent %s found as child %i of grandparent %s",
553                               p_parent->p_input->psz_name, i,
554                               p_grandparent->p_input->psz_name );
555                     b_found = VLC_TRUE;
556                     break;
557                 }
558             }
559             if( b_found && i + 1 < p_grandparent->i_children )
560             {
561                     return p_grandparent->pp_children[i+1];
562             }
563             /* Not found at root */
564             if( p_grandparent == p_root )
565             {
566                 return NULL;
567             }
568             else
569             {
570                 p_parent = p_grandparent;
571                 p_grandparent = p_parent->p_parent;
572             }
573         }
574     }
575     /* We reached root */
576     return NULL;
577 }
578
579 playlist_item_t *GetPrevUncle( playlist_t *p_playlist, playlist_item_t *p_item,
580                                playlist_item_t *p_root )
581 {
582     playlist_item_t *p_parent = p_item->p_parent;
583     playlist_item_t *p_grandparent;
584     vlc_bool_t b_found = VLC_FALSE;
585
586     if( p_parent != NULL )
587     {
588         p_grandparent = p_parent->p_parent;
589         while( 1 )
590         {
591             int i;
592             for( i = p_grandparent->i_children -1 ; i >= 0; i-- )
593             {
594                 if( p_parent == p_grandparent->pp_children[i] )
595                 {
596                     b_found = VLC_TRUE;
597                     break;
598                 }
599             }
600             if( b_found && i - 1 > 0 )
601             {
602                 return p_grandparent->pp_children[i-1];
603             }
604             /* Not found at root */
605             if( p_grandparent == p_root )
606             {
607                 return NULL;
608             }
609             else
610             {
611                 p_parent = p_grandparent;
612                 p_grandparent = p_parent->p_parent;
613             }
614         }
615     }
616     /* We reached root */
617     return NULL;
618 }
619
620
621 /* Recursively search the tree for previous item */
622 playlist_item_t *GetPrevItem( playlist_t *p_playlist,
623                               playlist_item_t *p_root,
624                               playlist_item_t *p_item )
625 {
626     playlist_item_t *p_parent;
627     int i;
628
629     /* Node with children, get the last one */
630     if( p_item && p_item->i_children > 0 )
631         return p_item->pp_children[p_item->i_children - 1];
632
633     /* Last child of its parent ? */
634     if( p_item != NULL )
635         p_parent = p_item->p_parent;
636     else
637     {
638         msg_Err( p_playlist, "Get the last one" );
639         abort();
640     };
641
642     for( i = p_parent->i_children -1 ; i >= 0 ;  i-- )
643     {
644         if( p_parent->pp_children[i] == p_item )
645         {
646             if( i-1 < 0 )
647             {
648                 /* Was already the first sibling. Look for uncles */
649                 PL_DEBUG( "Current item is the first of the node,"
650                           "looking for uncle from %s",
651                           p_parent->p_input->psz_name );
652                 return GetPrevUncle( p_playlist, p_item, p_root );
653             }
654             else
655             {
656                 return p_parent->pp_children[i-1];
657             }
658         }
659     }
660     msg_Err( p_playlist, "I should not be here" );
661     return NULL;
662 }
663
664 /* Dump the contents of a node */
665 void playlist_NodeDump( playlist_t *p_playlist, playlist_item_t *p_item,
666                         int i_level )
667 {
668     char str[512];
669     int i;
670
671     if( i_level == 1 )
672     {
673         msg_Dbg( p_playlist, "%s (%i)",
674                         p_item->p_input->psz_name, p_item->i_children );
675     }
676
677     if( p_item->i_children == -1 )
678     {
679         return;
680     }
681
682     for( i = 0; i< p_item->i_children; i++ )
683     {
684         memset( str, 32, 512 );
685         sprintf( str + 2 * i_level , "%s (%i)",
686                                 p_item->pp_children[i]->p_input->psz_name,
687                                 p_item->pp_children[i]->i_children );
688         msg_Dbg( p_playlist, "%s",str );
689         if( p_item->pp_children[i]->i_children >= 0 )
690         {
691             playlist_NodeDump( p_playlist, p_item->pp_children[i],
692                               i_level + 1 );
693         }
694     }
695     return;
696 }