directory.c 12.7 KB
Newer Older
1 2 3
/*****************************************************************************
 * directory.c: expands a directory (directory: access plug-in)
 *****************************************************************************
4
 * Copyright (C) 2002-2008 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 30 31 32
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

33
#include <vlc_common.h>
34
#include <vlc_plugin.h>
Clément Stenac's avatar
Clément Stenac committed
35
#include <vlc_access.h>
36 37 38 39 40 41 42 43 44 45 46 47 48 49

#ifdef HAVE_SYS_TYPES_H
#   include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
#   include <sys/stat.h>
#endif

#ifdef HAVE_UNISTD_H
#   include <unistd.h>
#elif defined( WIN32 ) && !defined( UNDER_CE )
#   include <io.h>
#endif

50
#ifdef HAVE_DIRENT_H
51 52 53
#   include <dirent.h>
#endif

Clément Stenac's avatar
Clément Stenac committed
54
#include <vlc_charset.h>
55
#include <vlc_url.h>
56

57
/*****************************************************************************
58
 * Module descriptor
59
 *****************************************************************************/
60 61
static int  Open ( vlc_object_t * );
static void Close( vlc_object_t * );
62

63
#define RECURSIVE_TEXT N_("Subdirectory behavior")
64 65
#define RECURSIVE_LONGTEXT N_( \
        "Select whether subdirectories must be expanded.\n" \
66 67 68
        "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" )
69

70 71 72
static const char *const psz_recursive_list[] = { "none", "collapse", "expand" };
static const char *const psz_recursive_list_text[] = {
    N_("none"), N_("collapse"), N_("expand") };
73

Clément Stenac's avatar
Clément Stenac committed
74
#define IGNORE_TEXT N_("Ignored extensions")
75
#define IGNORE_LONGTEXT N_( \
Clément Stenac's avatar
Clément Stenac committed
76 77 78 79
        "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." )
80

81
vlc_module_begin();
82
    set_category( CAT_INPUT );
83
    set_shortname( N_("Directory" ) );
84
    set_subcategory( SUBCAT_INPUT_ACCESS );
85
    set_description( N_("Standard filesystem directory input") );
86
    set_capability( "access", 55 );
87 88
    add_shortcut( "directory" );
    add_shortcut( "dir" );
89
    add_shortcut( "file" );
90
    add_string( "recursive", "expand" , NULL, RECURSIVE_TEXT,
91
                RECURSIVE_LONGTEXT, false );
92
      change_string_list( psz_recursive_list, psz_recursive_list_text, 0 );
93
    add_string( "ignore-filetypes", "m3u,db,nfo,jpg,gif,sfv,txt,sub,idx,srt,cue",
94
                NULL, IGNORE_TEXT, IGNORE_LONGTEXT, false );
95 96 97 98
    set_callbacks( Open, Close );
vlc_module_end();


99 100 101 102
/*****************************************************************************
 * Local prototypes, constants, structures
 *****************************************************************************/

103 104 105 106 107 108 109
enum
{
    MODE_EXPAND,
    MODE_COLLAPSE,
    MODE_NONE
};

110 111 112 113 114 115
typedef struct directory_t directory_t;
struct directory_t
{
    directory_t *parent;
    DIR         *handle;
    char        *uri;
116
#ifndef WIN32
117
    struct stat  st;
118
#endif
119 120
    char         path[1];
};
121

122 123 124 125 126 127 128
struct access_sys_t
{
    directory_t *current;
    DIR *handle;
    char *ignored_exts;
    int mode;
};
129

130 131
static block_t *Block( access_t * );
static int Control( access_t *, int, va_list );
132

133 134 135 136 137
/*****************************************************************************
 * Open: open the directory
 *****************************************************************************/
