directory.c 17.9 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 52 53

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

58
#ifdef HAVE_DIRENT_H
59 60 61
#   include <dirent.h>
#endif

Clément Stenac's avatar
Clément Stenac committed
62
#include <vlc_charset.h>
63

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

70
static int  DemuxOpen ( vlc_object_t * );
71

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

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

Clément Stenac's avatar
Clément Stenac committed
83
#define IGNORE_TEXT N_("Ignored extensions")
84
#define IGNORE_LONGTEXT N_( \
Clément Stenac's avatar
Clément Stenac committed
85 86 87 88
        "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." )
89

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

    add_submodule();
107
        set_description( "Directory EOF");
108 109 110
        set_capability( "demux2", 0 );
        add_shortcut( "directory" );
        set_callbacks( DemuxOpen, NULL );
111 112 113
vlc_module_end();


114 115 116 117
/*****************************************************************************
 * Local prototypes, constants, structures
 *****************************************************************************/

118 119 120 121 122 123 124 125
enum
{
    MODE_EXPAND,
    MODE_COLLAPSE,
    MODE_NONE
};

typedef struct stat_list_t stat_list_t;
126 127 128 129 130 131 132 133 134

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 );


135
static int ReadDir( playlist_t *, const char *psz_name, int i_mode,
136 137 138 139
                    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);
140

141 142 143 144 145
/*****************************************************************************
 * Open: open the directory
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
146
    access_t *p_access = (access_t*)p_this;
147

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

152 153
    p_access->p_sys = (access_sys_t *)handle;

154 155 156 157 158
    p_access->pf_read  = Read;
    p_access->pf_block = NULL;
    p_access->pf_seek  = NULL;
    p_access->pf_control= Control;

159
    /* Force a demux */
160
    p_access->psz_demux = strdup( "directory" );
Gildas Bazin's avatar
 
Gildas Bazin committed
161

162 163 164 165 166 167 168 169
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: close the target
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
170 171 172
    access_t *p_access = (access_t*)p_this;
    DIR *handle = (DIR *)p_access->p_sys;
    closedir (handle);
173 174 175 176 177 178 179 180 181 182
}

/*****************************************************************************
 * 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;
183 184 185
}

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

    if( psz_name == NULL )
        return VLC_ENOMEM;

197 198
    playlist_t         *p_playlist = pl_Yield( p_access );
    playlist_item_t    *p_item_in_category;
Clément Stenac's avatar
Clément Stenac committed
199 200
    input_item_t       *p_current_input = input_GetItem(
                                    (input_thread_t*)p_access->p_parent);
201 202 203
    playlist_item_t    *p_current = playlist_ItemGetByInput( p_playlist,
                                                             p_current_input,
                                                             VLC_FALSE );
204

Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
205 206
    if( p_current == NULL )
    {
207
        msg_Err( p_access, "unable to find item in playlist" );
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
208
        vlc_object_release( p_playlist );
209 210 211
        return VLC_ENOOBJ;
    }

212
    /* Remove the ending '/' char */
213
    if (psz_name[0])
214
    {
215 216
        char *ptr = psz_name + strlen (psz_name);
        switch (*--ptr)
217
        {
218 219 220
            case '/':
            case '\\':
                *ptr = '\0';
221
        }
222
    }
223

224
    /* Handle mode */
225 226
    psz = var_CreateGetString( p_access, "recursive" );
    if( *psz == '\0' || !strncmp( psz, "none" , 4 )  )
227
        i_mode = MODE_NONE;
228
    else if( !strncmp( psz, "collapse", 8 )  )
229 230 231
        i_mode = MODE_COLLAPSE;
    else
        i_mode = MODE_EXPAND;
232
    free( psz );
233

234
    p_current->p_input->i_type = ITEM_TYPE_DIRECTORY;
235 236
    p_item_in_category = playlist_ItemToNode( p_playlist, p_current,
                                              VLC_FALSE );
237 238 239 240 241

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

242
    ReadDir( p_playlist, psz_name, i_mode, p_current, p_item_in_category,
243
             p_current_input, (DIR *)p_access->p_sys, NULL );
244 245 246 247

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

249 250
    playlist_Signal( p_playlist );

251
    if( psz_name ) free( psz_name );
252
    vlc_object_release( p_playlist );
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 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

    /* 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:
295
        case ACCESS_SET_PRIVATE_ID_STATE:
296 297 298
            return VLC_EGENERIC;

        default:
299
            msg_Warn( p_access, "unimplemented query in control" );
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
            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;
318
}
319

320 321 322 323 324 325 326
/*****************************************************************************
 * Demux: EOF
 *****************************************************************************/
static int Demux( demux_t *p_demux )
{
    return 0;
}
327

328 329 330 331 332
/*****************************************************************************
 * DemuxControl:
 *****************************************************************************/
static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
{
333
    return demux2_vaControlHelper( p_demux->s, 0, 0, 0, 1, i_query, args );
334
}
335

336 337

static int Sort (const char **a, const char **b)
338
{
339
    return strcoll (*a, *b);
340
}
341

342 343 344 345 346 347 348
struct stat_list_t
{
    stat_list_t *parent;
    struct stat st;
};


349 350 351
/*****************************************************************************
 * ReadDir: read a directory and add its content to the list
 *****************************************************************************/
352
static int ReadDir( playlist_t *p_playlist, const char *psz_name,
353
                    int i_mode, playlist_item_t *p_parent,
354
                    playlist_item_t *p_parent_category,
355 356
                    input_item_t *p_current_input,
                    DIR *handle, stat_list_t *stparent )
