item.c 32.7 KB
Newer Older
1
/*****************************************************************************
2
 * item.c : Playlist item creation/deletion/add/removal functions
3
 *****************************************************************************
4
 * Copyright (C) 1999-2007 the VideoLAN team
Clément Stenac's avatar
Clément Stenac committed
5
 * $Id$
6 7
 *
 * Authors: Samuel Hocevar <sam@zoy.org>
8
 *          Clément Stenac <zorglub@videolan.org>
9 10 11 12 13 14 15 16 17 18 19 20 21
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
Antoine Cellerier's avatar
Antoine Cellerier committed
22
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23
 *****************************************************************************/
24 25 26 27
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

28
#include <vlc_common.h>
Clément Stenac's avatar
Clément Stenac committed
29
#include <assert.h>
30 31
#include <vlc_playlist.h>
#include "playlist_internal.h"
32

33 34 35 36 37 38
static void AddItem( playlist_t *p_playlist, playlist_item_t *p_item,
                     playlist_item_t *p_node, int i_mode, int i_pos );
static void GoAndPreparse( playlist_t *p_playlist, int i_mode,
                           playlist_item_t *, playlist_item_t * );
static void ChangeToNode( playlist_t *p_playlist, playlist_item_t *p_item );
static int DeleteInner( playlist_t * p_playlist, playlist_item_t *p_item,
39
                        bool b_stop );
40

41 42 43 44 45 46
/*****************************************************************************
 * An input item has gained a subitem (Event Callback)
 *****************************************************************************/
static void input_item_subitem_added( const vlc_event_t * p_event,
                                      void * user_data )
{
47 48
    playlist_item_t *p_parent_playlist_item = user_data;
    playlist_t * p_playlist = p_parent_playlist_item->p_playlist;
49
    input_item_t * p_parent, * p_child;
50 51
    playlist_item_t * p_child_in_category;
    playlist_item_t * p_item_in_category;
52
    bool b_play;
53 54 55 56

    p_parent = p_event->p_obj;
    p_child = p_event->u.input_item_subitem_added.p_new_child;

57 58
    PL_LOCK;
    b_play = var_CreateGetBool( p_playlist, "playlist-autostart" );
59

60
    /* This part is really hakish, but this playlist system isn't simple */
61 62
    /* First check if we haven't already added the item as we are
     * listening using the onelevel and the category representent
63 64 65 66
     * (Because of the playlist design) */
    p_child_in_category = playlist_ItemFindFromInputAndRoot(
                            p_playlist, p_child->i_id,
                            p_playlist->p_root_category,
67
                            false /* Only non-node */ );
68

69
    if( !p_child_in_category )
70
    {
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
71
        /* Then, transform to a node if needed */
72 73 74
        p_item_in_category = playlist_ItemFindFromInputAndRoot(
                                p_playlist, p_parent->i_id,
                                p_playlist->p_root_category,
75
                                false /* Only non-node */ );
76 77 78 79
        if( !p_item_in_category )
        {
            /* Item may have been removed */
            PL_UNLOCK;
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
80
            return;
81 82
        }

Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
83 84
        b_play = b_play && p_item_in_category == p_playlist->status.p_item;

Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
85 86 87 88
        /* If this item is already a node don't transform it */
        if( p_item_in_category->i_children == -1 )
        {
            p_item_in_category = playlist_ItemToNode( p_playlist,
89
                    p_item_in_category, true );
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
90 91
            p_item_in_category->p_input->i_type = ITEM_TYPE_PLAYLIST;
        }
92

93 94
        int i_ret = playlist_BothAddInput( p_playlist, p_child,
                p_item_in_category,
95
                PLAYLIST_APPEND | PLAYLIST_SPREPARSE , PLAYLIST_END,
96
                NULL, NULL,  true );
97

98
        if( i_ret == VLC_SUCCESS && b_play )
99 100
        {
            playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
101
                          true, p_item_in_category, NULL );
102
        }
103
    }
104

105 106
    PL_UNLOCK;

107 108
}

109
/*****************************************************************************
110
 * An input item's meta or duration has changed (Event Callback)
111
 *****************************************************************************/
112 113
static void input_item_changed( const vlc_event_t * p_event,
                                void * user_data )
114
{
115
    (void)p_event;
116 117 118 119 120
    playlist_item_t * p_item = user_data;
    var_SetInteger( p_item->p_playlist,
                    "item-change", p_item->i_id );
}

121 122 123
/*****************************************************************************
 * Listen to vlc_InputItemAddSubItem event
 *****************************************************************************/
124
static void install_input_item_observer( playlist_item_t * p_item )
125
{
126 127
    vlc_event_attach( &p_item->p_input->event_manager,
                      vlc_InputItemSubItemAdded,
128
                      input_item_subitem_added,
129
                      p_item );
130 131
    vlc_event_attach( &p_item->p_input->event_manager,
                      vlc_InputItemDurationChanged,
132
                      input_item_changed,
133
                      p_item );
134 135
    vlc_event_attach( &p_item->p_input->event_manager,
                      vlc_InputItemMetaChanged,
136
                      input_item_changed,
137
                      p_item );
138 139
}

