9b9705a6508a4aa38615b92b5fac8426627ceca9
[vlc.git] / modules / access / directory.c
1 /*****************************************************************************
2  * directory.c: expands a directory (directory: access plug-in)
3  *****************************************************************************
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  * 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_common.h>
34 #include <vlc_plugin.h>
35 #include <vlc_access.h>
36
37 #ifdef HAVE_SYS_TYPES_H
38 #   include <sys/types.h>
39 #endif
40 #ifdef HAVE_SYS_STAT_H
41 #   include <sys/stat.h>
42 #endif
43
44 #ifdef HAVE_UNISTD_H
45 #   include <unistd.h>
46 #elif defined( WIN32 ) && !defined( UNDER_CE )
47 #   include <io.h>
48 #endif
49
50 #ifdef HAVE_DIRENT_H
51 #   include <dirent.h>
52 #endif
53
54 #include <vlc_charset.h>
55 #include <vlc_url.h>
56
57 /*****************************************************************************
58  * Module descriptor
59  *****************************************************************************/
60 static int  Open ( vlc_object_t * );
61 static void Close( vlc_object_t * );
62
63 #define RECURSIVE_TEXT N_("Subdirectory behavior")
64 #define RECURSIVE_LONGTEXT N_( \
65         "Select whether subdirectories must be expanded.\n" \
66         "none: subdirectories do not appear in the playlist.\n" \
67         "collapse: subdirectories appear but are expanded on first play.\n" \
68         "expand: all subdirectories are expanded.\n" )
69
70 static const char *const psz_recursive_list[] = { "none", "collapse", "expand" };
71 static const char *const psz_recursive_list_text[] = {
72     N_("none"), N_("collapse"), N_("expand") };
73
74 #define IGNORE_TEXT N_("Ignored extensions")
75 #define IGNORE_LONGTEXT N_( \
76         "Files with these extensions will not be added to playlist when " \
77         "opening a directory.\n" \
78         "This is useful if you add directories that contain playlist files " \
79         "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     add_shortcut( "directory" )
88     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     set_callbacks( Open, Close )
96 vlc_module_end ()
97
98
99 /*****************************************************************************
100  * Local prototypes, constants, structures
101  *****************************************************************************/
102
103 enum
104 {
105     MODE_EXPAND,
106     MODE_COLLAPSE,
107     MODE_NONE
108 };
109
110 typedef struct directory_t directory_t;
111 struct directory_t
112 {
113     directory_t *parent;
114     DIR         *handle;
115     char        *uri;
116 #ifndef WIN32
117     struct stat  st;
118 #endif
119     char         path[1];
120 };
121
122 struct access_sys_t
123 {
124     directory_t *current;
125     DIR *handle;
126     char *ignored_exts;
127     int mode;
128     int i_item_count;
129     char *psz_xspf_extension;
130 };
131
132 static block_t *Block( access_t * );
133 static int Control( access_t *, int, va_list );
134
135 /*****************************************************************************
136  * Open: open the directory
137  *****************************************************************************/
138 static int Open( vlc_object_t *p_this )
139 {
140     access_t *p_access = (access_t*)p_this;
141     access_sys_t *p_sys;
142
143     if( !p_access->psz_path )
144         return VLC_EGENERIC;
145
146     DIR *handle;
147     if (strcmp (p_access->psz_path, "-"))
148         handle = utf8_opendir (p_access->psz_path);
149     else
150     {
151 #if 0   /* This won't work yet, it generates paths like "-/music.ogg".
152          * We'd need to use openat() here and in the file access... */
153         int fd = dup (0);
154         handle = fdopendir (fd);
155         if (handle == NULL)
156             close (fd);
157 #else
158         return VLC_EGENERIC;
159 #endif
160     }
161
162     if (handle == NULL)
163         return VLC_EGENERIC;
164
165     p_sys = malloc (sizeof (*p_sys));
166     if (!p_sys)
167     {
168         closedir( handle );
169         return VLC_ENOMEM;
170     }
171
172     p_access->p_sys = p_sys;
173     p_sys->current = NULL;
174     p_sys->handle = handle;
175     p_sys->ignored_exts = var_CreateGetString (p_access, "ignore-filetypes");
176     p_sys->i_item_count = 0;
177     p_sys->psz_xspf_extension = strdup( "" );
178
179     /* Handle mode */
180     char *psz = var_CreateGetString( p_access, "recursive" );
181     if( *psz == '\0' || !strcasecmp( psz, "none" )  )
182         p_sys->mode = MODE_NONE;
183     else if( !strcasecmp( psz, "collapse" )  )
184         p_sys->mode = MODE_COLLAPSE;
185     else
186         p_sys->mode = MODE_EXPAND;
187     free( psz );
188
189     p_access->pf_read  = NULL;
190     p_access->pf_block = Block;
191     p_access->pf_seek  = NULL;
192     p_access->pf_control= Control;
193     free (p_access->psz_demux);
194     p_access->psz_demux = strdup ("xspf-open");
195
196     return VLC_SUCCESS;
197 }
198
199 /*****************************************************************************
200  * Close: close the target
201  *****************************************************************************/
202 static void Close( vlc_object_t * p_this )
203 {
204     access_t *p_access = (access_t*)p_this;
205     access_sys_t *p_sys = p_access->p_sys;
206
207     while (p_sys->current)
208     {
209         directory_t *current = p_sys->current;
210
211         p_sys->current = current->parent;
212         closedir (current->handle);
213         free (current->uri);
214         free (current);
215     }
216     if (p_sys->handle != NULL)
217         closedir (p_sys->handle); /* corner case,:Block() not called ever */
218     free (p_sys->psz_xspf_extension);
219     free (p_sys->ignored_exts);
220     free (p_sys);
221 }
222
223 /**
224  * URI-encodes a file path. The only reserved characters is slash.
225  */
226 static char *encode_path (const char *path)
227 {
228     static const char sep[]= "%2F";
229     char *enc = encode_URI_component (path), *ptr = enc;
230
231     if (enc == NULL)
232         return NULL;
233
234     /* Replace '%2F' with '/'. TODO: extend encode_URI*() */
235     /* (On Windows, both ':' and '\\' will be encoded) */
236     while ((ptr = strstr (ptr, sep)) != NULL)
237     {
238         *ptr++ = '/';
239         memmove (ptr, ptr + 2, strlen (ptr) - 1);
240     }
241     return enc;
242 }
243
244 /* Detect directories that recurse into themselves. */
245 static bool has_inode_loop (const directory_t *dir)
246 {
247 #ifndef WIN32
248     dev_t dev = dir->st.st_dev;
249     ino_t inode = dir->st.st_ino;
250
251     while ((dir = dir->parent) != NULL)
252         if ((dir->st.st_dev == dev) && (dir->st.st_ino == inode))
253             return true;
254 #else
255 # define fstat( fd, st ) (0)
256 #endif
257     return false;
258 }
259
260 static block_t *Block (access_t *p_access)
261 {
262     access_sys_t *p_sys = p_access->p_sys;
263     directory_t *current = p_sys->current;
264
265     if (p_access->info.b_eof)
266         return NULL;
267
268     if (current == NULL)
269     {   /* Startup: send the XSPF header */
270         static const char header[] =
271             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
272             "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\" xmlns:vlc=\"http://www.videolan.org/vlc/playlist/ns/0/\">\n"
273             " <trackList>\n";
274         block_t *block = block_Alloc (sizeof (header) - 1);
275         if (!block)
276             goto fatal;
277         memcpy (block->p_buffer, header, sizeof (header) - 1);
278
279         /* "Open" the base directory */
280         current = malloc (sizeof (*current) + strlen (p_access->psz_path));
281         if (current == NULL)
282         {
283             block_Release (block);
284             goto fatal;
285         }
286         current->parent = NULL;
287         current->handle = p_sys->handle;
288         strcpy (current->path, p_access->psz_path);
289         current->uri = encode_path (current->path);
290         if ((current->uri == NULL)
291          || fstat (dirfd (current->handle), &current->st))
292         {
293             free (current->uri);
294             free (current);
295             block_Release (block);
296             goto fatal;
297         }
298
299         p_sys->handle = NULL;
300         p_sys->current = current;
301         return block;
302     }
303
304     char *entry = utf8_readdir (current->handle);
305     if (entry == NULL)
306     {   /* End of directory, go back to parent */
307         closedir (current->handle);
308         p_sys->current = current->parent;
309         free (current);
310
311         if (p_sys->current == NULL)
312         {   /* End of XSPF playlist */
313             char *footer;
314             int len = asprintf( &footer, " </trackList>\n" \
315                 " <extension application=\"http://www.videolan.org/vlc/playlist/0\">\n" \
316                 "%s" \
317                 " </extension>\n" \
318                 "</playlist>\n", p_sys->psz_xspf_extension );
319             if( len < 0 )
320                 goto fatal;
321
322             block_t *block = block_Alloc ( len );
323             if (!block)
324                 goto fatal;
325             memcpy (block->p_buffer, footer, len);
326             free( footer );
327             p_access->info.b_eof = true;
328             return block;
329         }
330         else
331         {
332             /* This was the end of a "subnode" */
333             /* Write the ID to the extension */
334             char *old_xspf_extension = p_sys->psz_xspf_extension;
335             if (old_xspf_extension == NULL)
336                 goto fatal;
337
338             int len2 = asprintf( &p_sys->psz_xspf_extension, "%s  </vlc:node>\n", old_xspf_extension );
339             if (len2 == -1)
340                 goto fatal;
341             free( old_xspf_extension );
342         }
343         return NULL;
344     }
345
346     /* Skip current, parent and hidden directories */
347     if (entry[0] == '.')
348         return NULL;
349     /* Handle recursion */
350     if (p_sys->mode != MODE_COLLAPSE)
351     {
352         directory_t *sub = malloc (sizeof (*sub) + strlen (current->path) + 1
353                                                  + strlen (entry));
354         if (sub == NULL)
355             return NULL;
356         sprintf (sub->path, "%s/%s", current->path, entry);
357
358         DIR *handle = utf8_opendir (sub->path);
359         if (handle != NULL)
360         {
361             sub->parent = current;
362             sub->handle = handle;
363
364             char *encoded = encode_URI_component (entry);
365             if ((encoded == NULL)
366              || (asprintf (&sub->uri, "%s/%s", current->uri, encoded) == -1))
367                  sub->uri = NULL;
368             free (encoded);
369
370             if ((p_sys->mode == MODE_NONE)
371              || fstat (dirfd (handle), &sub->st)
372              || has_inode_loop (sub)
373              || (sub->uri == NULL))
374             {
375                 closedir (handle);
376                 free (sub);
377                 return NULL;
378             }
379             p_sys->current = sub;
380
381             /* Add node to xspf extension */
382             char *old_xspf_extension = p_sys->psz_xspf_extension;
383             if (old_xspf_extension == NULL)
384                 goto fatal;
385
386             int len2 = asprintf( &p_sys->psz_xspf_extension, "%s  <vlc:node title=\"%s\">\n", old_xspf_extension, entry );
387             if (len2 == -1)
388                 goto fatal;
389             free( old_xspf_extension );
390
391             return NULL;
392         }
393         else
394             free (sub);
395     }
396
397     /* Skip files with ignored extensions */
398     if (p_sys->ignored_exts != NULL)
399     {
400         const char *ext = strrchr (entry, '.');
401         if (ext != NULL)
402         {
403             size_t extlen = strlen (++ext);
404             for (const char *type = p_sys->ignored_exts, *end;
405                  type[0]; type = end + 1)
406             {
407                 end = strchr (type, ',');
408                 if (end == NULL)
409                     end = type + strlen (type);
410
411                 if (type + extlen == end
412                  && !strncasecmp (ext, type, extlen))
413                     return NULL;
414
415                 if (*end == '\0')
416                     break;
417             }
418         }
419     }
420
421     char *encoded = encode_URI_component (entry);
422     free (entry);
423     if (encoded == NULL)
424         goto fatal;
425     int len = asprintf (&entry,
426                         "  <track><location>file://%s/%s</location>\n" \
427                         "   <extension application=\"http://www.videolan.org/vlc/playlist/0\">\n" \
428                         "    <vlc:id>%d</vlc:id>\n" \
429                         "   </extension>\n" \
430                         "  </track>\n",
431                         current->uri, encoded, p_sys->i_item_count++);
432     free (encoded);
433     if (len == -1)
434         goto fatal;
435
436     /* Write the ID to the extension */
437     char *old_xspf_extension = p_sys->psz_xspf_extension;
438     if (old_xspf_extension == NULL)
439         goto fatal;
440
441     int len2 = asprintf( &p_sys->psz_xspf_extension, "%s   <vlc:item tid=\"%i\" />\n",
442                             old_xspf_extension, p_sys->i_item_count-1 );
443     if (len2 == -1)
444         goto fatal;
445     free( old_xspf_extension );
446
447     /* TODO: new block allocator for malloc()ated data */
448     block_t *block = block_Alloc (len);
449     if (!block)
450     {
451         free (entry);
452         goto fatal;
453     }
454     memcpy (block->p_buffer, entry, len);
455     free (entry);
456     return block;
457
458 fatal:
459     p_access->info.b_eof = true;
460     return NULL;
461 }
462
463 /*****************************************************************************
464  * Control:
465  *****************************************************************************/
466 static int Control( access_t *p_access, int i_query, va_list args )
467 {
468     bool   *pb_bool;
469     int          *pi_int;
470     int64_t      *pi_64;
471
472     switch( i_query )
473     {
474         /* */
475         case ACCESS_CAN_SEEK:
476         case ACCESS_CAN_FASTSEEK:
477             pb_bool = (bool*)va_arg( args, bool* );
478             *pb_bool = false;
479             break;
480
481         case ACCESS_CAN_PAUSE:
482         case ACCESS_CAN_CONTROL_PACE:
483             pb_bool = (bool*)va_arg( args, bool* );
484             *pb_bool = true;
485             break;
486
487         /* */
488         case ACCESS_GET_MTU:
489             pi_int = (int*)va_arg( args, int * );
490             *pi_int = 0;
491             break;
492
493         case ACCESS_GET_PTS_DELAY:
494             pi_64 = (int64_t*)va_arg( args, int64_t * );
495             *pi_64 = DEFAULT_PTS_DELAY * 1000;
496             break;
497
498         /* */
499         case ACCESS_SET_PAUSE_STATE:
500         case ACCESS_GET_TITLE_INFO:
501         case ACCESS_SET_TITLE:
502         case ACCESS_SET_SEEKPOINT:
503         case ACCESS_SET_PRIVATE_ID_STATE:
504         case ACCESS_GET_CONTENT_TYPE:
505         case ACCESS_GET_META:
506             return VLC_EGENERIC;
507
508         default:
509             msg_Warn( p_access, "unimplemented query in control" );
510             return VLC_EGENERIC;
511     }
512     return VLC_SUCCESS;
513 }