directory.c 18 KB
Newer Older
1 2 3
/*****************************************************************************
 * directory.c: expands a directory (directory: access plug-in)
 *****************************************************************************
4
 * Copyright (C) 2002-2004 the VideoLAN team
5
 * $Id$
6
 *
7
 * Authors: Derk-Jan Hartman <hartman at videolan dot org>>
8 9 10 11 12
 *
 * 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.
13
 *
14 15 16 17 18 19 20 21 22 23 24 25 26
 * 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
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
27

28 29
#include <vlc/vlc.h>
#include <vlc/input.h>
30
#include <vlc_playlist.h>
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_TYPES_H
#   include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#   include <sys/stat.h>
#endif
#ifdef HAVE_ERRNO_H
#   include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
#   include <fcntl.h>
#endif

#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#elif defined( WIN32 ) && !defined( UNDER_CE )
#   include <io.h>
51 52
#elif defined( UNDER_CE )
#   define strcoll strcmp
53 54
#endif

55
#ifdef HAVE_DIRENT_H
56 57 58 59
#   include <dirent.h>
#endif

/*****************************************************************************
60
 * Module descriptor
61
 *****************************************************************************/
62 63
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );
64

65
static int  DemuxOpen ( vlc_object_t * );
66

67
#define RECURSIVE_TEXT N_("Subdirectory behavior")
68 69
#define RECURSIVE_LONGTEXT N_( \
        "Select whether subdirectories must be expanded.\n" \
70 71 72
        "none: subdirectories do not appear in the playlist.\n" \
        "collapse: subdirectories appear but are expanded on first play.\n" \
        "expand: all subdirectories are expanded.\n" )
73 74 75 76

static char *psz_recursive_list[] = { "none", "collapse", "expand" };
static char *psz_recursive_list_text[] = { N_("none"), N_("collapse"),
                                           N_("expand") };
77

78 79 80 81 82 83
#define IGNORE_TEXT N_("Ignore files with these extensions")
#define IGNORE_LONGTEXT N_( \
        "Specify a comma seperated list of file extensions. " \
        "Files with these extensions will not be added to playlist when opening a directory. " \
        "This is useful if you add directories that contain mp3 albums for instance." )

84
vlc_module_begin();
85
    set_category( CAT_INPUT );
86
    set_shortname( _("Directory" ) );
87
    set_subcategory( SUBCAT_INPUT_ACCESS );
Gildas Bazin's avatar
 
Gildas Bazin committed
88
    set_description( _("Standard filesystem directory input") );
89
    set_capability( "access2", 55 );
90 91
    add_shortcut( "directory" );
    add_shortcut( "dir" );
92 93
    add_string( "recursive", "expand" , NULL, RECURSIVE_TEXT,
                RECURSIVE_LONGTEXT, VLC_FALSE );
Gildas Bazin's avatar
Gildas Bazin committed
94
      change_string_list( psz_recursive_list, psz_recursive_list_text, 0 );
95
#ifdef HAVE_STRSEP
96
    add_string( "ignore-filetypes", "m3u,db,nfo,jpg,gif,sfv,txt,sub,idx,srt,cue",
97
                NULL, IGNORE_TEXT, IGNORE_LONGTEXT, VLC_FALSE );
98
#endif
99
    set_callbacks( Open, Close );
100 101

    add_submodule();
102
        set_description( "Directory EOF");
103 104 105
        set_capability( "demux2", 0 );
        add_shortcut( "directory" );
        set_callbacks( DemuxOpen, NULL );
106 107 108
vlc_module_end();


109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
/*****************************************************************************
 * Local prototypes, constants, structures
 *****************************************************************************/

#define MODE_EXPAND 0
#define MODE_COLLAPSE 1
#define MODE_NONE 2

static int Read( access_t *, uint8_t *, int );
static int ReadNull( access_t *, uint8_t *, int );
static int Control( access_t *, int, va_list );

static int Demux( demux_t *p_demux );
static int DemuxControl( demux_t *p_demux, int i_query, va_list args );