140
static void uninstall_input_item_observer( playlist_item_t * p_item )
141
{
142 143
    vlc_event_detach( &p_item->p_input->event_manager,
                      vlc_InputItemMetaChanged,
144
                      input_item_changed,
145
                      p_item );
146 147
    vlc_event_detach( &p_item->p_input->event_manager,
                      vlc_InputItemDurationChanged,
148
                      input_item_changed,
149
                      p_item );
150 151
    vlc_event_detach( &p_item->p_input->event_manager,
                      vlc_InputItemSubItemAdded,
152
                      input_item_subitem_added,
153
                      p_item );
154 155
}

156 157 158
/*****************************************************************************
 * Playlist item creation
 *****************************************************************************/
159 160 161
playlist_item_t * playlist_ItemNewWithType( vlc_object_t *p_obj,
                                            const char *psz_uri,
                                            const char *psz_name,
162
                                            int i_options,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
163
                                            const char *const *ppsz_options,
164
                                            int i_duration, int i_type )
165
{
166
    input_item_t *p_input;
167
    if( psz_uri == NULL ) return NULL;
168
    p_input = input_ItemNewWithType( p_obj, psz_uri,
169 170
                                     psz_name, i_options, ppsz_options,
                                     i_duration, i_type );
171 172
    return playlist_ItemNewFromInput( p_obj, p_input );
}
173

174
playlist_item_t *__playlist_ItemNewFromInput( vlc_object_t *p_obj,
175
                                              input_item_t *p_input )
176
{
177
    DECMALLOC_NULL( p_item, playlist_item_t );
178
    playlist_t *p_playlist = pl_Yield( p_obj );
179

180 181
    p_item->p_input = p_input;
    vlc_gc_incref( p_item->p_input );
182

183
    p_item->i_id = ++p_playlist->i_last_playlist_id;
184

185
    p_item->p_parent = NULL;
186 187 188
    p_item->i_children = -1;
    p_item->pp_children = NULL;
    p_item->i_flags = 0;
189
    p_item->p_playlist = p_playlist;
190

191
    install_input_item_observer( p_item );
192

193 194
    pl_Release( p_item->p_playlist );

195 196 197
    return p_item;
}

198 199 200 201
/***************************************************************************
 * Playlist item destruction
 ***************************************************************************/

202 203 204 205 206 207 208
/**
 * Delete item
 *
 * Delete a playlist item and detach its input item
 * \param p_item item to delete
 * \return VLC_SUCCESS
*/
209 210
int playlist_ItemDelete( playlist_item_t *p_item )
{
211
    uninstall_input_item_observer( p_item );
212

213 214 215 216 217
    vlc_gc_decref( p_item->p_input );
    free( p_item );
    return VLC_SUCCESS;
}

218 219 220 221 222 223 224 225 226 227
/**
 * Delete input item
 *
 * Remove an input item when it appears from a root playlist item
 * \param p_playlist playlist object
 * \param i_input_id id of the input to delete
 * \param p_root root playlist item
 * \param b_do_stop must stop or not the playlist
 * \return VLC_SUCCESS or VLC_EGENERIC
*/
228
static int DeleteFromInput( playlist_t *p_playlist, int i_input_id,
229
                            playlist_item_t *p_root, bool b_do_stop )
230 231 232 233 234 235 236 237
{
    int i;
    for( i = 0 ; i< p_root->i_children ; i++ )
    {
        if( p_root->pp_children[i]->i_children == -1 &&
            p_root->pp_children[i]->p_input->i_id == i_input_id )
        {
            DeleteInner( p_playlist, p_root->pp_children[i], b_do_stop );
238
            return VLC_SUCCESS;
239 240 241
        }
        else if( p_root->pp_children[i]->i_children >= 0 )
        {
242 243
            int i_ret = DeleteFromInput( p_playlist, i_input_id,
                                         p_root->pp_children[i], b_do_stop );
244
            if( i_ret == VLC_SUCCESS ) return VLC_SUCCESS;
245 246
        }
    }
247
    return VLC_EGENERIC;
248 249
}

250 251 252 253 254 255 256 257 258 259
/**
 * Delete input item
 *
 * Remove an input item when it appears from a root playlist item
 * \param p_playlist playlist object
 * \param i_input_id id of the input to delete
 * \param p_root root playlist item
 * \param b_locked TRUE if the playlist is locked
 * \return VLC_SUCCESS or VLC_EGENERIC
 */
260
int playlist_DeleteInputInParent( playlist_t *p_playlist, int i_input_id,
261
                                  playlist_item_t *p_root, bool b_locked )
