1 /*****************************************************************************
2 * view.c : Playlist views functions
3 *****************************************************************************
4 * Copyright (C) 1999-2004 VideoLAN
5 * $Id: item.c 7997 2004-06-18 11:35:45Z sigmunau $
7 * Authors: Clément Stenac <zorglub@videolan.org>
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 *****************************************************************************/
23 #include <stdlib.h> /* free(), strtol() */
24 #include <stdio.h> /* sprintf() */
25 #include <string.h> /* strerror() */
28 #include <vlc/input.h>
30 #include "vlc_playlist.h"
34 /************************************************************************
36 ************************************************************************/
39 playlist_item_t *playlist_FindDirectParent( playlist_t *p_playlist,
40 playlist_item_t *, int );
42 playlist_item_t *playlist_RecursiveFindNext( playlist_t *p_playlist,
44 playlist_item_t *p_root,
45 playlist_item_t *p_item,
46 playlist_item_t *p_parent );
48 playlist_item_t *playlist_RecursiveFindPrev( playlist_t *p_playlist,
50 playlist_item_t *p_root,
51 playlist_item_t *p_item,
52 playlist_item_t *p_parent );
54 void playlist_NodeDump( playlist_t *p_playlist, playlist_item_t *p_item,
57 /**********************************************************************
58 * Exported View management functions
59 **********************************************************************/
64 * \param p_playlist a playlist object
65 * \param i_id the view identifier
66 * \return the new view or NULL on failure
68 playlist_view_t * playlist_ViewCreate( playlist_t *p_playlist, int i_id,
71 playlist_view_t * p_view;
73 msg_Dbg( p_playlist, "creating view %i",i_id );
75 p_view = malloc( sizeof( playlist_view_t ) );
77 memset( p_view, 0, sizeof( playlist_view_t ) );
79 p_view->p_root = playlist_NodeCreate( p_playlist, i_id, NULL, NULL );
81 p_view->psz_name = psz_name ? strdup( psz_name ) : strdup(_("Undefined") );
87 * Creates a new view and add it to the list
89 * This function must be entered without the playlist lock
91 * \param p_playlist a playlist object
92 * \param i_id the view identifier
93 * \return VLC_SUCCESS or an error
95 int playlist_ViewInsert( playlist_t *p_playlist, int i_id, char *psz_name )
97 playlist_view_t *p_view =
98 playlist_ViewCreate( p_playlist, i_id , psz_name );
101 msg_Err( p_playlist, "Creation failed" );
105 vlc_mutex_lock( &p_playlist->object_lock );
107 INSERT_ELEM( p_playlist->pp_views, p_playlist->i_views,
108 p_playlist->i_views, p_view );
110 vlc_mutex_unlock( &p_playlist->object_lock );
118 * This function must be entered wit the playlist lock
120 * \param p_view the view to delete
123 int playlist_ViewDelete( playlist_t *p_playlist,playlist_view_t *p_view )
125 REMOVE_ELEM( p_playlist->pp_views, p_playlist->i_views, 0 );
130 * Dumps the content of a view
132 * \param p_playlist the playlist
133 * \param p_view the view to dump
136 int playlist_ViewDump( playlist_t *p_playlist, playlist_view_t *p_view )
138 msg_Dbg( p_playlist, "dumping view %i",p_view->i_id );
139 playlist_NodeDump( p_playlist,p_view->p_root, 1 );
144 * Counts the items of a view
146 * \param p_playlist the playlist
147 * \param p_view the view to count
148 * \return the number of items
150 int playlist_ViewItemCount( playlist_t *p_playlist,
151 playlist_view_t *p_view )
153 return playlist_NodeChildrenCount( p_playlist, p_view->p_root );
158 * Updates a view. Only make sense for "sorted" and "ALL" views
160 * \param p_playlist the playlist
161 * \param i_view the view to update
164 int playlist_ViewUpdate( playlist_t *p_playlist, int i_view)
166 playlist_view_t *p_view = playlist_ViewFind( p_playlist, i_view );
173 if( i_view == VIEW_ALL )
175 p_view->p_root->i_children = p_playlist->i_size;
176 p_view->p_root->pp_children = p_playlist->pp_items;
179 /* Handle update of sorted views here */
180 if( i_view == VIEW_S_AUTHOR )
182 playlist_ViewEmpty( p_playlist, i_view, VLC_FALSE );
184 playlist_NodeGroup( p_playlist, i_view, p_view->p_root,
185 p_playlist->pp_items,p_playlist->i_size,
186 SORT_AUTHOR, ORDER_NORMAL );
197 * \param p_playlist the playlist
198 * \param i_id the id to find
199 * \return the found view or NULL if not found
201 playlist_view_t *playlist_ViewFind( playlist_t *p_playlist, int i_id )
204 for( i=0 ; i< p_playlist->i_views ; i++ )
206 if( p_playlist->pp_views[i]->i_id == i_id )
208 return p_playlist->pp_views[i];
215 int playlist_ViewEmpty( playlist_t *p_playlist, int i_view,
216 vlc_bool_t b_delete_items )
218 playlist_view_t *p_view = playlist_ViewFind( p_playlist, i_view );
225 return playlist_NodeEmpty( p_playlist, p_view->p_root, b_delete_items );
228 /**********************************************************************
229 * Exported Nodes management functions
230 **********************************************************************/
235 * Create a playlist node
237 * \param p_playlist the playlist
238 * \paam psz_name the name of the node
239 * \param p_parent the parent node to attach to or NULL if no attach
240 * \return the new node
242 playlist_item_t * playlist_NodeCreate( playlist_t *p_playlist, int i_view,
244 playlist_item_t *p_parent )
246 /* Create the item */
247 playlist_item_t *p_item = (playlist_item_t *)malloc(
248 sizeof( playlist_item_t ) );
250 playlist_add_t *p_add = (playlist_add_t*)malloc( sizeof(playlist_add_t));
252 vlc_input_item_Init( VLC_OBJECT(p_playlist), &p_item->input );
258 if( psz_name != NULL )
260 p_item->input.psz_name = strdup( psz_name );
264 p_item->input.psz_name = strdup( _("Undefined") );
267 p_item->input.psz_uri = NULL;
269 p_item->b_enabled = VLC_TRUE;
270 p_item->i_nb_played = 0;
274 p_item->i_children = 0;
275 p_item->pp_children = NULL;
277 p_item->input.i_duration = -1;
278 p_item->input.ppsz_options = NULL;
279 p_item->input.i_options = 0;
280 p_item->input.i_categories = 0;
281 p_item->input.pp_categories = NULL;
282 p_item->input.i_id = ++p_playlist->i_last_id;
284 p_item->pp_parents = NULL;
285 p_item->i_parents = 0;
287 p_item->i_flags |= PLAYLIST_SKIP_FLAG; /* Default behaviour */
289 vlc_mutex_init( p_playlist, &p_item->input.lock );
291 if( p_parent != NULL )
293 playlist_NodeAppend( p_playlist, i_view, p_item, p_parent );
296 p_add->p_node = p_parent;
297 p_add->p_item = p_item;
298 p_add->i_view = i_view;
299 val.p_address = p_add;
300 var_Set( p_playlist, "item-append", val);
308 * Remove all the children of a node
310 * \param p_playlist the playlist
311 * \param p_root the node
312 * \param b_delete_items do we have to delete the children items ?
313 * \return VLC_SUCCESS or an error
315 int playlist_NodeEmpty( playlist_t *p_playlist, playlist_item_t *p_root,
316 vlc_bool_t b_delete_items )
319 if( p_root->i_children == -1 )
324 /* Delete the children */
325 for( i = p_root->i_children-1 ; i >= 0 ;i-- )
327 if( p_root->pp_children[i]->i_children > -1 )
329 playlist_NodeDelete( p_playlist, p_root->pp_children[i],
332 else if( b_delete_items )
334 /* Delete the item here */
335 playlist_Delete( p_playlist, p_root->pp_children[i]->input.i_id );
342 * Remove all the children of a node and removes the node
344 * \param p_playlist the playlist
345 * \param p_root the node
346 * \param b_delete_items do we have to delete the children items ?
347 * \return VLC_SUCCESS or an error
349 int playlist_NodeDelete( playlist_t *p_playlist, playlist_item_t *p_root,
350 vlc_bool_t b_delete_items )
353 if( p_root->i_children == -1 )
358 /* Delete the children */
359 for( i = p_root->i_children - 1 ; i >= 0; i-- )
361 if( p_root->pp_children[i]->i_children > -1 )
363 playlist_NodeDelete( p_playlist, p_root->pp_children[i],
366 else if( b_delete_items )
368 /* Delete the item here */
369 playlist_Delete( p_playlist, p_root->pp_children[i]->input.i_id );
372 /* Delete the node */
373 if( p_root->i_flags & PLAYLIST_RO_FLAG )
375 msg_Dbg( p_playlist, "unable to remove node, write-protected" );
379 for( i = 0 ; i< p_root->i_parents; i++ )
381 playlist_NodeRemoveItem( p_playlist, p_root,
382 p_root->pp_parents[i]->p_parent );
384 var_SetInteger( p_playlist, "item-deleted", p_root->input.i_id );
385 playlist_ItemDelete( p_root );
393 * Adds an item to the childs of a node
395 * \param p_playlist the playlist
396 * \param i_view the view of the node ( needed for parent search )
397 * \param p_item the item to append
398 * \param p_parent the parent node
399 * \return VLC_SUCCESS or an error
401 int playlist_NodeAppend( playlist_t *p_playlist,
403 playlist_item_t *p_item,
404 playlist_item_t *p_parent )
406 return playlist_NodeInsert( p_playlist, i_view, p_item, p_parent, -1 );
409 int playlist_NodeInsert( playlist_t *p_playlist,
411 playlist_item_t *p_item,
412 playlist_item_t *p_parent,
416 vlc_bool_t b_found = VLC_FALSE;
417 if( !p_parent || p_parent->i_children == -1 )
419 msg_Err( p_playlist, "invalid node" );
423 if( i_position == -1 ) i_position = p_parent->i_children ;
425 INSERT_ELEM( p_parent->pp_children,
426 p_parent->i_children,
430 /* Add the parent to the array */
431 for( i= 0; i< p_item->i_parents ; i++ )
433 if( p_item->pp_parents[i]->i_view == i_view )
439 if( b_found == VLC_FALSE )
441 struct item_parent_t *p_ip = (struct item_parent_t *)
442 malloc(sizeof(struct item_parent_t) );
443 p_ip->i_view = i_view;
444 p_ip->p_parent = p_parent;
446 INSERT_ELEM( p_item->pp_parents,
447 p_item->i_parents, p_item->i_parents,
451 /* Let the interface know this has been updated */
452 p_parent->i_serial++;
457 * Deletes an item from the children of a node
459 * \param p_playlist the playlist
460 * \param p_item the item to remove
461 * \param p_parent the parent node
462 * \return VLC_SUCCESS or an error
464 int playlist_NodeRemoveItem( playlist_t *p_playlist,
465 playlist_item_t *p_item,
466 playlist_item_t *p_parent )
469 if( !p_parent || p_parent->i_children == -1 )
471 msg_Err( p_playlist, "invalid node" );
474 for( i= 0; i< p_parent->i_children ; i++ )
476 if( p_parent->pp_children[i] == p_item )
478 REMOVE_ELEM( p_parent->pp_children, p_parent->i_children, i );
482 /* Let the interface know this has been updated */
483 p_parent->i_serial++;
490 * Count the children of a node
492 * \param p_playlist the playlist
493 * \param p_node the node
494 * \return the number of children
496 int playlist_NodeChildrenCount( playlist_t *p_playlist, playlist_item_t*p_node)
500 if( p_node->i_children == -1 )
505 for( i=0 ; i< p_node->i_children;i++ )
507 if( p_node->pp_children[i]->i_children == -1 )
513 i_nb += playlist_NodeChildrenCount( p_playlist,
514 p_node->pp_children[i] );
521 * Search a child of a node by its name
523 * \param p_node the node
524 * \param psz_search the name of the child to search
525 * \return the child item or NULL if not found or error
527 playlist_item_t *playlist_ChildSearchName( playlist_item_t *p_node,
528 const char *psz_search )
532 if( p_node->i_children < 0 )
536 for( i = 0 ; i< p_node->i_children; i++ )
538 if( !strcmp( p_node->pp_children[i]->input.psz_name, psz_search ) )
540 return p_node->pp_children[i];
547 /**********************************************************************
549 **********************************************************************/
552 * Finds the next item to play
554 * \param p_playlist the playlist
555 * \param i_view the view
556 * \param p_root the root node
557 * \param p_node the node we are playing from
558 * \param p_item the item we were playing (NULL if none )
559 * \return the next item to play, or NULL if none found
561 playlist_item_t *playlist_FindNextFromParent( playlist_t *p_playlist,
562 int i_view, /* FIXME: useless */
563 playlist_item_t *p_root,
564 playlist_item_t *p_node,
565 playlist_item_t *p_item )
567 playlist_item_t *p_search, *p_next;
569 #ifdef PLAYLIST_DEBUG
572 msg_Dbg( p_playlist, "finding next of %s within %s",
573 p_item->input.psz_name, p_node->input.psz_name );
577 msg_Dbg( p_playlist, "finding something to play within %s",
578 p_node->input.psz_name );
583 if( !p_node || p_node->i_children == -1 )
585 msg_Err( p_playlist,"invalid arguments for FindNextFromParent" );
589 /* Find the parent node of the item */
592 p_search = playlist_FindDirectParent( p_playlist, p_item, i_view );
593 if( p_search == NULL )
595 msg_Err( p_playlist, "parent node not found" );
604 /* Now, go up the tree until we find a suitable next item */
605 p_next = playlist_RecursiveFindNext( p_playlist,i_view,
606 p_node, p_item, p_search );
608 /* Not found, do we go past p_node ? */
611 if( p_playlist->b_go_next )
613 p_next = playlist_RecursiveFindNext( p_playlist, i_view,
614 p_root, p_item, p_search );
619 /* OK, we could continue, so set our current node to the root */
620 p_playlist->status.p_node = p_root;
631 * Finds the previous item to play
633 * \param p_playlist the playlist
634 * \param i_view the view
635 * \param p_root the root node
636 * \param p_node the node we are playing from
637 * \param p_item the item we were playing (NULL if none )
638 * \return the next item to play, or NULL if none found
640 playlist_item_t *playlist_FindPrevFromParent( playlist_t *p_playlist,
642 playlist_item_t *p_root,
643 playlist_item_t *p_node,
644 playlist_item_t *p_item )
646 playlist_item_t *p_search, *p_next;
648 #ifdef PLAYLIST_DEBUG
651 msg_Dbg( p_playlist, "Finding prev of %s within %s",
652 p_item->input.psz_name, p_node->input.psz_name );
656 msg_Dbg( p_playlist, "Finding prev from %s",p_node->input.psz_name );
660 if( !p_node || p_node->i_children == -1 )
662 msg_Err( p_playlist,"invalid arguments for FindPrevFromParent" );
666 /* Find the parent node of the item */
669 p_search = playlist_FindDirectParent( p_playlist, p_item, i_view );
670 if( p_search == NULL )
672 msg_Err( p_playlist, "parent node not found" );
681 /* Now, go up the tree until we find a suitable next item */
682 p_next = playlist_RecursiveFindPrev( p_playlist,i_view,
683 p_node, p_item, p_search );
687 if( p_playlist->b_go_next )
689 p_next = playlist_RecursiveFindPrev( p_playlist, i_view,
690 p_root, p_item, p_search );
695 /* OK, we could continue, so set our current node to the root */
696 p_playlist->status.p_node = p_root;
706 /************************************************************************
707 * Following functions are local
708 ***********************************************************************/
711 /* Recursively search the tree for next item */
712 playlist_item_t *playlist_RecursiveFindNext( playlist_t *p_playlist,
714 playlist_item_t *p_root,
715 playlist_item_t *p_item,
716 playlist_item_t *p_parent )
719 playlist_item_t *p_parent_parent;
721 for( i= 0 ; i < p_parent->i_children ; i++ )
723 if( p_parent->pp_children[i] == p_item || p_item == NULL )
729 #ifdef PLAYLIST_DEBUG
730 msg_Dbg( p_playlist,"Current item found, child %i of %s",
731 i , p_parent->input.psz_name );
733 /* We found our item */
734 if( i+1 >= p_parent->i_children )
737 #ifdef PLAYLIST_DEBUG
738 msg_Dbg( p_playlist, "Going up the tree,at parent of %s",
739 p_parent->input.psz_name );
741 if( p_parent == p_root )
743 /* Hmm, seems it's the end for you, guy ! */
747 /* Go up one level */
748 p_parent_parent = playlist_FindDirectParent( p_playlist,
750 if( p_parent_parent == NULL )
752 msg_Warn( p_playlist, "Unable to find parent !");
755 return playlist_RecursiveFindNext( p_playlist, i_view,p_root,
756 p_parent, p_parent_parent );
760 if( p_parent->pp_children[i+1]->i_children == -1 )
762 /* Cool, we have found a real item to play */
763 #ifdef PLAYLIST_DEBUG
764 msg_Dbg( p_playlist, "Playing child %i of %s",
765 i+1 , p_parent->input.psz_name );
767 return p_parent->pp_children[i+1];
769 else if( p_parent->pp_children[i+1]->i_children > 0 )
771 /* Select the first child of this node */
772 #ifdef PLAYLIST_DEBUG
773 msg_Dbg( p_playlist, "%s is a node with children, "
775 p_parent->pp_children[i+1]->input.psz_name);
777 if( p_parent->pp_children[i+1]->pp_children[0]
780 /* first child is a node ! */
781 return playlist_RecursiveFindNext( p_playlist, i_view,
783 p_parent->pp_children[i+1]->pp_children[0]);
785 return p_parent->pp_children[i+1]->pp_children[0];
789 /* This node has no child... We must continue */
790 #ifdef PLAYLIST_DEBUG
791 msg_Dbg( p_playlist, "%s is a node with no children",
792 p_parent->pp_children[i+1]->input.psz_name);
794 p_item = p_parent->pp_children[i+1];
799 /* Just in case :) */
803 /* Recursively search the tree for previous item */
804 playlist_item_t *playlist_RecursiveFindPrev( playlist_t *p_playlist,
806 playlist_item_t *p_root,
807 playlist_item_t *p_item,
808 playlist_item_t *p_parent )
811 playlist_item_t *p_parent_parent;
813 for( i= p_parent->i_children - 1 ; i >= 0 ; i-- )
815 if( p_parent->pp_children[i] == p_item || p_item == NULL )
821 #ifdef PLAYLIST_DEBUG
822 msg_Dbg( p_playlist,"Current item found, child %i of %s",
823 i , p_parent->input.psz_name );
825 /* We found our item */
829 #ifdef PLAYLIST_DEBUG
830 msg_Dbg( p_playlist, "Going up the tree,at parent of %s",
831 p_parent->input.psz_name );
833 if( p_parent == p_root )
835 /* Hmm, seems it's the end for you, guy ! */
838 /* Go up one level */
839 p_parent_parent = playlist_FindDirectParent( p_playlist,
841 return playlist_RecursiveFindPrev( p_playlist, i_view,p_root,
842 p_parent, p_parent_parent );
846 if( p_parent->pp_children[i-1]->i_children == -1 )
848 /* Cool, we have found a real item to play */
849 #ifdef PLAYLIST_DEBUG
850 msg_Dbg( p_playlist, "Playing child %i of %s",
851 i-1, p_parent->input.psz_name );
853 return p_parent->pp_children[i-1];
855 else if( p_parent->pp_children[i-1]->i_children > 0 )
857 /* Select the last child of this node */
858 #ifdef PLAYLIST_DEBUG
859 msg_Dbg( p_playlist, "%s is a node with children,"
861 p_parent->pp_children[i-1]->input.psz_name);
863 if( p_parent->pp_children[i-1]->pp_children[p_parent->
864 pp_children[i-1]->i_children-1]->i_children >= 0 )
866 /* Last child is a node */
867 return playlist_RecursiveFindPrev( p_playlist, i_view,
869 p_parent->pp_children[i-1]->pp_children[
870 p_parent->pp_children[i-1]->i_children-1]);
872 return p_parent->pp_children[i-1]->pp_children[
873 p_parent->pp_children[i-1]->i_children-1];
877 /* This node has no child... We must continue */
878 #ifdef PLAYLIST_DEBUG
879 msg_Dbg( p_playlist, "%s is a node with no children",
880 p_parent->pp_children[i-1]->input.psz_name);
882 p_item = p_parent->pp_children[i-1];
890 /* This function returns the parent of an item in a view */
891 playlist_item_t *playlist_FindDirectParent( playlist_t *p_playlist,
892 playlist_item_t *p_item,
896 for( i= 0; i< p_item->i_parents ; i++ )
898 if( p_item->pp_parents[i]->i_view == i_view )
900 return p_item->pp_parents[i]->p_parent;
907 /* This function dumps a node */
908 void playlist_NodeDump( playlist_t *p_playlist, playlist_item_t *p_item,
916 msg_Dbg( p_playlist, "%s (%i)",
917 p_item->input.psz_name, p_item->i_children );
920 if( p_item->i_children == -1 )
925 for( i = 0; i< p_item->i_children; i++ )
927 memset( str, 32, 512 );
928 sprintf( str + 2 * i_level , "%s (%i)",
929 p_item->pp_children[i]->input.psz_name,
930 p_item->pp_children[i]->i_children );
931 msg_Dbg( p_playlist, "%s",str );
932 if( p_item->pp_children[i]->i_children >= 0 )
934 playlist_NodeDump( p_playlist, p_item->pp_children[i],