125 126
static int ReadDir( playlist_t *, char *psz_name, int i_mode, int *pi_pos,
                    playlist_item_t * );
127

128 129 130 131 132
/*****************************************************************************
 * Open: open the directory
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
133
    access_t *p_access = (access_t*)p_this;
134

Gildas Bazin's avatar
 
Gildas Bazin committed
135
#ifdef HAVE_SYS_STAT_H
136 137 138
    struct stat stat_info;

    if( ( stat( p_access->psz_path, &stat_info ) == -1 ) ||
Gildas Bazin's avatar
 
Gildas Bazin committed
139
        !S_ISDIR( stat_info.st_mode ) )
140 141

#elif defined(WIN32)
142 143
    int i_ret;

144
#   ifdef UNICODE
145 146 147
    wchar_t psz_path[MAX_PATH];
    mbstowcs( psz_path, p_access->psz_path, MAX_PATH );
    psz_path[MAX_PATH-1] = 0;
148
#   else
149
    char *psz_path = p_access->psz_path;
150
#   endif /* UNICODE */
151

152 153 154
    i_ret = GetFileAttributes( psz_path );
    if( i_ret == -1 || !(i_ret & FILE_ATTRIBUTE_DIRECTORY) )

Gildas Bazin's avatar
 
Gildas Bazin committed
155
#else
156 157
    if( strcmp( p_access->psz_access, "dir") &&
        strcmp( p_access->psz_access, "directory") )
Gildas Bazin's avatar
 
Gildas Bazin committed
158
#endif
159
    {
160
        return VLC_EGENERIC;
161
    }
Gildas Bazin's avatar
 
Gildas Bazin committed
162

163 164 165 166 167
    p_access->pf_read  = Read;
    p_access->pf_block = NULL;
    p_access->pf_seek  = NULL;
    p_access->pf_control= Control;

168
    /* Force a demux */
169
    p_access->psz_demux = strdup( "directory" );
Gildas Bazin's avatar
 
Gildas Bazin committed
170

171 172 173 174 175 176 177 178
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: close the target
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
179 180 181 182 183 184 185 186 187 188
}

/*****************************************************************************
 * ReadNull: read the directory
 *****************************************************************************/
static int ReadNull( access_t *p_access, uint8_t *p_buffer, int i_len)
{
    /* Return fake data */
    memset( p_buffer, 0, i_len );
    return i_len;
189 190 191
}

/*****************************************************************************
192
 * Read: read the directory
193
 *****************************************************************************/