262 263 264 265
{
    int i_ret;
    if( !b_locked ) PL_LOCK;
    i_ret = DeleteFromInput( p_playlist, i_input_id,
266
                             p_root, true );
267 268 269 270
    if( !b_locked ) PL_UNLOCK;
    return i_ret;
}

271 272 273 274 275 276 277 278 279
/**
 * Delete from input
 *
 * Remove an input item from ONELEVEL and CATEGORY
 * \param p_playlist playlist object
 * \param i_input_id id of the input to delete
 * \param b_locked TRUE if the playlist is locked
 * \return VLC_SUCCESS or VLC_ENOITEM
 */
280
int playlist_DeleteFromInput( playlist_t *p_playlist, int i_input_id,
281
                              bool b_locked )
282
{
283
    int i_ret1, i_ret2;
284
    if( !b_locked ) PL_LOCK;
285
    i_ret1 = DeleteFromInput( p_playlist, i_input_id,
286
                             p_playlist->p_root_category, true );
287
    i_ret2 = DeleteFromInput( p_playlist, i_input_id,
288
                     p_playlist->p_root_onelevel, true );
289
    if( !b_locked ) PL_UNLOCK;
290 291
    return ( i_ret1 == VLC_SUCCESS || i_ret2 == VLC_SUCCESS ) ?
                            VLC_SUCCESS : VLC_ENOITEM;
292 293
}

294 295 296 297 298 299 300
/**
 * Clear the playlist
 *
 * \param p_playlist playlist object
 * \param b_locked TRUE if the playlist is locked
 * \return nothing
 */
301
void playlist_Clear( playlist_t * p_playlist, bool b_locked )
302
{
303
    if( !b_locked ) PL_LOCK;
304 305
    playlist_NodeEmpty( p_playlist, p_playlist->p_local_category, true );
    playlist_NodeEmpty( p_playlist, p_playlist->p_local_onelevel, true );
306
    if( !b_locked ) PL_UNLOCK;
307
}
308

309 310 311 312 313 314 315 316 317
/**
 * Delete playlist item
 *
 * Remove a playlist item from the playlist, given its id
 * This function is to be used only by the playlist
 * \param p_playlist playlist object
 * \param i_id id of the item do delete
 * \return VLC_SUCCESS or an error
 */
318
int playlist_DeleteFromItemId( playlist_t *p_playlist, int i_id )
319
{
320
    playlist_item_t *p_item = playlist_ItemGetById( p_playlist, i_id,
321
                                                    true );
322
    if( !p_item ) return VLC_EGENERIC;
323
    return DeleteInner( p_playlist, p_item, true );
324 325 326 327 328
}

/***************************************************************************
 * Playlist item addition
 ***************************************************************************/
329 330 331 332
/**
 * Playlist add
 *
 * Add an item to the playlist or the media library
333 334 335 336 337 338
 * \param p_playlist the playlist to add into
 * \param psz_uri the mrl to add to the playlist
 * \param psz_name a text giving a name or description of this item
 * \param i_mode the mode used when adding
 * \param i_pos the position in the playlist where to add. If this is
 *        PLAYLIST_END the item will be added at the end of the playlist
339
 *        regardless of its size
340
 * \param b_playlist TRUE for playlist, FALSE for media library
341
 * \param b_locked TRUE if the playlist is locked
342
 * \return The id of the playlist item
343
 */
344 345
int playlist_Add( playlist_t *p_playlist, const char *psz_uri,
                  const char *psz_name, int i_mode, int i_pos,
346
                  bool b_playlist, bool b_locked )
347
{
348
    return playlist_AddExt( p_playlist, psz_uri, psz_name,
349
                            i_mode, i_pos, -1, NULL, 0, b_playlist, b_locked );
350
}
351

352
/**
353
 * Add a MRL into the playlist or the media library, duration and options given
354 355 356 357 358 359 360
 *
 * \param p_playlist the playlist to add into
 * \param psz_uri the mrl to add to the playlist
 * \param psz_name a text giving a name or description of this item
 * \param i_mode the mode used when adding
 * \param i_pos the position in the playlist where to add. If this is
 *        PLAYLIST_END the item will be added at the end of the playlist
361
 *        regardless of its size
362 363 364
 * \param i_duration length of the item in milliseconds.
 * \param ppsz_options an array of options
 * \param i_options the number of options
365
 * \param b_playlist TRUE for playlist, FALSE for media library
366
 * \param b_locked TRUE if the playlist is locked
367 368
 * \return The id of the playlist item
*/
369 370
int playlist_AddExt( playlist_t *p_playlist, const char * psz_uri,
                     const char *psz_name, int i_mode, int i_pos,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
371
                     mtime_t i_duration, const char *const *ppsz_options,
372
                     int i_options, bool b_playlist, bool b_locked )