static int Open( vlc_object_t *p_this )
{
138
    access_t *p_access = (access_t*)p_this;
139
    access_sys_t *p_sys;
140

141 142 143
    if( !p_access->psz_path )
        return VLC_EGENERIC;

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

148 149 150 151 152 153 154 155
    p_sys = malloc (sizeof (*p_sys));
    if (!p_sys)
        return VLC_ENOMEM;

    p_access->p_sys = p_sys;
    p_sys->current = NULL;
    p_sys->handle = handle;
    p_sys->ignored_exts = var_CreateGetString (p_access, "ignore-filetypes");
156

157 158 159 160 161 162 163 164 165 166 167 168
    /* Handle mode */
    char *psz = var_CreateGetString( p_access, "recursive" );
    if( *psz == '\0' || !strcasecmp( psz, "none" )  )
        p_sys->mode = MODE_NONE;
    else if( !strcasecmp( psz, "collapse" )  )
        p_sys->mode = MODE_COLLAPSE;
    else
        p_sys->mode = MODE_EXPAND;
    free( psz );

    p_access->pf_read  = NULL;
    p_access->pf_block = Block;
169 170
    p_access->pf_seek  = NULL;
    p_access->pf_control= Control;
171 172
    free (p_access->psz_demux);
    p_access->psz_demux = strdup ("xspf-open");
Gildas Bazin's avatar
 
Gildas Bazin committed
173

174 175 176 177 178 179 180 181
    return VLC_SUCCESS;
}

/*****************************************************************************
 * Close: close the target
 *****************************************************************************/
static void Close( vlc_object_t * p_this )
{
182
    access_t *p_access = (access_t*)p_this;
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    access_sys_t *p_sys = p_access->p_sys;

    while (p_sys->current)
    {
        directory_t *current = p_sys->current;

        p_sys->current = current->parent;
        closedir (current->handle);
        free (current->uri);
        free (current);
    }
    if (p_sys->handle != NULL)
        closedir (p_sys->handle); /* corner case,:Block() not called ever */
    free (p_sys->ignored_exts);
    free (p_sys);
198 199
}

200 201 202 203
/**
 * URI-encodes a file path. The only reserved characters is slash.
 */
static char *encode_path (const char *path)
204
{
205 206 207 208 209 210 211 212 213 214 215 216 217 218
    static const char sep[]= "%2F";
    char *enc = encode_URI_component (path), *ptr = enc;

    if (enc == NULL)
        return NULL;

    /* Replace '%2F' with '/'. TODO: extend encode_URI*() */
    /* (On Windows, both ':' and '\\' will be encoded) */
    while ((ptr = strstr (ptr, sep)) != NULL)
    {
        *ptr++ = '/';
        memmove (ptr, ptr + 2, strlen (ptr) - 1);
    }
    return enc;
219 220
}

221 222
/* Detect directories that recurse into themselves. */
static bool has_inode_loop (const directory_t *dir)
223
{
224
#ifndef WIN32
225 226
    dev_t dev = dir->st.st_dev;
    ino_t inode = dir->st.st_ino;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
227

228 229 230
    while ((dir = dir->parent) != NULL)
        if ((dir->st.st_dev == dev) && (dir->st.st_ino == inode))
            return true;
231 232 233
#else
# define fstat( fd, st ) (0)
#endif
234 235
    return false;
}
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
236