194
static int Read( access_t *p_access, uint8_t *p_buffer, int i_len)
195
{
196 197
    char *psz_name = NULL;
    char *psz;
198
    int  i_mode, i_pos;
199

200 201 202
    playlist_item_t *p_item;
    vlc_bool_t b_play = VLC_FALSE;

203 204 205
    playlist_t *p_playlist =
        (playlist_t *) vlc_object_find( p_access,
                                        VLC_OBJECT_PLAYLIST, FIND_ANYWHERE );
206 207 208

    if( !p_playlist )
    {
209
        msg_Err( p_access, "can't find playlist" );
210 211
        goto end;
    }
212

213
    /* Remove the ending '/' char */
214
    psz_name = strdup( p_access->psz_path );
215 216 217 218 219 220 221 222
    if( psz_name == NULL )
        goto end;

    if( (psz_name[strlen(psz_name)-1] == '/') ||
        (psz_name[strlen(psz_name)-1] == '\\') )
    {
        psz_name[strlen(psz_name)-1] = '\0';
    }
223

224
    /* Initialize structure */
225 226
    psz = var_CreateGetString( p_access, "recursive" );
    if( *psz == '\0' || !strncmp( psz, "none" , 4 )  )
227 228 229
    {
        i_mode = MODE_NONE;
    }
230
    else if( !strncmp( psz, "collapse", 8 )  )
231 232 233 234 235 236 237
    {
        i_mode = MODE_COLLAPSE;
    }
    else
    {
        i_mode = MODE_EXPAND;
    }
238
    free( psz );
239

240 241 242
    /* Make sure we are deleted when we are done */
    /* The playlist position we will use for the add */
    i_pos = p_playlist->i_index + 1;
Gildas Bazin's avatar
 
Gildas Bazin committed
243

244
    msg_Dbg( p_access, "opening directory `%s'", psz_name );
245

246 247 248 249 250 251 252 253 254 255 256
    if( &p_playlist->status.p_item->input ==
        ((input_thread_t *)p_access->p_parent)->input.p_item )
    {
        p_item = p_playlist->status.p_item;
        b_play = VLC_TRUE;
        msg_Dbg( p_access, "starting directory playback");
    }
    else
    {
        input_item_t *p_current = ( (input_thread_t*)p_access->p_parent)->
                                                        input.p_item;
257
        p_item = playlist_LockItemGetByInput( p_playlist, p_current );
258 259 260 261 262 263 264 265
        msg_Dbg( p_access, "not starting directory playback");
        if( !p_item )
        {
            msg_Dbg( p_playlist, "unable to find item in playlist");
            return -1;
        }
        b_play = VLC_FALSE;
    }
266

267
    p_item->input.i_type = ITEM_TYPE_DIRECTORY;
268
    if( ReadDir( p_playlist, psz_name , i_mode, &i_pos,
269
                 p_item ) != VLC_SUCCESS )
270 271
    {
    }
272
end:
273

274
    /* Begin to read the directory */
275 276 277 278 279 280
    if( b_play )
    {
        playlist_Control( p_playlist, PLAYLIST_VIEWPLAY,
                          p_playlist->status.i_view,
                          p_playlist->status.p_item, NULL );
    }
281
    if( psz_name ) free( psz_name );
282
    vlc_object_release( p_playlist );
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

    /* Return fake data forever */
    p_access->pf_read = ReadNull;
    return ReadNull( p_access, p_buffer, i_len );
}

/*****************************************************************************
 * DemuxOpen:
 *****************************************************************************/