373
{
374
    int i_ret;
375 376 377
    input_item_t *p_input = input_ItemNewExt( p_playlist, psz_uri, psz_name,
                                              i_options, ppsz_options,
                                              i_duration );
378

379 380
    i_ret = playlist_AddInput( p_playlist, p_input, i_mode, i_pos, b_playlist,
                               b_locked );
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
381
    int i_id = i_ret == VLC_SUCCESS ? p_input->i_id : -1;
382

383
    vlc_gc_decref( p_input );
384 385

    return i_id;
386
}
387

388 389 390 391 392 393 394 395 396 397 398
/**
 * Add an input item to the playlist node
 *
 * \param p_playlist the playlist to add into
 * \param p_input the input item to add
 * \param i_mode the mode used when adding
 * \param i_pos the position in the playlist where to add. If this is
 *        PLAYLIST_END the item will be added at the end of the playlist
 *        regardless of its size
 * \param b_playlist TRUE for playlist, FALSE for media library
 * \param b_locked TRUE if the playlist is locked
399
 * \return VLC_SUCCESS or VLC_ENOMEM or VLC_EGENERIC
400
*/
401
int playlist_AddInput( playlist_t* p_playlist, input_item_t *p_input,
402 403
                       int i_mode, int i_pos, bool b_playlist,
                       bool b_locked )
404
{
Clément Stenac's avatar
Clément Stenac committed
405
    playlist_item_t *p_item_cat, *p_item_one;
406
    if( p_playlist->b_die ) return VLC_EGENERIC;
407 408 409
    if( !p_playlist->b_doing_ml )
        PL_DEBUG( "adding item `%s' ( %s )", p_input->psz_name,
                                             p_input->psz_uri );
410

411
    if( !b_locked ) PL_LOCK;
412

413
    /* Add to ONELEVEL */
Clément Stenac's avatar
Clément Stenac committed
414
    p_item_one = playlist_ItemNewFromInput( p_playlist, p_input );
415
    if( p_item_one == NULL ) return VLC_ENOMEM;
416 417
    AddItem( p_playlist, p_item_one,
             b_playlist ? p_playlist->p_local_onelevel :
418
                          p_playlist->p_ml_onelevel , i_mode, i_pos );
419 420

    /* Add to CATEGORY */
Clément Stenac's avatar
Clément Stenac committed
421
    p_item_cat = playlist_ItemNewFromInput( p_playlist, p_input );
422
    if( p_item_cat == NULL ) return VLC_ENOMEM;
423
    AddItem( p_playlist, p_item_cat,
424
             b_playlist ? p_playlist->p_local_category :
425
                          p_playlist->p_ml_category , i_mode, i_pos );
426

Clément Stenac's avatar
Clément Stenac committed
427
    GoAndPreparse( p_playlist, i_mode, p_item_cat, p_item_one );
428

429
    if( !b_locked ) PL_UNLOCK;
430 431 432
    return VLC_SUCCESS;
}

433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
/**
 * Add input
 *
 * Add an input item to p_direct_parent in the category tree, and to the
 * matching top category in onelevel
 * \param p_playlist the playlist to add into
 * \param p_input the input item to add
 * \param p_direct_parent the parent item to add into
 * \param i_mode the mode used when adding
 * \param i_pos the position in the playlist where to add. If this is
 *        PLAYLIST_END the item will be added at the end of the playlist
 *        regardless of its size
 * \param i_cat id of the items category
 * \param i_one id of the item onelevel category
 * \param b_locked TRUE if the playlist is locked
448
 * \return VLC_SUCCESS if success, VLC_EGENERIC if fail, VLC_ENOMEM if OOM
449
 */
450 451 452
int playlist_BothAddInput( playlist_t *p_playlist,
                           input_item_t *p_input,
                           playlist_item_t *p_direct_parent,
453
                           int i_mode, int i_pos,
454
                           int *i_cat, int *i_one, bool b_locked )
455
{
Clément Stenac's avatar
Clément Stenac committed
456
    playlist_item_t *p_item_cat, *p_item_one, *p_up;
457
    int i_top;
Clément Stenac's avatar
Clément Stenac committed
458
    assert( p_input );
459
    if( p_playlist->b_die ) return VLC_EGENERIC;
460
    if( !b_locked ) PL_LOCK;
461 462

    /* Add to category */
Clément Stenac's avatar
Clément Stenac committed
463
    p_item_cat = playlist_ItemNewFromInput( p_playlist, p_input );
464
    if( p_item_cat == NULL ) return VLC_ENOMEM;
465
    AddItem( p_playlist, p_item_cat, p_direct_parent, i_mode, i_pos );
466 467

    /* Add to onelevel */
468
    /** \todo make a faster case for ml import */
Clément Stenac's avatar
Clément Stenac committed
469
    p_item_one = playlist_ItemNewFromInput( p_playlist, p_input );
470
    if( p_item_one == NULL ) return VLC_ENOMEM;
471 472 473

    p_up = p_direct_parent;
    while( p_up->p_parent != p_playlist->p_root_category )
474
    {
475
        p_up = p_up->p_parent;
476
    }
477
    for( i_top = 0 ; i_top < p_playlist->p_root_onelevel->i_children; i_top++ )
Clément Stenac's avatar
Clément Stenac committed
478
    {
479 480
        if( p_playlist->p_root_onelevel->pp_children[i_top]->p_input->i_id ==
                             p_up->p_input->i_id )
481
        {
Clément Stenac's avatar
Clément Stenac committed
482
            AddItem( p_playlist, p_item_one,
483 484
                     p_playlist->p_root_onelevel->pp_children[i_top],
                     i_mode, i_pos );
485 486
            break;
        }
Clément Stenac's avatar
Clément Stenac committed
487
    }
Clément Stenac's avatar
Clément Stenac committed
488
    GoAndPreparse( p_playlist, i_mode, p_item_cat, p_item_one );
Clément Stenac's avatar
Clément Stenac committed
489

490 491 492
    if( i_cat ) *i_cat = p_item_cat->i_id;
    if( i_one ) *i_one = p_item_one->i_id;

493
    if( !b_locked ) PL_UNLOCK;
494
    return VLC_SUCCESS;
495
}
496

