directory.c 16.4 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
 * 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
21
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 23 24 25 26
 *****************************************************************************/

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

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

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

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

60 61
#include "charset.h"

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 97
    add_string( "recursive", "expand" , NULL, RECURSIVE_TEXT,
                RECURSIVE_LONGTEXT, VLC_FALSE );
Gildas Bazin's avatar
Gildas Bazin committed
98
      change_string_list( psz_recursive_list, psz_recursive_list_text, 0 );
99
    add_string( "ignore-filetypes", "m3u,db,nfo,jpg,gif,sfv,txt,sub,idx,srt,cue",
100
                NULL, IGNORE_TEXT, IGNORE_LONGTEXT, VLC_FALSE );
101
    set_callbacks( Open, Close );
102 103

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


111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
/*****************************************************************************
 * 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 );


127
static int ReadDir( playlist_t *, const char *psz_name, int i_mode,
128
                    playlist_item_t *, playlist_item_t *, input_item_t * );
129

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

137
    struct stat stat_info;
138

139
#ifdef S_ISDIR
140 141
    if (utf8_stat (p_access->psz_path, &stat_info)
     || !S_ISDIR (stat_info.st_mode))
Gildas Bazin's avatar
 
Gildas Bazin committed
142
#else
143 144
    if( strcmp( p_access->psz_access, "dir") &&
        strcmp( p_access->psz_access, "directory") )
Gildas Bazin's avatar
 
Gildas Bazin committed
145
#endif
146
        return VLC_EGENERIC;
Gildas Bazin's avatar
 
Gildas Bazin committed
147

148 149 150 151 152
    p_access->pf_read  = Read;
    p_access->pf_block = NULL;
    p_access->pf_seek  = NULL;
    p_access->pf_control= Control;

153
    /* Force a demux */
154
    p_access->psz_demux = strdup( "directory" );
Gildas Bazin's avatar
 
Gildas Bazin committed
155

156 157 158 159 160 161 162 163
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: close the target
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
164 165 166 167 168 169 170 171 172 173
}

/*****************************************************************************
 * 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;
174 175 176
}

/*****************************************************************************
177
 * Read: read the directory
178
 *****************************************************************************/
