directory.c 18 KB
Newer Older
1 2 3
/*****************************************************************************
 * directory.c: expands a directory (directory: access plug-in)
 *****************************************************************************
4
 * Copyright (C) 2002-2007 the VideoLAN team
5
 * $Id$
6
 *
7
 * Authors: Derk-Jan Hartman <hartman at videolan dot org>
8
 *          Rémi Denis-Courmont
9 10 11 12 13
 *
 * 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.
14
 *
15 16 17 18 19 20 21
 * 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
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
28

29
#include <vlc/vlc.h>
30
#include <vlc_playlist.h>
31
#include <vlc_input.h>
Clément Stenac's avatar
Clément Stenac committed
32 33
#include <vlc_access.h>
#include <vlc_demux.h>
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51

#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>
52 53
#elif defined( UNDER_CE )
#   define strcoll strcmp
54 55
#endif

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

Clément Stenac's avatar
Clément Stenac committed
60
#include <vlc_charset.h>
61

62
/*****************************************************************************
63
 * Module descriptor
64
 *****************************************************************************/
65 66
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );
67

68
static int  DemuxOpen ( vlc_object_t * );
69

70
#define RECURSIVE_TEXT N_("Subdirectory behavior")
71 72
#define RECURSIVE_LONGTEXT N_( \
        "Select whether subdirectories must be expanded.\n" \
73 74 75
        "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" )
76

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

Clément Stenac's avatar
Clément Stenac committed
81
#define IGNORE_TEXT N_("Ignored extensions")
82
#define IGNORE_LONGTEXT N_( \
Clément Stenac's avatar
Clément Stenac committed
83 84 85 86
        "Files with these extensions will not be added to playlist when " \
        "opening a directory.\n" \
        "This is useful if you add directories that contain playlist files " \
        "for instance. Use a comma-separated list of extensions." )
87

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

    add_submodule();
105
        set_description( "Directory EOF");
106 107
        set_capability( "demux2", 0 );
        set_callbacks( DemuxOpen, NULL );
108 109 110
vlc_module_end();


111 112 113 114
/*****************************************************************************
 * Local prototypes, constants, structures
 *****************************************************************************/

115 116 117 118 119 120 121 122
enum
{
    MODE_EXPAND,
    MODE_COLLAPSE,
    MODE_NONE
};

typedef struct stat_list_t stat_list_t;
123

124 125
static ssize_t Read( access_t *, uint8_t *, size_t );
static ssize_t ReadNull( access_t *, uint8_t *, size_t );
126 127 128 129 130 131
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 );


132
static int ReadDir( playlist_t *, const char *psz_name, int i_mode,
133 134 135 136
                    playlist_item_t *, playlist_item_t *, input_item_t *,
                    DIR *handle, stat_list_t *stats );

static DIR *OpenDir (vlc_object_t *obj, const char *psz_name);
137

138 139 140 141 142
/*****************************************************************************
 * Open: open the directory
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
143
    access_t *p_access = (access_t*)p_this;
144

145 146
    DIR *handle = OpenDir (p_this, p_access->psz_path);
    if (handle == NULL)
147
        return VLC_EGENERIC;
Gildas Bazin's avatar
 
Gildas Bazin committed
148

149 150
    p_access->p_sys = (access_sys_t *)handle;

151 152 153 154 155
    p_access->pf_read  = Read;
    p_access->pf_block = NULL;
    p_access->pf_seek  = NULL;
    p_access->pf_control= Control;

156
    /* Force a demux */
157
    p_access->psz_demux = strdup( "directory" );
Gildas Bazin's avatar
 
Gildas Bazin committed
158

159 160 161 162 163 164 165 166
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: close the target
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
167 168 169
    access_t *p_access = (access_t*)p_this;
    DIR *handle = (DIR *)p_access->p_sys;
    closedir (handle);
170 171 172 173 174
}

/*****************************************************************************
 * ReadNull: read the directory
 *****************************************************************************/