497 498 499 500 501 502 503 504 505 506 507 508 509
/**
 * Add an input item to a given node
 *
 * \param p_playlist the playlist to add into
 * \param p_input the input item to add
 * \param p_parent the parent item to add into
 * \param i_mode the mode used when addin
 * \param i_pos the position in the playlist where to add. If this is
 *        PLAYLIST_END the item will be added at the end of the playlist
 *        regardless of its size
 * \param b_locked TRUE if the playlist is locked
 * \return the new playlist item
 */
510 511 512
playlist_item_t * playlist_NodeAddInput( playlist_t *p_playlist,
                                         input_item_t *p_input,
                                         playlist_item_t *p_parent,
513
                                         int i_mode, int i_pos,
514
                                         bool b_locked )
515
{
516
    playlist_item_t *p_item;
Clément Stenac's avatar
Clément Stenac committed
517 518
    assert( p_input );
    assert( p_parent && p_parent->i_children != -1 );
519

520 521
    if( p_playlist->b_die )
        return NULL;
522
    if( !b_locked ) PL_LOCK;
523

524 525
    p_item = playlist_ItemNewFromInput( p_playlist, p_input );
    if( p_item == NULL ) return NULL;
526
    AddItem( p_playlist, p_item, p_parent, i_mode, i_pos );
527

528
    if( !b_locked ) PL_UNLOCK;
529 530

    return p_item;
531
}
532

533 534 535 536
/*****************************************************************************
 * Playlist item misc operations
 *****************************************************************************/

Clément Stenac's avatar
Clément Stenac committed
537
/**
538 539
 * Item to node
 *
540 541
 * Transform an item to a node. Return the node in the category tree, or NULL
 * if not found there
Pierre d'Herbemont's avatar
Pierre d'Herbemont committed
542
 * This function must be entered without the playlist lock
543 544 545 546
 * \param p_playlist the playlist object
 * \param p_item the item to transform
 * \param b_locked TRUE if the playlist is locked
 * \return the item transform in a node
Clément Stenac's avatar
Clément Stenac committed
547
 */
548
playlist_item_t *playlist_ItemToNode( playlist_t *p_playlist,
549
                                      playlist_item_t *p_item,
550
                                      bool b_locked )
Clément Stenac's avatar
Clément Stenac committed
551
{
552 553

    playlist_item_t *p_item_in_category;
554 555 556 557 558
    /* What we do
     * Find the input in CATEGORY.
     *  - If we find it
     *    - change it to node
     *    - we'll return it at the end
559 560
     *    - If we are a direct child of onelevel root, change to node, else
     *      delete the input from ONELEVEL
561 562 563 564 565 566 567
     *  - If we don't find it, just change to node (we are probably in VLM)
     *    and return NULL
     *
     * If we were in ONELEVEL, we thus retrieve the node in CATEGORY (will be
     * useful for later BothAddInput )
     */

568 569
    if( !b_locked ) PL_LOCK;

570
    /* Fast track the media library, no time to loose */
571 572
    if( p_item == p_playlist->p_ml_category ) {
        if( !b_locked ) PL_UNLOCK;
573
        return p_item;
574
    }
575

576
    /** \todo First look if we don't already have it */
577
    p_item_in_category = playlist_ItemFindFromInputAndRoot(
578
                                            p_playlist, p_item->p_input->i_id,
Clément Stenac's avatar
Clément Stenac committed
579
                                            p_playlist->p_root_category,
580
                                            true );
581 582

    if( p_item_in_category )
Clément Stenac's avatar
Clément Stenac committed
583
    {
584 585
        playlist_item_t *p_item_in_one = playlist_ItemFindFromInputAndRoot(
                                            p_playlist, p_item->p_input->i_id,
Clément Stenac's avatar
Clément Stenac committed
586
                                            p_playlist->p_root_onelevel,
587
                                            true );
588 589
        assert( p_item_in_one );

590
        /* We already have it, and there is nothing more to do */
591
        ChangeToNode( p_playlist, p_item_in_category );
592

593
        /* Item in one is a root, change it to node */
594 595 596 597
        if( p_item_in_one->p_parent == p_playlist->p_root_onelevel )
            ChangeToNode( p_playlist, p_item_in_one );
        else
        {
598
            DeleteFromInput( p_playlist, p_item_in_one->p_input->i_id,
599
                             p_playlist->p_root_onelevel, false );
600
        }
601
        p_playlist->b_reset_currently_playing = true;
602
        vlc_cond_signal( &p_playlist->object_wait );
603 604
        var_SetInteger( p_playlist, "item-change", p_item_in_category->
                                                        p_input->i_id );
605
        if( !b_locked ) PL_UNLOCK;
606 607 608 609 610
        return p_item_in_category;
    }
    else
    {
        ChangeToNode( p_playlist, p_item );
611
        if( !b_locked ) PL_UNLOCK;
612
        return NULL;
Clément Stenac's avatar
Clément Stenac committed
613 614 615
    }
}