179
static int Read( access_t *p_access, uint8_t *p_buffer, int i_len)
180
{
181 182 183 184 185 186
    char               *psz;
    int                 i_mode, i_activity;
    playlist_t         *p_playlist = pl_Yield( p_access );
    playlist_item_t    *p_item_in_category;
    input_item_t       *p_current_input = ( (input_thread_t*)p_access->p_parent)
                                                       ->input.p_item;
187 188 189
    playlist_item_t    *p_current = playlist_ItemGetByInput( p_playlist,
                                                             p_current_input,
                                                             VLC_FALSE );
190 191 192
    char               *psz_name = strdup (p_access->psz_path);

    if( psz_name == NULL )
193
        return VLC_ENOMEM;
194

195 196 197 198 199
    if( p_current == NULL ) {
        msg_Err( p_access, "unable to find item in playlist" );
        return VLC_ENOOBJ;
    }

200
    /* Remove the ending '/' char */
201
    if (psz_name[0])
202
    {
203 204
        char *ptr = psz_name + strlen (psz_name);
        switch (*--ptr)
205
        {
206 207 208
            case '/':
            case '\\':
                *ptr = '\0';
209
        }
210
    }
211

212
    /* Handle mode */
213 214
    psz = var_CreateGetString( p_access, "recursive" );
    if( *psz == '\0' || !strncmp( psz, "none" , 4 )  )
215
        i_mode = MODE_NONE;
216
    else if( !strncmp( psz, "collapse", 8 )  )
217 218 219
        i_mode = MODE_COLLAPSE;
    else
        i_mode = MODE_EXPAND;
220
    free( psz );
221

222
    msg_Dbg( p_access, "opening directory `%s'", p_access->psz_path );
223

224
    p_current->p_input->i_type = ITEM_TYPE_DIRECTORY;
225 226
    p_item_in_category = playlist_ItemToNode( p_playlist, p_current,
                                              VLC_FALSE );
227 228 229 230 231

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

232 233
    ReadDir( p_playlist, psz_name, i_mode, p_current, p_item_in_category,
             p_current_input );
234 235 236 237

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

239
    if( psz_name ) free( psz_name );
240
    vlc_object_release( p_playlist );
241 242 243 244 245 246 247 248 249 250 251 252 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

    /* 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:
283
        case ACCESS_SET_PRIVATE_ID_STATE:
284 285 286
            return VLC_EGENERIC;

        default:
287
            msg_Warn( p_access, "unimplemented query in control" );
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
            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;
306
}
307

308 309 310 311 312 313 314
/*****************************************************************************
 * Demux: EOF
 *****************************************************************************/
static int Demux( demux_t *p_demux )
{
    return 0;
}
315

316 317 318 319 320
/*****************************************************************************
 * DemuxControl:
 *****************************************************************************/
static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
{
321
    return demux2_vaControlHelper( p_demux->s, 0, 0, 0, 1, i_query, args );
322
}
323

324 325

static int Sort (const char **a, const char **b)
326
{
327
    return strcoll (*a, *b);
328
}
329

330 331 332
/*****************************************************************************
 * ReadDir: read a directory and add its content to the list
 *****************************************************************************/
333
static int ReadDir( playlist_t *p_playlist, const char *psz_name,
334
                    int i_mode, playlist_item_t *p_parent,
335 336
                    playlist_item_t *p_parent_category,
                    input_item_t *p_current_input )
337
{
338
    char **pp_dir_content = NULL;
339
    int             i_dir_content, i, i_return = VLC_SUCCESS;
340
    playlist_item_t *p_node;
341

342
    char **ppsz_extensions = NULL;
343
    int i_extensions = 0;
344 345 346
    char *psz_ignore;

    /* Get the first directory entry */
347
    i_dir_content = utf8_scandir (psz_name, &pp_dir_content, NULL, Sort);
348 349 350 351 352 353 354 355 356 357 358 359 360 361
    if( i_dir_content == -1 )
    {
        msg_Warn( p_playlist, "failed to read directory" );
        return VLC_EGENERIC;
    }
    else if( i_dir_content <= 0 )
    {
        /* directory is empty */
        if( pp_dir_content ) free( pp_dir_content );
        return VLC_SUCCESS;
    }

    /* Build array with ignores */
    psz_ignore = var_CreateGetString( p_playlist, "ignore-filetypes" );
362 363
    if( psz_ignore && *psz_ignore )
    {
364 365
        char *psz_parser = psz_ignore;
        int a;
366

367 368 369 370
        for( a = 0; psz_parser[a] != '\0'; a++ )
        {
            if( psz_parser[a] == ',' ) i_extensions++;
        }
371

372
        ppsz_extensions = (char **)calloc (i_extensions, sizeof (char *));
373

374 375
        for( a = 0; a < i_extensions; a++ )
        {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
376
            char *tmp, *ptr;
377

378
            while( psz_parser[0] != '\0' && psz_parser[0] == ' ' ) psz_parser++;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
379 380 381 382 383
            ptr = strchr( psz_parser, ',');
            tmp = ( ptr == NULL )
                 ? strdup( psz_parser )
                 : strndup( psz_parser, ptr - psz_parser );

384
            ppsz_extensions[a] = tmp;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
385
            psz_parser = ptr + 1;
386
        }
387
    }
388
    if( psz_ignore ) free( psz_ignore );
389

390 391
    /* While we still have entries in the directory */
    for( i = 0; i < i_dir_content; i++ )
392
    {
393
        const char *entry = pp_dir_content[i];
394
        int i_size_entry = strlen( psz_name ) +
395 396
                           strlen( entry ) + 2;
        char psz_uri[i_size_entry];
397

398
        sprintf( psz_uri, "%s/%s", psz_name, entry);
399

400
        /* if it starts with '.' then forget it */
401
        if (entry[0] != '.')
402
        {
403
#if defined( S_ISDIR )
Gildas Bazin's avatar
Gildas Bazin committed
404
            struct stat stat_data;
405

406
            if (!utf8_stat (psz_uri, &stat_data)
407
             && S_ISDIR(stat_data.st_mode) && i_mode != MODE_COLLAPSE )
408 409
#else
            if( 0 )
Gildas Bazin's avatar
Gildas Bazin committed
410
#endif
411
            {
412 413
#if defined( S_ISLNK )
/*
414
 * FIXME: there is a ToCToU race condition here; but it is rather tricky
415 416 417
 * impossible to fix while keeping some kind of portable code, and maybe even
 * in a non-portable way.
 */
418
                if (utf8_lstat (psz_uri, &stat_data)
419 420
                 || S_ISLNK(stat_data.st_mode) )
                {
421
                    msg_Dbg( p_playlist, "skipping directory symlink %s",
422 423 424 425
                             psz_uri );
                    continue;
                }
#endif
426 427
                if( i_mode == MODE_NONE )
                {
428
                    msg_Dbg( p_playlist, "skipping subdirectory %s", psz_uri );
429 430
                    continue;
                }
431
                else if( i_mode == MODE_EXPAND )
432
                {
433
                    msg_Dbg(p_playlist, "creading subdirectory %s", psz_uri );
434

435 436
                    p_node = playlist_NodeCreate (p_playlist, entry,
                                                  p_parent_category);
437

438 439
                    /* If we had the parent in category, the it is now node.
                     * Else, we still don't have  */
440
                    if( ReadDir( p_playlist, psz_uri , MODE_EXPAND,
441 442
                                 p_node, p_parent_category ? p_node : NULL,
                                 p_current_input )
443
                          != VLC_SUCCESS )
444
                    {
445 446
                        i_return = VLC_EGENERIC;
                        break;
447 448 449 450 451
                    }
                }
            }
            else
            {
452
                input_item_t *p_input;
453

454 455
                if( i_extensions > 0 )
                {
456
                    const char *psz_dot = strrchr (entry, '.' );
457
                    if( psz_dot++ && *psz_dot )
458 459 460 461 462 463 464 465 466
                    {
                        int a;
                        for( a = 0; a < i_extensions; a++ )
                        {
                            if( !strcmp( psz_dot, ppsz_extensions[a] ) )
                                break;
                        }
                        if( a < i_extensions )
                        {
467
                            msg_Dbg( p_playlist, "ignoring file %s", psz_uri );
468 469 470 471
                            continue;
                        }
                    }
                }
472

473
                p_input = input_ItemNewWithType( VLC_OBJECT(p_playlist),
474
                                                 psz_uri, entry, 0, NULL,
475
                                                 -1, ITEM_TYPE_VFILE );
476
                if (p_input != NULL)
477 478 479
                {
                    if( p_current_input )
                        input_ItemCopyOptions( p_current_input, p_input );
480 481 482
                    playlist_BothAddInput( p_playlist, p_input,
                                           p_parent_category,
                                           PLAYLIST_APPEND|PLAYLIST_PREPARSE,
483
                                           PLAYLIST_END, NULL, NULL );
484
                }
485 486 487
            }
        }
    }
488

489
    for( i = 0; i < i_extensions; i++ )
490
        if( ppsz_extensions[i] ) free( ppsz_extensions[i] );
491
    if( ppsz_extensions ) free( ppsz_extensions );
492

493 494 495
    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 );
496 497

    return i_return;
498
}