237 238 239 240
static block_t *Block (access_t *p_access)
{
    access_sys_t *p_sys = p_access->p_sys;
    directory_t *current = p_sys->current;
241

242 243
    if (p_access->info.b_eof)
        return NULL;
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
    if (current == NULL)
    {   /* Startup: send the XSPF header */
        static const char header[] =
            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
            "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\">\n"
            " <trackList>\n";
        block_t *block = block_Alloc (sizeof (header) - 1);
        if (!block)
            goto fatal;
        memcpy (block->p_buffer, header, sizeof (header) - 1);

        /* "Open" the base directory */
        current = malloc (sizeof (*current) + strlen (p_access->psz_path));
        if (current == NULL)
        {
            block_Release (block);
            goto fatal;
        }
        current->parent = NULL;
        current->handle = p_sys->handle;
        strcpy (current->path, p_access->psz_path);
        current->uri = encode_path (current->path);
        if ((current->uri == NULL)
         || fstat (dirfd (current->handle), &current->st))
        {
            free (current->uri);
            free (current);
            block_Release (block);
            goto fatal;
        }
275

276 277 278 279
        p_sys->handle = NULL;
        p_sys->current = current;
        return block;
    }
280

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    char *entry = utf8_readdir (current->handle);
    if (entry == NULL)
    {   /* End of directory, go back to parent */
        closedir (current->handle);
        p_sys->current = current->parent;
        free (current);

        if (p_sys->current == NULL)
        {   /* End of XSPF playlist */
            static const char footer[] =
                " </trackList>\n"
                "</playlist>\n";

            block_t *block = block_Alloc (sizeof (footer) - 1);
            if (!block)
                goto fatal;
            memcpy (block->p_buffer, footer, sizeof (footer) - 1);
            p_access->info.b_eof = true;
            return block;
        }
        return NULL;
302 303
    }

304 305 306 307 308
    /* Skip current and parent directories */
    if (!strcmp (entry, ".") || !strcmp (entry, ".."))
        return NULL;
    /* Handle recursion */
    if (p_sys->mode != MODE_COLLAPSE)
309
    {
310 311 312 313 314 315 316 317
        directory_t *sub = malloc (sizeof (*sub) + strlen (current->path) + 1
                                                 + strlen (entry));
        if (sub == NULL)
            return NULL;
        sprintf (sub->path, "%s/%s", current->path, entry);

        DIR *handle = utf8_opendir (sub->path);
        if (handle != NULL)
318
        {
319 320 321 322 323 324 325 326 327 328 329 330 331 332
            sub->parent = current;
            sub->handle = handle;
            sub->uri = encode_path (sub->path);

            if ((p_sys->mode == MODE_NONE)
             || fstat (dirfd (handle), &sub->st)
             || has_inode_loop (sub)
             || (sub->uri == NULL))
            {
                closedir (handle);
                free (sub);
                return NULL;
            }
            p_sys->current = sub;
333
            return NULL;
334
        }
335 336
        else
            free (sub);
337
    }
338

339 340 341 342 343 344 345 346 347 348 349 350 351
    /* Skip files with ignored extensions */
    if (p_sys->ignored_exts != NULL)
    {
        const char *ext = strrchr (entry, '.');
        if (ext != NULL)
        {
            size_t extlen = strlen (++ext);
            for (const char *type = p_sys->ignored_exts, *end;
                 type[0]; type = end + 1)
            {
                end = strchr (type, ',');
                if (end == NULL)
                    end = type + strlen (type);
352

353 354 355
                if (type + extlen == end
                 && !strncasecmp (ext, type, extlen))
                    return NULL;
356 357 358

                if (*end == '\0')
                    break;
359 360 361
            }
        }
    }
362

363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
    char *encoded = encode_URI_component (entry);
    free (entry);
    if (encoded == NULL)
        goto fatal;
    int len = asprintf (&entry,
                        "  <track><location>file://%s/%s</location></track>\n",
                        current->uri, encoded);
    free (encoded);
    if (len == -1)
        goto fatal;

    /* TODO: new block allocator for malloc()ated data */
    block_t *block = block_Alloc (len);
    if (!block)
    {
        free (entry);
        goto fatal;
    }
    memcpy (block->p_buffer, entry, len);
    free (entry);
    return block;
384

385 386 387
fatal:
    p_access->info.b_eof = true;
    return NULL;
388 389 390
}

/*****************************************************************************
391
 * Control:
392 393 394
 *****************************************************************************/
static int Control( access_t *p_access, int i_query, va_list args )
{
395
    bool   *pb_bool;
396 397 398 399 400 401 402 403
    int          *pi_int;
    int64_t      *pi_64;

    switch( i_query )
    {
        /* */
        case ACCESS_CAN_SEEK:
        case ACCESS_CAN_FASTSEEK:
404 405 406 407
            pb_bool = (bool*)va_arg( args, bool* );
            *pb_bool = false;
            break;

408 409
        case ACCESS_CAN_PAUSE:
        case ACCESS_CAN_CONTROL_PACE:
410
            pb_bool = (bool*)va_arg( args, bool* );
411
            *pb_bool = true;
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
            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:
430
        case ACCESS_SET_PRIVATE_ID_STATE:
431
        case ACCESS_GET_CONTENT_TYPE:
432
        case ACCESS_GET_META:
433 434 435
            return VLC_EGENERIC;

        default:
436
            msg_Warn( p_access, "unimplemented query in control" );
437 438 439 440
            return VLC_EGENERIC;
    }
    return VLC_SUCCESS;
}