357
{
358
    char **pp_dir_content = NULL;
359
    int             i_dir_content, i, i_return = VLC_SUCCESS;
360
    playlist_item_t *p_node;
361

362
    char **ppsz_extensions = NULL;
363
    int i_extensions = 0;
364 365
    char *psz_ignore;

366
    struct stat_list_t stself;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
367
#ifndef WIN32
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    int fd = dirfd (handle);

    if ((fd == -1) || fstat (fd, &stself.st))
    {
        msg_Err (p_playlist, "cannot stat `%s': %s", psz_name,
                 strerror (errno));
        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
387
    }
388 389
#else
        /* Windows has st_dev (driver letter - 'A'), but it zeroes st_ino,
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
390 391
         * so that the test above will always incorrectly succeed.
         * Besides, Windows does not have dirfd(). */
392 393 394 395
#endif

    stself.parent = stparent;

396
    /* Get the first directory entry */
397
    i_dir_content = utf8_loaddir (handle, &pp_dir_content, NULL, Sort);
398 399
    if( i_dir_content == -1 )
    {
400 401
        msg_Err (p_playlist, "cannot read `%s': %s", psz_name,
                 strerror (errno));
402 403 404 405 406
        return VLC_EGENERIC;
    }
    else if( i_dir_content <= 0 )
    {
        /* directory is empty */
407 408
        msg_Dbg( p_playlist, "%s directory is empty", psz_name );
        free( pp_dir_content );
409 410 411 412 413
        return VLC_SUCCESS;
    }

    /* Build array with ignores */
    psz_ignore = var_CreateGetString( p_playlist, "ignore-filetypes" );
414 415
    if( psz_ignore && *psz_ignore )
    {
416 417
        char *psz_parser = psz_ignore;
        int a;
418

419 420 421 422
        for( a = 0; psz_parser[a] != '\0'; a++ )
        {
            if( psz_parser[a] == ',' ) i_extensions++;
        }
423

424
        ppsz_extensions = (char **)calloc (i_extensions, sizeof (char *));
425

426 427
        for( a = 0; a < i_extensions; a++ )
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
428
            char *tmp, *ptr;
429

430
            while( psz_parser[0] != '\0' && psz_parser[0] == ' ' ) psz_parser++;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
431 432 433 434 435
            ptr = strchr( psz_parser, ',');
            tmp = ( ptr == NULL )
                 ? strdup( psz_parser )
                 : strndup( psz_parser, ptr - psz_parser );

436
            ppsz_extensions[a] = tmp;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
437
            psz_parser = ptr + 1;
438
        }
439
    }
440
    if( psz_ignore ) free( psz_ignore );
441

442 443
    /* While we still have entries in the directory */
    for( i = 0; i < i_dir_content; i++ )
444
    {
445
        const char *entry = pp_dir_content[i];
446
        int i_size_entry = strlen( psz_name ) +
447
                           strlen( entry ) + 2 + 7 /* strlen("file://") */;
448
        char psz_uri[i_size_entry];
449

450
        sprintf( psz_uri, "%s/%s", psz_name, entry);
451

452
        /* if it starts with '.' then forget it */
453
        if (entry[0] != '.')
454
        {
455 456
            DIR *subdir = (i_mode != MODE_COLLAPSE)
                    ? OpenDir (VLC_OBJECT (p_playlist), psz_uri) : NULL;
457

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

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

                p_node = playlist_NodeCreate( p_playlist, entry,
                                              p_parent_category,
                                              PLAYLIST_NO_REBUILD );

                /* 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 :-(
482 483 484
            }
            else
            {
485
                input_item_t *p_input;
486

487 488
                if( i_extensions > 0 )
                {
489
                    const char *psz_dot = strrchr (entry, '.' );
490
                    if( psz_dot++ && *psz_dot )
491 492 493 494 495 496 497 498 499
                    {
                        int a;
                        for( a = 0; a < i_extensions; a++ )
                        {
                            if( !strcmp( psz_dot, ppsz_extensions[a] ) )
                                break;
                        }
                        if( a < i_extensions )
                        {
500
                            msg_Dbg( p_playlist, "ignoring file %s", psz_uri );
501 502 503 504
                            continue;
                        }
                    }
                }
505

506 507
                memmove (psz_uri + 7, psz_uri, sizeof (psz_uri) - 7);
                memcpy (psz_uri, "file://", 7);
508
                p_input = input_ItemNewWithType( VLC_OBJECT(p_playlist),
509
                                                 psz_uri, entry, 0, NULL,
510
                                                 -1, ITEM_TYPE_VFILE );
511
                if (p_input != NULL)
512 513 514
                {
                    if( p_current_input )
                        input_ItemCopyOptions( p_current_input, p_input );
515 516
                    playlist_BothAddInput( p_playlist, p_input,
                                           p_parent_category,
517 518
                                           PLAYLIST_APPEND|PLAYLIST_PREPARSE|
                                           PLAYLIST_NO_REBUILD,
519 520
                                           PLAYLIST_END, NULL, NULL,
                                           VLC_FALSE );
521
                }
522 523 524
            }
        }
    }
525

526
    for( i = 0; i < i_extensions; i++ )
527
        if( ppsz_extensions[i] ) free( ppsz_extensions[i] );
528
    if( ppsz_extensions ) free( ppsz_extensions );
529

530 531 532
    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 );
533 534

    return i_return;
535
}
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554


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)
            msg_Err (obj, "%s: %s", path, strerror (err));
        else
            msg_Dbg (obj, "skipping non-directory `%s'", path);
        errno = err;

        return NULL;
    }
    return handle;
}