175
static ssize_t ReadNull( access_t *p_access, uint8_t *p_buffer, size_t i_len)
176 177 178 179
{
    /* Return fake data */
    memset( p_buffer, 0, i_len );
    return i_len;
180 181 182
}

/*****************************************************************************
183
 * Read: read the directory
184
 *****************************************************************************/
185
static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len)
186
{
187 188
    char               *psz;
    int                 i_mode, i_activity;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
189 190 191 192 193
    char               *psz_name = strdup (p_access->psz_path);

    if( psz_name == NULL )
        return VLC_ENOMEM;

194
    playlist_t         *p_playlist = pl_Yield( p_access );
195 196
    input_thread_t     *p_input = (input_thread_t*)vlc_object_find( p_access, VLC_OBJECT_INPUT, FIND_PARENT );

197
    playlist_item_t    *p_item_in_category;
198 199 200 201 202 203 204 205 206 207 208 209
    input_item_t       *p_current_input;
    playlist_item_t    *p_current;

    if( !p_input )
    {
        msg_Err( p_access, "unable to find input (internal error)" );
        vlc_object_release( p_playlist );
        return VLC_ENOOBJ;
    }

    p_current_input = input_GetItem( p_input );
    p_current = playlist_ItemGetByInput( p_playlist, p_current_input, VLC_FALSE );
210

211
    if( !p_current )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
212
    {
213
        msg_Err( p_access, "unable to find item in playlist" );
214
        vlc_object_release( p_input );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
215
        vlc_object_release( p_playlist );
216 217 218
        return VLC_ENOOBJ;
    }

219
    /* Remove the ending '/' char */
220
    if( psz_name[0] )
221
    {
222 223
        char *ptr = psz_name + strlen (psz_name);
        switch (*--ptr)
224
        {
225 226 227
            case '/':
            case '\\':
                *ptr = '\0';
228
        }
229
    }
230

231
    /* Handle mode */
232 233
    psz = var_CreateGetString( p_access, "recursive" );
    if( *psz == '\0' || !strncmp( psz, "none" , 4 )  )
234
        i_mode = MODE_NONE;
235
    else if( !strncmp( psz, "collapse", 8 )  )
236 237 238
        i_mode = MODE_COLLAPSE;
    else
        i_mode = MODE_EXPAND;
239
    free( psz );
240

241
    p_current->p_input->i_type = ITEM_TYPE_DIRECTORY;
242 243
    p_item_in_category = playlist_ItemToNode( p_playlist, p_current,
                                              VLC_FALSE );
244 245 246 247 248

    i_activity = var_GetInteger( p_playlist, "activity" );
    var_SetInteger( p_playlist, "activity", i_activity +
                    DIRECTORY_ACTIVITY );

249
    ReadDir( p_playlist, psz_name, i_mode, p_current, p_item_in_category,
250
             p_current_input, (DIR *)p_access->p_sys, NULL );
251 252 253 254

    i_activity = var_GetInteger( p_playlist, "activity" );
    var_SetInteger( p_playlist, "activity", i_activity -
                    DIRECTORY_ACTIVITY );
255

256 257
    playlist_Signal( p_playlist );

258
    if( psz_name ) free( psz_name );
259
    vlc_object_release( p_input );
260
    vlc_object_release( p_playlist );
261 262 263

    /* Return fake data forever */
    p_access->pf_read = ReadNull;
264
    return -1;
265 266 267
}

/*****************************************************************************
268
 * Control:
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302
 *****************************************************************************/
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:
303
        case ACCESS_SET_PRIVATE_ID_STATE:
304
        case ACCESS_GET_CONTENT_TYPE:
305 306 307
            return VLC_EGENERIC;

        default:
308
            msg_Warn( p_access, "unimplemented query in control" );
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
            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;
327
}
328

329 330 331 332 333 334 335
/*****************************************************************************
 * Demux: EOF
 *****************************************************************************/
static int Demux( demux_t *p_demux )
{
    return 0;
}
336

337 338 339 340 341
/*****************************************************************************
 * DemuxControl:
 *****************************************************************************/
static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
{
342
    return demux2_vaControlHelper( p_demux->s, 0, 0, 0, 1, i_query, args );
343
}
344