616 617 618 619 620 621 622
/**
 * Find an item within a root, given its input id.
 *
 * \param p_playlist the playlist object
 * \param i_input_id id of the input
 * \param p_root root playlist item
 * \param b_items_only TRUE if we want the item himself
623 624 625 626
 * \return the first found item, or NULL if not found
 */
playlist_item_t *playlist_ItemFindFromInputAndRoot( playlist_t *p_playlist,
                                                    int i_input_id,
Clément Stenac's avatar
Clément Stenac committed
627
                                                    playlist_item_t *p_root,
628
                                                    bool b_items_only )
629 630 631 632
{
    int i;
    for( i = 0 ; i< p_root->i_children ; i++ )
    {
Clément Stenac's avatar
Clément Stenac committed
633
        if( ( b_items_only ? p_root->pp_children[i]->i_children == -1 : 1 ) &&
634 635 636 637 638 639 640 641
            p_root->pp_children[i]->p_input->i_id == i_input_id )
        {
            return p_root->pp_children[i];
        }
        else if( p_root->pp_children[i]->i_children >= 0 )
        {
            playlist_item_t *p_search =
                 playlist_ItemFindFromInputAndRoot( p_playlist, i_input_id,
Clément Stenac's avatar
Clément Stenac committed
642 643
                                                    p_root->pp_children[i],
                                                    b_items_only );
644 645 646 647 648
            if( p_search ) return p_search;
        }
    }
    return NULL;
}
649 650


Clément Stenac's avatar
Clément Stenac committed
651 652 653 654 655
static int TreeMove( playlist_t *p_playlist, playlist_item_t *p_item,
                     playlist_item_t *p_node, int i_newpos )
{
    int j;
    playlist_item_t *p_detach = p_item->p_parent;
656 657
    (void)p_playlist;

Clément Stenac's avatar
Clément Stenac committed
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
    if( p_node->i_children == -1 ) return VLC_EGENERIC;

    for( j = 0; j < p_detach->i_children; j++ )
    {
         if( p_detach->pp_children[j] == p_item ) break;
    }
    REMOVE_ELEM( p_detach->pp_children, p_detach->i_children, j );

    /* Attach to new parent */
    INSERT_ELEM( p_node->pp_children, p_node->i_children, i_newpos, p_item );
    p_item->p_parent = p_node;

    return VLC_SUCCESS;
}

673
/**
674 675 676
 * Moves an item
 *
 * This function must be entered with the playlist lock
677
 *
678 679 680 681 682
 * \param p_playlist the playlist
 * \param p_item the item to move
 * \param p_node the new parent of the item
 * \param i_newpos the new position under this new parent
 * \return VLC_SUCCESS or an error
683
 */
