]> git.sesse.net Git - vlc/blob - modules/access/directory.c
Merge branch 'master' of git@git.videolan.org:vlc
[vlc] / modules / access / directory.c
1 /*****************************************************************************
2  * directory.c: expands a directory (directory: access plug-in)
3  *****************************************************************************
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  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc/vlc.h>
34 #include <vlc_playlist.h>
35 #include <vlc_input.h>
36 #include <vlc_access.h>
37 #include <vlc_demux.h>
38
39 #ifdef HAVE_SYS_TYPES_H
40 #   include <sys/types.h>
41 #endif
42 #ifdef HAVE_SYS_STAT_H
43 #   include <sys/stat.h>
44 #endif
45 #ifdef HAVE_ERRNO_H
46 #   include <errno.h>
47 #endif
48 #ifdef HAVE_FCNTL_H
49 #   include <fcntl.h>
50 #endif
51
52 #ifdef HAVE_UNISTD_H
53 #   include <unistd.h>
54 #elif defined( WIN32 ) && !defined( UNDER_CE )
55 #   include <io.h>
56 #elif defined( UNDER_CE )
57 #   define strcoll strcmp
58 #endif
59
60 #ifdef HAVE_DIRENT_H
61 #   include <dirent.h>
62 #endif
63
64 #include <vlc_charset.h>
65
66 /*****************************************************************************
67  * Module descriptor
68  *****************************************************************************/
69 static int  Open ( vlc_object_t * );
70 static void Close( vlc_object_t * );
71
72 static int  DemuxOpen ( vlc_object_t * );
73
74 #define RECURSIVE_TEXT N_("Subdirectory behavior")
75 #define RECURSIVE_LONGTEXT N_( \
76         "Select whether subdirectories must be expanded.\n" \
77         "none: subdirectories do not appear in the playlist.\n" \
78         "collapse: subdirectories appear but are expanded on first play.\n" \
79         "expand: all subdirectories are expanded.\n" )
80
81 static const char *psz_recursive_list[] = { "none", "collapse", "expand" };
82 static const char *psz_recursive_list_text[] = { N_("none"), N_("collapse"),
83                                                  N_("expand") };
84
85 #define IGNORE_TEXT N_("Ignored extensions")
86 #define IGNORE_LONGTEXT N_( \
87         "Files with these extensions will not be added to playlist when " \
88         "opening a directory.\n" \
89         "This is useful if you add directories that contain playlist files " \
90         "for instance. Use a comma-separated list of extensions." )
91
92 vlc_module_begin();
93     set_category( CAT_INPUT );
94     set_shortname( _("Directory" ) );
95     set_subcategory( SUBCAT_INPUT_ACCESS );
96     set_description( _("Standard filesystem directory input") );
97     set_capability( "access", 55 );
98     add_shortcut( "directory" );
99     add_shortcut( "dir" );
100     add_shortcut( "file" );
101     add_string( "recursive", "expand" , NULL, RECURSIVE_TEXT,
102                 RECURSIVE_LONGTEXT, false );
103       change_string_list( psz_recursive_list, psz_recursive_list_text, 0 );
104     add_string( "ignore-filetypes", "m3u,db,nfo,jpg,gif,sfv,txt,sub,idx,srt,cue",
105                 NULL, IGNORE_TEXT, IGNORE_LONGTEXT, false );
106     set_callbacks( Open, Close );
107
108     add_submodule();
109         set_description( "Directory EOF");
110         set_capability( "demux", 0 );
111         set_callbacks( DemuxOpen, NULL );
112 vlc_module_end();
113
114
115 /*****************************************************************************
116  * Local prototypes, constants, structures
117  *****************************************************************************/
118
119 enum
120 {
121     MODE_EXPAND,
122     MODE_COLLAPSE,
123     MODE_NONE
124 };
125
126 typedef struct stat_list_t stat_list_t;
127
128 static ssize_t Read( access_t *, uint8_t *, size_t );
129 static ssize_t ReadNull( access_t *, uint8_t *, size_t );
130 static int Control( access_t *, int, va_list );
131
132 static int Demux( demux_t *p_demux );
133 static int DemuxControl( demux_t *p_demux, int i_query, va_list args );
134
135
136 static int ReadDir( playlist_t *, const char *psz_name, int i_mode,
137                     playlist_item_t *, playlist_item_t *, input_item_t *,
138                     DIR *handle, stat_list_t *stats );
139
140 static DIR *OpenDir (vlc_object_t *obj, const char *psz_name);
141
142 /*****************************************************************************
143  * Open: open the directory
144  *****************************************************************************/
145 static int Open( vlc_object_t *p_this )
146 {
147     access_t *p_access = (access_t*)p_this;
148
149     if( !p_access->psz_path )
150         return VLC_EGENERIC;
151
152     struct stat st;
153     if( !stat( p_access->psz_path, &st ) && !S_ISDIR( st.st_mode ) )
154         return VLC_EGENERIC;
155
156     DIR *handle = OpenDir (p_this, p_access->psz_path);
157     if (handle == NULL)
158         return VLC_EGENERIC;
159
160     p_access->p_sys = (access_sys_t *)handle;
161
162     p_access->pf_read  = Read;
163     p_access->pf_block = NULL;
164     p_access->pf_seek  = NULL;
165     p_access->pf_control= Control;
166
167     /* Force a demux */
168     p_access->psz_demux = strdup( "directory" );
169
170     return VLC_SUCCESS;
171 }
172
173 /*****************************************************************************
174  * Close: close the target
175  *****************************************************************************/
176 static void Close( vlc_object_t * p_this )
177 {
178     access_t *p_access = (access_t*)p_this;
179     DIR *handle = (DIR *)p_access->p_sys;
180     closedir (handle);
181 }
182
183 /*****************************************************************************
184  * ReadNull: read the directory
185  *****************************************************************************/
186 static ssize_t ReadNull( access_t *p_access, uint8_t *p_buffer, size_t i_len)
187 {
188     /* Return fake data */
189     memset( p_buffer, 0, i_len );
190     return i_len;
191 }
192
193 /*****************************************************************************
194  * Read: read the directory
195  *****************************************************************************/
196 static ssize_t Read( access_t *p_access, uint8_t *p_buffer, size_t i_len)
197 {
198     char               *psz;
199     int                 i_mode, i_activity;
200     char               *psz_name = strdup (p_access->psz_path);
201
202     if( psz_name == NULL )
203         return VLC_ENOMEM;
204
205     playlist_t         *p_playlist = pl_Yield( p_access );
206     input_thread_t     *p_input = (input_thread_t*)vlc_object_find( p_access, VLC_OBJECT_INPUT, FIND_PARENT );
207
208     playlist_item_t    *p_item_in_category;
209     input_item_t       *p_current_input;
210     playlist_item_t    *p_current;
211
212     if( !p_input )
213     {
214         msg_Err( p_access, "unable to find input (internal error)" );
215         vlc_object_release( p_playlist );
216         return VLC_ENOOBJ;
217     }
218
219     p_current_input = input_GetItem( p_input );
220     p_current = playlist_ItemGetByInput( p_playlist, p_current_input, false );
221
222     if( !p_current )
223     {
224         msg_Err( p_access, "unable to find item in playlist" );
225         vlc_object_release( p_input );
226         vlc_object_release( p_playlist );
227         return VLC_ENOOBJ;
228     }
229
230     /* Remove the ending '/' char */
231     if( psz_name[0] )
232     {
233         char *ptr = psz_name + strlen (psz_name);
234         switch (*--ptr)
235         {
236             case '/':
237             case '\\':
238                 *ptr = '\0';
239         }
240     }
241
242     /* Handle mode */
243     psz = var_CreateGetString( p_access, "recursive" );
244     if( *psz == '\0' || !strncmp( psz, "none" , 4 )  )
245         i_mode = MODE_NONE;
246     else if( !strncmp( psz, "collapse", 8 )  )
247         i_mode = MODE_COLLAPSE;
248     else
249         i_mode = MODE_EXPAND;
250     free( psz );
251
252     p_current->p_input->i_type = ITEM_TYPE_DIRECTORY;
253     p_item_in_category = playlist_ItemToNode( p_playlist, p_current,
254                                               false );
255
256     i_activity = var_GetInteger( p_playlist, "activity" );
257     var_SetInteger( p_playlist, "activity", i_activity +
258                     DIRECTORY_ACTIVITY );
259
260     ReadDir( p_playlist, psz_name, i_mode, p_current, p_item_in_category,
261              p_current_input, (DIR *)p_access->p_sys, NULL );
262
263     i_activity = var_GetInteger( p_playlist, "activity" );
264     var_SetInteger( p_playlist, "activity", i_activity -
265                     DIRECTORY_ACTIVITY );
266
267     playlist_Signal( p_playlist );
268
269     free( psz_name );
270     vlc_object_release( p_input );
271     vlc_object_release( p_playlist );
272
273     /* Return fake data forever */
274     p_access->pf_read = ReadNull;
275     return -1;
276 }
277
278 /*****************************************************************************
279  * Control:
280  *****************************************************************************/
281 static int Control( access_t *p_access, int i_query, va_list args )
282 {
283     bool   *pb_bool;
284     int          *pi_int;
285     int64_t      *pi_64;
286
287     switch( i_query )
288     {
289         /* */
290         case ACCESS_CAN_SEEK:
291         case ACCESS_CAN_FASTSEEK:
292         case ACCESS_CAN_PAUSE:
293         case ACCESS_CAN_CONTROL_PACE:
294             pb_bool = (bool*)va_arg( args, bool* );
295             *pb_bool = false;    /* FIXME */
296             break;
297
298         /* */
299         case ACCESS_GET_MTU:
300             pi_int = (int*)va_arg( args, int * );
301             *pi_int = 0;
302             break;
303
304         case ACCESS_GET_PTS_DELAY:
305             pi_64 = (int64_t*)va_arg( args, int64_t * );
306             *pi_64 = DEFAULT_PTS_DELAY * 1000;
307             break;
308
309         /* */
310         case ACCESS_SET_PAUSE_STATE:
311         case ACCESS_GET_TITLE_INFO:
312         case ACCESS_SET_TITLE:
313         case ACCESS_SET_SEEKPOINT:
314         case ACCESS_SET_PRIVATE_ID_STATE:
315         case ACCESS_GET_CONTENT_TYPE:
316             return VLC_EGENERIC;
317
318         default:
319             msg_Warn( p_access, "unimplemented query in control" );
320             return VLC_EGENERIC;
321     }
322     return VLC_SUCCESS;
323 }
324
325 /*****************************************************************************
326  * DemuxOpen:
327  *****************************************************************************/
328 static int DemuxOpen ( vlc_object_t *p_this )
329 {
330     demux_t *p_demux = (demux_t*)p_this;
331
332     if( strcmp( p_demux->psz_demux, "directory" ) )
333         return VLC_EGENERIC;
334
335     p_demux->pf_demux   = Demux;
336     p_demux->pf_control = DemuxControl;
337     return VLC_SUCCESS;
338 }
339
340 /*****************************************************************************
341  * Demux: EOF
342  *****************************************************************************/
343 static int Demux( demux_t *p_demux )
344 {
345     return 0;
346 }
347
348 /*****************************************************************************
349  * DemuxControl:
350  *****************************************************************************/
351 static int DemuxControl( demux_t *p_demux, int i_query, va_list args )
352 {
353     return demux_vaControlHelper( p_demux->s, 0, 0, 0, 1, i_query, args );
354 }
355
356
357 static int Sort (const char **a, const char **b)
358 {
359     return strcoll (*a, *b);
360 }
361
362 struct stat_list_t
363 {
364     stat_list_t *parent;
365     struct stat st;
366 };
367
368
369 /*****************************************************************************
370  * ReadDir: read a directory and add its content to the list
371  *****************************************************************************/
372 static int ReadDir( playlist_t *p_playlist, const char *psz_name,
373                     int i_mode, playlist_item_t *p_parent,
374                     playlist_item_t *p_parent_category,
375                     input_item_t *p_current_input,
376                     DIR *handle, stat_list_t *stparent )
377 {
378     char **pp_dir_content = NULL;
379     int             i_dir_content, i, i_return = VLC_SUCCESS;
380     playlist_item_t *p_node;
381
382     char **ppsz_extensions = NULL;
383     int i_extensions = 0;
384     char *psz_ignore;
385
386     struct stat_list_t stself;
387 #ifndef WIN32
388     int fd = dirfd (handle);
389
390     if ((fd == -1) || fstat (fd, &stself.st))
391     {
392         msg_Err (p_playlist, "cannot stat `%s': %m", psz_name);
393         return VLC_EGENERIC;
394     }
395
396     for (stat_list_t *stats = stparent; stats != NULL; stats = stats->parent)
397     {
398         if ((stself.st.st_ino == stats->st.st_ino)
399          && (stself.st.st_dev == stats->st.st_dev))
400         {
401             msg_Warn (p_playlist,
402                       "ignoring infinitely recursive directory `%s'",
403                       psz_name);
404             return VLC_SUCCESS;
405         }
406     }
407 #else
408         /* Windows has st_dev (driver letter - 'A'), but it zeroes st_ino,
409          * so that the test above will always incorrectly succeed.
410          * Besides, Windows does not have dirfd(). */
411 #endif
412
413     stself.parent = stparent;
414
415     /* Get the first directory entry */
416     i_dir_content = utf8_loaddir (handle, &pp_dir_content, NULL, Sort);
417     if( i_dir_content == -1 )
418     {
419         msg_Err (p_playlist, "cannot read `%s': %m", psz_name);
420         return VLC_EGENERIC;
421     }
422     else if( i_dir_content <= 0 )
423     {
424         /* directory is empty */
425         msg_Dbg( p_playlist, "%s directory is empty", psz_name );
426         free( pp_dir_content );
427         return VLC_SUCCESS;
428     }
429
430     /* Build array with ignores */
431     psz_ignore = var_CreateGetString( p_playlist, "ignore-filetypes" );
432     if( psz_ignore && *psz_ignore )
433     {
434         char *psz_parser = psz_ignore;
435         int a;
436
437         for( a = 0; psz_parser[a] != '\0'; a++ )
438         {
439             if( psz_parser[a] == ',' ) i_extensions++;
440         }
441
442         ppsz_extensions = (char **)calloc (i_extensions, sizeof (char *));
443
444         for( a = 0; a < i_extensions; a++ )
445         {
446             char *tmp, *ptr;
447
448             while( psz_parser[0] != '\0' && psz_parser[0] == ' ' ) psz_parser++;
449             ptr = strchr( psz_parser, ',');
450             tmp = ( ptr == NULL )
451                  ? strdup( psz_parser )
452                  : strndup( psz_parser, ptr - psz_parser );
453
454             ppsz_extensions[a] = tmp;
455             psz_parser = ptr + 1;
456         }
457     }
458     free( psz_ignore );
459
460     /* While we still have entries in the directory */
461     for( i = 0; i < i_dir_content; i++ )
462     {
463         const char *entry = pp_dir_content[i];
464         int i_size_entry = strlen( psz_name ) +
465                            strlen( entry ) + 2 + 7 /* strlen("file://") */;
466         char psz_uri[i_size_entry];
467
468         sprintf( psz_uri, "%s/%s", psz_name, entry);
469
470         /* if it starts with '.' then forget it */
471         if (entry[0] != '.')
472         {
473             DIR *subdir = (i_mode != MODE_COLLAPSE)
474                     ? OpenDir (VLC_OBJECT (p_playlist), psz_uri) : NULL;
475
476             if (subdir != NULL) /* Recurse into subdirectory */
477             {
478                 if( i_mode == MODE_NONE )
479                 {
480                     msg_Dbg( p_playlist, "skipping subdirectory `%s'",
481                              psz_uri );
482                     closedir (subdir);
483                     continue;
484                 }
485
486                 msg_Dbg (p_playlist, "creating subdirectory %s", psz_uri);
487
488                 p_node = playlist_NodeCreate( p_playlist, entry,
489                                               p_parent_category,
490                                               PLAYLIST_NO_REBUILD, NULL );
491
492                 /* If we had the parent in category, the it is now node.
493                  * Else, we still don't have  */
494                 i_return = ReadDir( p_playlist, psz_uri , MODE_EXPAND,
495                                     p_node, p_parent_category ? p_node : NULL,
496                                     p_current_input, subdir, &stself );
497                 closedir (subdir);
498                 if (i_return)
499                     break; // error :-(
500             }
501             else
502             {
503                 input_item_t *p_input;
504
505                 if( i_extensions > 0 )
506                 {
507                     const char *psz_dot = strrchr (entry, '.' );
508                     if( psz_dot++ && *psz_dot )
509                     {
510                         int a;
511                         for( a = 0; a < i_extensions; a++ )
512                         {
513                             if( !strcmp( psz_dot, ppsz_extensions[a] ) )
514                                 break;
515                         }
516                         if( a < i_extensions )
517                         {
518                             msg_Dbg( p_playlist, "ignoring file %s", psz_uri );
519                             continue;
520                         }
521                     }
522                 }
523
524                 memmove (psz_uri + 7, psz_uri, sizeof (psz_uri) - 7);
525                 memcpy (psz_uri, "file://", 7);
526                 p_input = input_ItemNewWithType( VLC_OBJECT(p_playlist),
527                                                  psz_uri, entry, 0, NULL,
528                                                  -1, ITEM_TYPE_FILE );
529                 if (p_input != NULL)
530                 {
531                     if( p_current_input )
532                         input_ItemCopyOptions( p_current_input, p_input );
533                     int i_ret = playlist_BothAddInput( p_playlist, p_input,
534                                            p_parent_category,
535                                            PLAYLIST_APPEND|PLAYLIST_PREPARSE|
536                                            PLAYLIST_NO_REBUILD,
537                                            PLAYLIST_END, NULL, NULL,
538                                            false );
539                     vlc_gc_decref( p_input );
540                     if( i_ret != VLC_SUCCESS )
541                         return VLC_EGENERIC;
542                 }
543             }
544         }
545     }
546
547     for( i = 0; i < i_extensions; i++ )
548         free( ppsz_extensions[i] );
549     free( ppsz_extensions );
550
551     for( i = 0; i < i_dir_content; i++ )
552         free( pp_dir_content[i] );
553     free( pp_dir_content );
554
555     return i_return;
556 }
557
558
559 static DIR *OpenDir (vlc_object_t *obj, const char *path)
560 {
561     msg_Dbg (obj, "opening directory `%s'", path);
562     DIR *handle = utf8_opendir (path);
563     if (handle == NULL)
564     {
565         int err = errno;
566         if (err != ENOTDIR)
567             msg_Err (obj, "%s: %m", path);
568         else
569             msg_Dbg (obj, "skipping non-directory `%s'", path);
570         errno = err;
571
572         return NULL;
573     }
574     return handle;
575 }