345 346

static int Sort (const char **a, const char **b)
347
{
348
    return strcoll (*a, *b);
349
}
350

351 352 353 354 355 356 357
struct stat_list_t
{
    stat_list_t *parent;
    struct stat st;
};


358 359 360
/*****************************************************************************
 * ReadDir: read a directory and add its content to the list
 *****************************************************************************/
361
static int ReadDir( playlist_t *p_playlist, const char *psz_name,
362
                    int i_mode, playlist_item_t *p_parent,
363
                    playlist_item_t *p_parent_category,
364 365
                    input_item_t *p_current_input,
                    DIR *handle, stat_list_t *stparent )
366
{
367
    char **pp_dir_content = NULL;
368
    int             i_dir_content, i, i_return = VLC_SUCCESS;
369
    playlist_item_t *p_node;
370

371
    char **ppsz_extensions = NULL;
372
    int i_extensions = 0;
373 374
    char *psz_ignore;

375
    struct stat_list_t stself;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
376
#ifndef WIN32
377 378 379 380
    int fd = dirfd (handle);

    if ((fd == -1) || fstat (fd, &stself.st))
    {
381
        msg_Err (p_playlist, "cannot stat `%s': %m", psz_name);
382 383 384 385 386 387 388 389 390 391 392 393 394
        return VLC_EGENERIC;
    }

    for (stat_list_t *stats = stparent; stats != NULL; stats = stats->parent)
    {
        if ((stself.st.st_ino == stats->st.st_ino)
         && (stself.st.st_dev == stats->st.st_dev))
        {
            msg_Warn (p_playlist,
                      "ignoring infinitely recursive directory `%s'",
                      psz_name);
            return VLC_SUCCESS;
        }
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
395
    }
396 397
#else
        /* Windows has st_dev (driver letter - 'A'), but it zeroes st_ino,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
398 399
         * so that the test above will always incorrectly succeed.
         * Besides, Windows does not have dirfd(). */
400 401 402 403
#endif

    stself.parent = stparent;

404
    /* Get the first directory entry */
405
    i_dir_content = utf8_loaddir (handle, &pp_dir_content, NULL, Sort);
406 407
    if( i_dir_content == -1 )
    {
408
        msg_Err (p_playlist, "cannot read `%s': %m", psz_name);
409 410 411 412 413
        return VLC_EGENERIC;
    }
    else if( i_dir_content <= 0 )
    {
        /* directory is empty */
414 415
        msg_Dbg( p_playlist, "%s directory is empty", psz_name );
        free( pp_dir_content );
416 417 418 419 420
        return VLC_SUCCESS;
    }

    /* Build array with ignores */
    psz_ignore = var_CreateGetString( p_playlist, "ignore-filetypes" );
421 422
    if( psz_ignore && *psz_ignore )
    {
423 424
        char *psz_parser = psz_ignore;
        int a;
425

426 427 428 429
        for( a = 0; psz_parser[a] != '\0'; a++ )
        {
            if( psz_parser[a] == ',' ) i_extensions++;
        }
430

431
        ppsz_extensions = (char **)calloc (i_extensions, sizeof (char *));
432

433 434
        for( a = 0; a < i_extensions; a++ )
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
435
            char *tmp, *ptr;
436

437
            while( psz_parser[0] != '\0' && psz_parser[0] == ' ' ) psz_parser++;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
438 439 440 441 442
            ptr = strchr( psz_parser, ',');
            tmp = ( ptr == NULL )
                 ? strdup( psz_parser )
                 : strndup( psz_parser, ptr - psz_parser );

443
            ppsz_extensions[a] = tmp;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
444
            psz_parser = ptr + 1;
445
        }
446
    }
447
    if( psz_ignore ) free( psz_ignore );
448

449 450
    /* While we still have entries in the directory */
    for( i = 0; i < i_dir_content; i++ )