684 685 686
int playlist_TreeMove( playlist_t * p_playlist, playlist_item_t *p_item,
                       playlist_item_t *p_node, int i_newpos )
{
687
    int i_ret;
Clément Stenac's avatar
Clément Stenac committed
688 689 690
    /* Drop on a top level node. Move in the two trees */
    if( p_node->p_parent == p_playlist->p_root_category ||
        p_node->p_parent == p_playlist->p_root_onelevel )
691
    {
Clément Stenac's avatar
Clément Stenac committed
692 693
        /* Fixme: avoid useless lookups but we need some clean helpers */
        {
Clément Stenac's avatar
Clément Stenac committed
694 695 696 697
            /* Fixme: if we try to move a node on a top-level node, it will
             * fail because the node doesn't exist in onelevel and we will
             * do some shit in onelevel. We should recursively move all items
             * within the node */
Clément Stenac's avatar
Clément Stenac committed
698 699 700 701 702
            playlist_item_t *p_node_onelevel;
            playlist_item_t *p_item_onelevel;
            p_node_onelevel = playlist_ItemFindFromInputAndRoot( p_playlist,
                                                p_node->p_input->i_id,
                                                p_playlist->p_root_onelevel,
703
                                                false );
Clément Stenac's avatar
Clément Stenac committed
704 705 706
            p_item_onelevel = playlist_ItemFindFromInputAndRoot( p_playlist,
                                                p_item->p_input->i_id,
                                                p_playlist->p_root_onelevel,
707
                                                false );
Clément Stenac's avatar
Clément Stenac committed
708 709
            if( p_node_onelevel && p_item_onelevel )
                TreeMove( p_playlist, p_item_onelevel, p_node_onelevel, 0 );
Clément Stenac's avatar
Clément Stenac committed
710 711 712 713 714 715 716
        }
        {
            playlist_item_t *p_node_category;
            playlist_item_t *p_item_category;
            p_node_category = playlist_ItemFindFromInputAndRoot( p_playlist,
                                                p_node->p_input->i_id,
                                                p_playlist->p_root_category,
717
                                                false );
Clément Stenac's avatar
Clément Stenac committed
718 719 720
            p_item_category = playlist_ItemFindFromInputAndRoot( p_playlist,
                                                p_item->p_input->i_id,
                                                p_playlist->p_root_category,
721
                                                false );
Clément Stenac's avatar
Clément Stenac committed
722 723
            if( p_node_category && p_item_category )
                TreeMove( p_playlist, p_item_category, p_node_category, 0 );
Clément Stenac's avatar
Clément Stenac committed
724
        }
725
        i_ret = VLC_SUCCESS;
726
    }
Clément Stenac's avatar
Clément Stenac committed
727
    else
728
        i_ret = TreeMove( p_playlist, p_item, p_node, i_newpos );
729
    p_playlist->b_reset_currently_playing = true;
730 731
    vlc_cond_signal( &p_playlist->object_wait );
    return i_ret;
732 733
}

734 735
/**
 * Send a notification that an item has been added to a node
Rémi Duraffort's avatar
Rémi Duraffort committed
736
 *
737 738 739 740 741 742
 * \param p_playlist the playlist object
 * \param i_item_id id of the item added
 * \param i_node_id id of the node in wich the item was added
 * \param b_signal TRUE if the function must send a signal
 * \return nothing
 */
743
void playlist_SendAddNotify( playlist_t *p_playlist, int i_item_id,
744
                             int i_node_id, bool b_signal )
745 746
{
    vlc_value_t val;
747
    playlist_add_t *p_add = (playlist_add_t *)malloc( sizeof( playlist_add_t) );
748 749 750
    if( !p_add )
        return;

751 752 753
    p_add->i_item = i_item_id;
    p_add->i_node = i_node_id;
    val.p_address = p_add;
754
    p_playlist->b_reset_currently_playing = true;
755 756
    if( b_signal )
        vlc_cond_signal( &p_playlist->object_wait );
757 758 759 760 761 762 763 764
    var_Set( p_playlist, "item-append", val );
    free( p_add );
}

/*****************************************************************************
 * Playlist item accessors
 *****************************************************************************/

765 766 767 768 769 770 771
/**
 * Set the name of a playlist item
 *
 * \param p_item the item
 * \param psz_name the name
 * \return VLC_SUCCESS or VLC_EGENERIC
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
772
int playlist_ItemSetName( playlist_item_t *p_item, const char *psz_name )
773 774 775
{
    if( psz_name && p_item )
    {
776
        input_item_SetName( p_item->p_input, psz_name );
777 778 779 780 781
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}

782 783 784 785 786
/***************************************************************************
 * The following functions are local
 ***************************************************************************/

/* Enqueue an item for preparsing, and play it, if needed */
787 788 789
static void GoAndPreparse( playlist_t *p_playlist, int i_mode,
                           playlist_item_t *p_item_cat,
                           playlist_item_t *p_item_one )
790
{
Clément Stenac's avatar
Clément Stenac committed
791
    if( (i_mode & PLAYLIST_GO ) )
792
    {
Clément Stenac's avatar
Clément Stenac committed
793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
        playlist_item_t *p_parent = p_item_one;
        playlist_item_t *p_toplay = NULL;
        while( p_parent )
        {
            if( p_parent == p_playlist->p_root_category )
            {
                p_toplay = p_item_cat; break;
            }
            else if( p_parent == p_playlist->p_root_onelevel )
            {
                p_toplay = p_item_one; break;
            }
            p_parent = p_parent->p_parent;
        }
        assert( p_toplay );
808
        p_playlist->request.b_request = true;
809
        p_playlist->request.i_skip = 0;
Clément Stenac's avatar
Clément Stenac committed
810
        p_playlist->request.p_item = p_toplay;
811 812
        if( p_playlist->p_input )
            input_StopThread( p_playlist->p_input );
Clément Stenac's avatar
Clément Stenac committed
813
        p_playlist->request.i_status = PLAYLIST_RUNNING;
814
        vlc_cond_signal( &p_playlist->object_wait );
815
    }
816
    /* Preparse if PREPARSE or SPREPARSE & not enough meta */
817 818
    char *psz_artist = input_item_GetArtist( p_item_cat->p_input );
    char *psz_album = input_item_GetAlbum( p_item_cat->p_input );
819 820 821
    if( p_playlist->b_auto_preparse &&
          (i_mode & PLAYLIST_PREPARSE ||
          ( i_mode & PLAYLIST_SPREPARSE &&
822
            ( EMPTY_STR( psz_artist ) || ( EMPTY_STR( psz_album ) ) )
823
          ) ) )
Clément Stenac's avatar
Clément Stenac committed
824
        playlist_PreparseEnqueue( p_playlist, p_item_cat->p_input );
825
    /* If we already have it, signal it */
826
    else if( !EMPTY_STR( psz_artist ) && !EMPTY_STR( psz_album ) )
827
        input_item_SetPreparsed( p_item_cat->p_input, true );
828 829
    free( psz_artist );
    free( psz_album );
830 831 832
}