static int Control( access_t *p_access, int i_query, va_list args )
{
    vlc_bool_t   *pb_bool;
    int          *pi_int;
    int64_t      *pi_64;

    switch( i_query )
    {
        /* */
        case ACCESS_CAN_SEEK:
        case ACCESS_CAN_FASTSEEK:
        case ACCESS_CAN_PAUSE:
        case ACCESS_CAN_CONTROL_PACE:
            pb_bool = (vlc_bool_t*)va_arg( args, vlc_bool_t* );
            *pb_bool = VLC_FALSE;    /* FIXME */
            break;

        /* */
        case ACCESS_GET_MTU:
            pi_int = (int*)va_arg( args, int * );
            *pi_int = 0;
            break;

        case ACCESS_GET_PTS_DELAY:
            pi_64 = (int64_t*)va_arg( args, int64_t * );
            *pi_64 = DEFAULT_PTS_DELAY * 1000;
            break;

        /* */
        case ACCESS_SET_PAUSE_STATE:
        case ACCESS_GET_TITLE_INFO:
        case ACCESS_SET_TITLE:
        case ACCESS_SET_SEEKPOINT:
325
        case ACCESS_SET_PRIVATE_ID_STATE:
326 327 328
            return VLC_EGENERIC;

        default:
329
            msg_Warn( p_access, "unimplemented query in control" );
330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
            return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}

/*****************************************************************************
 * DemuxOpen:
 *****************************************************************************/
static int DemuxOpen ( vlc_object_t *p_this )
{
    demux_t *p_demux = (demux_t*)p_this;

    if( strcmp( p_demux->psz_demux, "directory" ) )
        return VLC_EGENERIC;

    p_demux->pf_demux   = Demux;
    p_demux->pf_control = DemuxControl;
    return VLC_SUCCESS;
348
}
349

350 351 352 353 354 355 356 357 358 359 360 361
/*****************************************************************************
 * Demux: EOF
 *****************************************************************************/
static int Demux( demux_t *p_demux )
{
    return 0;
}
/*****************************************************************************
 * DemuxControl:
 *****************************************************************************/
static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
{
362
    return demux2_vaControlHelper( p_demux->s, 0, 0, 0, 1, i_query, args );
363
}
364

365
#if defined(SYS_BEOS) || defined(WIN32) || defined(SYS_SOLARIS)
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
/* BeOS doesn't have scandir/alphasort/versionsort */
static int alphasort( const struct dirent **a, const struct dirent **b )
{
    return strcoll( (*a)->d_name, (*b)->d_name );
}

static int scandir( const char *name, struct dirent ***namelist,
                    int (*filter) ( const struct dirent * ),
                    int (*compar) ( const struct dirent **,
                                    const struct dirent ** ) )
{
    DIR            * p_dir;
    struct dirent  * p_content;
    struct dirent ** pp_list;
    int              ret, size;

382
    if( !namelist || !( p_dir = opendir( name ) ) ) return -1;
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

    ret     = 0;
    pp_list = NULL;
    while( ( p_content = readdir( p_dir ) ) )
    {
        if( filter && !filter( p_content ) )
        {
            continue;
        }
        pp_list = realloc( pp_list, ( ret + 1 ) * sizeof( struct dirent * ) );
        size = sizeof( struct dirent ) + strlen( p_content->d_name ) + 1;
        pp_list[ret] = malloc( size );
        memcpy( pp_list[ret], p_content, size );
        ret++;
    }

    closedir( p_dir );

    if( compar )
    {
        qsort( pp_list, ret, sizeof( struct dirent * ),
               (int (*)(const void *, const void *)) compar );
    }

    *namelist = pp_list;
    return ret;
}
#endif

static int Filter( const struct dirent *foo )
413 414 415
{
    return VLC_TRUE;
}
416 417 418
/*****************************************************************************
 * ReadDir: read a directory and add its content to the list
 *****************************************************************************/
419
static int ReadDir( playlist_t *p_playlist,
420 421
                    char *psz_name , int i_mode, int *pi_position,
                    playlist_item_t *p_parent )
422
{
423
    struct dirent   **pp_dir_content;
424
    int             i_dir_content, i;
425
    playlist_item_t *p_node;
426 427 428

    /* Build array with ignores */
#ifdef HAVE_STRSEP
429 430 431
    char **ppsz_extensions = 0;
    int i_extensions = 0;
    char *psz_ignore = var_CreateGetString( p_playlist, "ignore-filetypes" );
432 433 434 435 436 437
    if( psz_ignore && *psz_ignore )
    {
        char *psz_backup;
        char *psz_parser = psz_backup = strdup( psz_ignore );
        int a = 0;

438
        while( strsep( &psz_parser, "," ) ) i_extensions++;
439 440
        free( psz_backup );

441 442 443 444 445
        ppsz_extensions = (char **)malloc( sizeof( char * ) * i_extensions );

        psz_parser = psz_ignore;
        while( a < i_extensions &&
               ( ppsz_extensions[a++] = strsep( &psz_parser, "," ) ) );
446 447
    }
#endif /* HAVE_STRSEP */
448

449 450 451
    /* Change the item to a node */
    if( p_parent->i_children == -1 )
    {
452
        playlist_LockItemToNode( p_playlist,p_parent );
453
    }
454

455
    /* get the first directory entry */
456
    i_dir_content = scandir( psz_name, &pp_dir_content, Filter, alphasort );
457 458 459 460 461
    if( i_dir_content == -1 )
    {
        msg_Warn( p_playlist, "Failed to read directory" );
        return VLC_EGENERIC;
    }
462 463 464 465 466
    else if( i_dir_content <= 0 )
    {
        /* directory is empty */
        return VLC_SUCCESS;
    }
467

468 469
    /* While we still have entries in the directory */
    for( i = 0; i < i_dir_content; i++ )
470
    {
471
        struct dirent *p_dir_content = pp_dir_content[i];
472
        int i_size_entry = strlen( psz_name ) +
Derk-Jan Hartman's avatar
Derk-Jan Hartman committed
473
                           strlen( p_dir_content->d_name ) + 2;
474
        char *psz_uri = (char *)malloc( sizeof(char) * i_size_entry );
475

476
        sprintf( psz_uri, "%s/%s", psz_name, p_dir_content->d_name );
477

478 479
        /* if it starts with '.' then forget it */
        if( p_dir_content->d_name[0] != '.' )
480
        {
481
#if defined( S_ISDIR )
Gildas Bazin's avatar
Gildas Bazin committed
482
            struct stat stat_data;
483 484
            stat( psz_uri, &stat_data );
            if( S_ISDIR(stat_data.st_mode) && i_mode != MODE_COLLAPSE )
485
#elif defined( DT_DIR )
486
            if( ( p_dir_content->d_type & DT_DIR ) && i_mode != MODE_COLLAPSE )
Gildas Bazin's avatar
Gildas Bazin committed
487 488 489
#else
            if( 0 )
#endif
490 491 492
            {
                if( i_mode == MODE_NONE )
                {
493
                    msg_Dbg( p_playlist, "Skipping subdirectory %s", psz_uri );
494
                    free( psz_uri );
495 496
                    continue;
                }
497
                else if( i_mode == MODE_EXPAND )
498
                {
499
                    char *psz_newname;
500
                    msg_Dbg(p_playlist, "Reading subdirectory %s", psz_uri );
501 502 503 504 505 506 507 508 509 510 511 512

                    if( !strncmp( psz_uri, psz_name, strlen( psz_name ) ) )
                    {
                        char *psz_subdir = psz_uri;
                        /* Skip the parent path + the separator */
                        psz_subdir += strlen( psz_name ) + 1;
                        psz_newname = strdup( psz_subdir );
                    }
                    else
                    {
                        psz_newname = strdup( psz_uri );
                    }
513 514
                    p_node = playlist_NodeCreate( p_playlist,
                                       p_parent->pp_parents[0]->i_view,
515
                                       psz_newname, p_parent );
516

Clément Stenac's avatar
Clément Stenac committed
517 518
                    playlist_CopyParents(  p_parent, p_node );

519
                    p_node->input.i_type = ITEM_TYPE_DIRECTORY;
Clément Stenac's avatar
Clément Stenac committed
520

521 522
                    if( ReadDir( p_playlist, psz_uri , MODE_EXPAND,
                                 pi_position, p_node ) != VLC_SUCCESS )
523 524 525
                    {
                        return VLC_EGENERIC;
                    }
526 527

                    free( psz_newname );
528 529 530 531
                }
            }
            else
            {
532 533
                playlist_item_t *p_item;

534 535 536 537
#ifdef HAVE_STRSEP
                if( i_extensions > 0 )
                {
                    char *psz_dot = strrchr( p_dir_content->d_name, '.' );
538
                    if( psz_dot++ && *psz_dot )
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
                    {
                        int a;
                        for( a = 0; a < i_extensions; a++ )
                        {
                            if( !strcmp( psz_dot, ppsz_extensions[a] ) )
                                break;
                        }
                        if( a < i_extensions )
                        {
                            msg_Dbg( p_playlist, "Ignoring file %s", psz_uri );
                            free( psz_uri );
                            continue;
                        }
                    }
                }
#endif /* HAVE_STRSEP */
555

Laurent Aimar's avatar
Laurent Aimar committed
556 557
                p_item = playlist_ItemNewWithType( VLC_OBJECT(p_playlist),
                        psz_uri, p_dir_content->d_name, ITEM_TYPE_VFILE );
558 559 560 561
                playlist_NodeAddItem( p_playlist,p_item,
                                      p_parent->pp_parents[0]->i_view,
                                      p_parent,
                                      PLAYLIST_APPEND, PLAYLIST_END );
Clément Stenac's avatar
Clément Stenac committed
562 563

                playlist_CopyParents( p_parent, p_item );
564 565
            }
        }
566
        free( psz_uri );
567
    }
568 569

#ifdef HAVE_STRSEP
570
    if( ppsz_extensions ) free( ppsz_extensions );
571 572 573
    if( psz_ignore ) free( psz_ignore );
#endif /* HAVE_STRSEP */

574
    free( pp_dir_content );
575 576
    return VLC_SUCCESS;
}