451
    {
452
        const char *entry = pp_dir_content[i];
453
        int i_size_entry = strlen( psz_name ) +
454
                           strlen( entry ) + 2 + 7 /* strlen("file://") */;
455
        char psz_uri[i_size_entry];
456

457
        sprintf( psz_uri, "%s/%s", psz_name, entry);
458

459
        /* if it starts with '.' then forget it */
460
        if (entry[0] != '.')
461
        {
462 463
            DIR *subdir = (i_mode != MODE_COLLAPSE)
                    ? OpenDir (VLC_OBJECT (p_playlist), psz_uri) : NULL;
464

465
            if (subdir != NULL) /* Recurse into subdirectory */
466 467 468
            {
                if( i_mode == MODE_NONE )
                {
469 470 471
                    msg_Dbg( p_playlist, "skipping subdirectory `%s'",
                             psz_uri );
                    closedir (subdir);
472 473
                    continue;
                }
474 475 476 477 478

                msg_Dbg (p_playlist, "creating subdirectory %s", psz_uri);

                p_node = playlist_NodeCreate( p_playlist, entry,
                                              p_parent_category,
479
                                              PLAYLIST_NO_REBUILD, NULL );
480 481 482 483 484 485 486 487 488

                /* If we had the parent in category, the it is now node.
                 * Else, we still don't have  */
                i_return = ReadDir( p_playlist, psz_uri , MODE_EXPAND,
                                    p_node, p_parent_category ? p_node : NULL,
                                    p_current_input, subdir, &stself );
                closedir (subdir);
                if (i_return)
                    break; // error :-(
489 490 491
            }
            else
            {
492
                input_item_t *p_input;
493

494 495
                if( i_extensions > 0 )
                {
496
                    const char *psz_dot = strrchr (entry, '.' );
497
                    if( psz_dot++ && *psz_dot )
498 499 500 501 502 503 504 505 506
                    {
                        int a;
                        for( a = 0; a < i_extensions; a++ )
                        {
                            if( !strcmp( psz_dot, ppsz_extensions[a] ) )
                                break;
                        }
                        if( a < i_extensions )
                        {
507
                            msg_Dbg( p_playlist, "ignoring file %s", psz_uri );
508 509 510 511
                            continue;
                        }
                    }
                }
512

513 514
                memmove (psz_uri + 7, psz_uri, sizeof (psz_uri) - 7);
                memcpy (psz_uri, "file://", 7);
515
                p_input = input_ItemNewWithType( VLC_OBJECT(p_playlist),
516
                                                 psz_uri, entry, 0, NULL,
517
                                                 -1, ITEM_TYPE_FILE );
518
                if (p_input != NULL)
519 520 521
                {
                    if( p_current_input )
                        input_ItemCopyOptions( p_current_input, p_input );
522 523
                    playlist_BothAddInput( p_playlist, p_input,
                                           p_parent_category,
524 525
                                           PLAYLIST_APPEND|PLAYLIST_PREPARSE|
                                           PLAYLIST_NO_REBUILD,
526 527
                                           PLAYLIST_END, NULL, NULL,
                                           VLC_FALSE );
528
                    vlc_gc_decref( p_input );
529
                }
530 531 532
            }
        }
    }
533

534
    for( i = 0; i < i_extensions; i++ )
535
        if( ppsz_extensions[i] ) free( ppsz_extensions[i] );
536
    if( ppsz_extensions ) free( ppsz_extensions );
537

538 539 540
    for( i = 0; i < i_dir_content; i++ )
        if( pp_dir_content[i] ) free( pp_dir_content[i] );
    if( pp_dir_content ) free( pp_dir_content );
541 542

    return i_return;
543
}
544 545 546 547 548 549 550 551 552 553


static DIR *OpenDir (vlc_object_t *obj, const char *path)
{
    msg_Dbg (obj, "opening directory `%s'", path);
    DIR *handle = utf8_opendir (path);
    if (handle == NULL)
    {
        int err = errno;
        if (err != ENOTDIR)
554
            msg_Err (obj, "%s: %m", path);
555 556 557 558 559 560 561 562
        else
            msg_Dbg (obj, "skipping non-directory `%s'", path);
        errno = err;

        return NULL;
    }
    return handle;
}