/* Add the playlist item to the requested node and fire a notification */
833 834
static void AddItem( playlist_t *p_playlist, playlist_item_t *p_item,
                     playlist_item_t *p_node, int i_mode, int i_pos )
835
{
Clément Stenac's avatar
Clément Stenac committed
836 837
    ARRAY_APPEND(p_playlist->items, p_item);
    ARRAY_APPEND(p_playlist->all_items, p_item);
838 839 840 841 842

    if( i_pos == PLAYLIST_END )
        playlist_NodeAppend( p_playlist, p_item, p_node );
    else
        playlist_NodeInsert( p_playlist, p_item, p_node, i_pos );
Clément Stenac's avatar
Clément Stenac committed
843

844
    if( !p_playlist->b_doing_ml )
845 846
        playlist_SendAddNotify( p_playlist, p_item->i_id, p_node->i_id,
                                 !( i_mode & PLAYLIST_NO_REBUILD ) );
847 848 849
}

/* Actually convert an item to a node */
850
static void ChangeToNode( playlist_t *p_playlist, playlist_item_t *p_item )
851 852 853 854 855 856
{
    int i;
    if( p_item->i_children == -1 )
        p_item->i_children = 0;

    /* Remove it from the array of available items */
Clément Stenac's avatar
Clément Stenac committed
857 858 859
    ARRAY_BSEARCH( p_playlist->items,->i_id, int, p_item->i_id, i );
    if( i != -1 )
        ARRAY_REMOVE( p_playlist->items, i );
860 861 862
}

/* Do the actual removal */
863
static int DeleteInner( playlist_t * p_playlist, playlist_item_t *p_item,
864
                        bool b_stop )
865
{
Clément Stenac's avatar
Clément Stenac committed
866
    int i;
867
    int i_id = p_item->i_id;
868
    bool b_delay_deletion = false;
869 870 871

    if( p_item->i_children > -1 )
    {
872
        return playlist_NodeDelete( p_playlist, p_item, true, false );
873
    }
874
    p_playlist->b_reset_currently_playing = true;
875 876 877
    var_SetInteger( p_playlist, "item-deleted", i_id );

    /* Remove the item from the bank */
Clément Stenac's avatar
Clément Stenac committed
878 879 880
    ARRAY_BSEARCH( p_playlist->all_items,->i_id, int, i_id, i );
    if( i != -1 )
        ARRAY_REMOVE( p_playlist->all_items, i );
Clément Stenac's avatar
Clément Stenac committed
881

882 883 884 885
    ARRAY_BSEARCH( p_playlist->items,->i_id, int, i_id, i );
    if( i != -1 )
        ARRAY_REMOVE( p_playlist->items, i );

886 887
    /* Check if it is the current item */
    if( p_playlist->status.p_item == p_item )
888
    {
889 890
        /* Hack we don't call playlist_Control for lock reasons */
        if( b_stop )
891
        {
892
            p_playlist->request.i_status = PLAYLIST_STOPPED;
893
            p_playlist->request.b_request = true;
894 895
            p_playlist->request.p_item = NULL;
            msg_Info( p_playlist, "stopping playback" );
896
            vlc_cond_signal( &p_playlist->object_wait );
897
        }
898
        b_delay_deletion = true;
899
    }
900

901
    PL_DEBUG( "deleting item `%s'", p_item->p_input->psz_name );
902 903 904 905

    /* Remove the item from its parent */
    playlist_NodeRemoveItem( p_playlist, p_item, p_item->p_parent );

906
    if( !b_delay_deletion )
907 908
        playlist_ItemDelete( p_item );
    else
909 910
    {
        PL_DEBUG( "marking %s for further deletion", PLI_NAME( p_item ) );
911
        p_item->i_flags |= PLAYLIST_REMOVE_FLAG;
912
    }
913 914

    return VLC_SUCCESS;
915
}