From bc72d967d45c4ad8d0be6516b9a8e358057d403b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Andre Date: Sun, 18 Jan 2009 19:34:32 +0100 Subject: [PATCH] Zip: add a stream_filter and an access The stream_filter does the following job: [zip stream] -->filter--> [xspf playlist] The access is called zip or unzip and takes MRLs of the form: zip://[web-encoded-path]![file-in-zip] As there is some automatic web-form decoding of the MRL, the MRLs in the playlist generated by zipstream are encoded two times. --- modules/access/zip/Modules.am | 6 + modules/access/zip/zip.h | 73 +++ modules/access/zip/zipaccess.c | 425 ++++++++++++++++++ modules/access/zip/zipstream.c | 794 +++++++++++++++++++++++++++++++++ 4 files changed, 1298 insertions(+) create mode 100644 modules/access/zip/Modules.am create mode 100644 modules/access/zip/zip.h create mode 100644 modules/access/zip/zipaccess.c create mode 100644 modules/access/zip/zipstream.c diff --git a/modules/access/zip/Modules.am b/modules/access/zip/Modules.am new file mode 100644 index 0000000000..303c29f206 --- /dev/null +++ b/modules/access/zip/Modules.am @@ -0,0 +1,6 @@ +if HAVE_ZLIB +SOURCES_zip = \ + zipstream.c \ + zipaccess.c \ + $(NULL) +endif diff --git a/modules/access/zip/zip.h b/modules/access/zip/zip.h new file mode 100644 index 0000000000..e6dde5a97e --- /dev/null +++ b/modules/access/zip/zip.h @@ -0,0 +1,73 @@ +/***************************************************************************** + * zip.h: Module (access+demux) to extract different archives, based on zlib + ***************************************************************************** + * Copyright (C) 2007 the VideoLAN team + * $Id$ + * + * Authors: Jean-Philippe André + * + * 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. + * + * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/** ************************************************************************** + * Common includes and shared headers + *****************************************************************************/ + +#ifndef ZIP_ACCESSDEMUX_H +#define ZIP_ACCESSDEMUX_H + +#include +#include +#include +#include +#include +#include +#include +#include "unzip.h" +#include "ioapi.h" + +#include + +#define ZIP_FILENAME_LEN 512 +#define ZIP_BUFFER_LEN 32768 +#define ZIP_SEP "|" +#define ZIP_SEP_CHAR '|' + + +/** ************************************************************************** + * Module access points: stream_filter + *****************************************************************************/ +int StreamOpen( vlc_object_t* ); +void StreamClose( vlc_object_t* ); + +/** ************************************************************************** + * Module access points: access + *****************************************************************************/ +int AccessOpen( vlc_object_t *p_this ); +void AccessClose( vlc_object_t *p_this ); + +/** ************************************************************************** + * zipIO function headers : how to use vlc_stream to read the zip + * Note: static because the implementations differ + *****************************************************************************/ +static void* ZCALLBACK ZipIO_Open( void* opaque, const char* filename, int m ); +static uLong ZCALLBACK ZipIO_Read( void*, void* stream, void* buf, uLong sz ); +static uLong ZCALLBACK ZipIO_Write( void*, void* stream, const void*, uLong ); +static long ZCALLBACK ZipIO_Tell( void*, void* stream ); +static long ZCALLBACK ZipIO_Seek( void*, void* stream, uLong offset, int ori ); +static int ZCALLBACK ZipIO_Close( void*, void* stream ); +static int ZCALLBACK ZipIO_Error( void*, void* stream ); + +#endif /* ZIP_ACCESSDEMUX_H */ diff --git a/modules/access/zip/zipaccess.c b/modules/access/zip/zipaccess.c new file mode 100644 index 0000000000..a30e69640c --- /dev/null +++ b/modules/access/zip/zipaccess.c @@ -0,0 +1,425 @@ +/***************************************************************************** + * zipaccess.c: Module (access) to extract different archives, based on zlib + ***************************************************************************** + * Copyright (C) 2007 the VideoLAN team + * $Id$ + * + * Authors: Jean-Philippe André + * + * 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. + * + * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/** @todo: + * - implement crypto (using url zip://user:password@path-to-archive#ZIP#file + * - read files in zip with long name (use unz_file_info.size_filename) + * - multi-volume archive support ? + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#ifdef HAVE_ZLIB_H + +#include "zip.h" +#include + +/** ************************************************************************** + * This is our own access_sys_t for zip files + *****************************************************************************/ +struct access_sys_t +{ + /* zlib / unzip members */ + unzFile zipFile; + zlib_filefunc_def *fileFunctions; + + /* file in zip information */ + char *psz_fileInzip; +}; + +static int AccessControl( access_t *p_access, int i_query, va_list args ); +static ssize_t AccessRead( access_t *, uint8_t *, size_t ); +static int AccessSeek( access_t *, int64_t ); +static int OpenFileInZip( access_t *p_access, int i_pos ); + +/** ************************************************************************** + * \brief Open access + *****************************************************************************/ +int AccessOpen( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t*)p_this; + access_sys_t *p_sys; + int i_ret = VLC_EGENERIC; + unzFile file = 0; + + char *psz_pathToZip = NULL, *psz_path = NULL, *psz_sep = NULL; + + p_access->p_sys = p_sys = (access_sys_t*) + calloc( sizeof( access_sys_t ), 1 ); + if( !p_sys ) + return VLC_ENOMEM; + + /* Split the MRL */ + psz_path = strdup( p_access->psz_path ); + psz_sep = strchr( psz_path, ZIP_SEP_CHAR ); + if( !psz_sep ) + return VLC_EGENERIC; + + *psz_sep = '\0'; + psz_pathToZip = unescape_URI_duplicate( psz_path ); + p_sys->psz_fileInzip = strdup( psz_sep + 1 ); + + /* Define IO functions */ + zlib_filefunc_def *p_func = (zlib_filefunc_def*) + calloc( 1, sizeof( zlib_filefunc_def ) ); + p_func->zopen_file = ZipIO_Open; + p_func->zread_file = ZipIO_Read; + p_func->zwrite_file = ZipIO_Write; // see comment + p_func->ztell_file = ZipIO_Tell; + p_func->zseek_file = ZipIO_Seek; + p_func->zclose_file = ZipIO_Close; + p_func->zerror_file = ZipIO_Error; + p_func->opaque = p_access; + + /* Open zip archive */ + file = p_access->p_sys->zipFile = unzOpen2( psz_pathToZip, p_func ); + if( !file ) + { + msg_Err( p_access, "not a valid zip archive: '%s'", psz_pathToZip ); + goto exit; + } + + /* Open file in zip */ + OpenFileInZip( p_access, 0 ); + + /* Set callback */ + ACCESS_SET_CALLBACKS( AccessRead, NULL, AccessControl, AccessSeek ); + + /* Get some infos about current file. Maybe we could want some more ? */ + unz_file_info z_info; + unzGetCurrentFileInfo( file, &z_info, NULL, 0, NULL, 0, NULL, 0 ); + + /* Set access informations: size is needed for AccessSeek */ + p_access->info.i_size = z_info.uncompressed_size; + p_access->info.i_pos = 0; + p_access->info.b_eof = false; + + i_ret = VLC_SUCCESS; + +exit: + if( i_ret != VLC_SUCCESS ) + { + if( file ) + { + unzCloseCurrentFile( file ); + unzClose( file ); + } + free( p_sys->psz_fileInzip ); + free( p_sys->fileFunctions ); + free( p_sys ); + } + + free( psz_pathToZip ); + free( psz_path ); + return i_ret; +} + +/** ************************************************************************** + * \brief Close access: free structures + *****************************************************************************/ +void AccessClose( vlc_object_t *p_this ) +{ + access_t *p_access = (access_t*)p_this; + access_sys_t *p_sys = p_access->p_sys; + if( p_sys ) + { + unzFile file = p_sys->zipFile; + if( file ) + { + unzCloseCurrentFile( file ); + unzClose( file ); + } + free( p_sys->psz_fileInzip ); + free( p_sys->fileFunctions ); + free( p_sys ); + } + var_Destroy( p_access, "zip-no-xspf" ); +} + +/** ************************************************************************** + * \brief Control access + *****************************************************************************/ +static int AccessControl( access_t *p_access, int i_query, va_list args ) +{ + bool *pb_bool; + int *pi_int; + int64_t *pi_64; + + switch( i_query ) + { + /* */ + case ACCESS_CAN_SEEK: + case ACCESS_CAN_PAUSE: + case ACCESS_CAN_CONTROL_PACE: + pb_bool = (bool*)va_arg( args, bool* ); + *pb_bool = true; + break; + + case ACCESS_CAN_FASTSEEK: + pb_bool = (bool*)va_arg( args, bool* ); + *pb_bool = false; + 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; + break; + + /* */ + case ACCESS_SET_PAUSE_STATE: + /* Nothing to do */ + break; + + case ACCESS_GET_TITLE_INFO: + case ACCESS_SET_TITLE: + case ACCESS_SET_SEEKPOINT: + case ACCESS_SET_PRIVATE_ID_STATE: + case ACCESS_GET_META: + case ACCESS_GET_PRIVATE_ID_STATE: + case ACCESS_GET_CONTENT_TYPE: + return VLC_EGENERIC; + + default: + msg_Warn( p_access, "unimplemented query %d in control", i_query ); + return VLC_EGENERIC; + + } + return VLC_SUCCESS; +} + +/** ************************************************************************** + * \brief Read access + * Reads current opened file in zip. This does not open the file in zip. + * Return -1 if no data yet, 0 if no more data, else real data read + *****************************************************************************/ +static ssize_t AccessRead( access_t *p_access, uint8_t *p_buffer, size_t sz ) +{ + access_sys_t *p_sys = p_access->p_sys; + assert( p_sys ); + unzFile file = p_sys->zipFile; + if( !file ) + { + msg_Err( p_access, "archive not opened !" ); + return VLC_EGENERIC; + } + + int i_read = 0; + i_read = unzReadCurrentFile( file, p_buffer, sz ); + + p_access->info.i_pos = unztell( file ); + return ( i_read >= 0 ? i_read : VLC_EGENERIC ); +} + +/** ************************************************************************** + * \brief Seek inside zip file + *****************************************************************************/ +static int AccessSeek( access_t *p_access, int64_t seek_len ) +{ + access_sys_t *p_sys = p_access->p_sys; + assert( p_sys ); + unzFile file = p_sys->zipFile; + if( !file ) + { + msg_Err( p_access, "archive not opened !" ); + return VLC_EGENERIC; + } + + /* Reopen file in zip if needed */ + if( p_access->info.i_pos != 0 ) + { + OpenFileInZip( p_access, p_access->info.i_pos + seek_len ); + } + + /* Read seek_len data and drop it */ + int i_seek = 0; + int i_read = 1; + char *p_buffer = ( char* ) calloc( 1, ZIP_BUFFER_LEN ); + while( ( i_seek < seek_len ) && ( i_read > 0 ) ) + { + i_read = ( seek_len - i_seek < ZIP_BUFFER_LEN ) + ? ( seek_len - i_seek ) : ZIP_BUFFER_LEN; + i_read = unzReadCurrentFile( file, p_buffer, i_read ); + if( i_read < 0 ) + { + msg_Warn( p_access, "could not seek in file" ); + free( p_buffer ); + return VLC_EGENERIC; + } + else + { + i_seek += i_read; + } + } + free( p_buffer ); + + p_access->info.i_pos = unztell( file ); + return VLC_SUCCESS; +} + +/** ************************************************************************** + * \brief Open file in zip + *****************************************************************************/ +static int OpenFileInZip( access_t *p_access, int i_pos ) +{ + access_sys_t *p_sys = p_access->p_sys; + unzFile file = p_sys->zipFile; + if( !p_sys->psz_fileInzip ) + { + return VLC_EGENERIC; + } + + i_pos = __MIN( i_pos, 0 ); + p_access->info.i_pos = 0; + + unzCloseCurrentFile( file ); /* returns UNZ_PARAMERROR if file not opened */ + if( unzLocateFile( file, p_sys->psz_fileInzip, 0 ) != UNZ_OK ) + { + msg_Err( p_access, "could not [re]locate file in zip: '%s'", + p_sys->psz_fileInzip ); + return VLC_EGENERIC; + } + if( unzOpenCurrentFile( file ) != UNZ_OK ) + { + msg_Err( p_access, "could not [re]open file in zip: '%s'", + p_sys->psz_fileInzip ); + return VLC_EGENERIC; + } + if( i_pos > 0 ) + return AccessSeek( p_access, i_pos ); + else + return VLC_SUCCESS; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: open (read only) + *****************************************************************************/ +static void* ZCALLBACK ZipIO_Open( void* opaque, const char* file, int mode ) +{ + assert(opaque != NULL); + assert(mode == (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING)); + + access_t *p_access = (access_t*) opaque; + + return stream_UrlNew( p_access, file ); +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: read + *****************************************************************************/ +static uLong ZCALLBACK ZipIO_Read( void* opaque, void* stream, + void* buf, uLong size ) +{ + (void)opaque; + //access_t *p_access = (access_t*) opaque; + //msg_Dbg(p_access, "read %d", size); + return stream_Read( (stream_t*) stream, buf, size ); +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: write (assert insteadof segfault) + *****************************************************************************/ +static uLong ZCALLBACK ZipIO_Write( void* opaque, void* stream, + const void* buf, uLong size ) +{ + (void)opaque; (void)stream; (void)buf; (void)size; + int zip_access_cannot_write_this_should_not_happen = 0; + assert(zip_access_cannot_write_this_should_not_happen); + return 0; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: tell + *****************************************************************************/ +static long ZCALLBACK ZipIO_Tell( void* opaque, void* stream ) +{ + (void)opaque; + int64_t i64_tell = stream_Tell( (stream_t*) stream ); + //access_t *p_access = (access_t*) opaque; + //msg_Dbg(p_access, "tell %" PRIu64, i64_tell); + return (long)i64_tell; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: seek + *****************************************************************************/ +static long ZCALLBACK ZipIO_Seek( void* opaque, void* stream, + uLong offset, int origin ) +{ + (void)opaque; + //access_t *p_access = (access_t*) opaque; + int64_t pos = offset; + switch( origin ) + { + case SEEK_CUR: + pos += stream_Tell( (stream_t*) stream ); + break; + case SEEK_SET: + break; + case SEEK_END: + pos += stream_Size( (stream_t*) stream ); + break; + default: + return -1; + } + //msg_Dbg( p_access, "seek (%d,%d): %" PRIu64, offset, origin, pos ); + stream_Seek( (stream_t*) stream, pos ); + /* Note: in unzip.c, unzlocal_SearchCentralDir seeks to the end of + the stream, which is doable but returns an error in VLC. + That's why we always assume this was OK. FIXME */ + return 0; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: close + *****************************************************************************/ +static int ZCALLBACK ZipIO_Close( void* opaque, void* stream ) +{ + (void)opaque; + stream_Delete( (stream_t*) stream ); + return 0; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: test error (man 3 ferror) + *****************************************************************************/ +static int ZCALLBACK ZipIO_Error( void* opaque, void* stream ) +{ + (void)opaque; + (void)stream; + //msg_Dbg( p_access, "error" ); + return 0; +} + + + +#else +# error Can not compile zip demuxer without zlib support +#endif diff --git a/modules/access/zip/zipstream.c b/modules/access/zip/zipstream.c new file mode 100644 index 0000000000..af2adcc1c4 --- /dev/null +++ b/modules/access/zip/zipstream.c @@ -0,0 +1,794 @@ +/***************************************************************************** + * zipstream.c: stream_filter that creates a XSPF playlist from a Zip archive + ***************************************************************************** + * Copyright (C) 2007 the VideoLAN team + * $Id$ + * + * Authors: Jean-Philippe André + * + * 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. + * + * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/** ************************************************************************** + * Preamble + *****************************************************************************/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "zip.h" +#include + +/* FIXME remove */ +#include + +#define FILENAME_TEXT N_( "Media in Zip" ) +#define FILENAME_LONGTEXT N_( "Path to the media in the Zip archive" ) + +/** ************************************************************************** + * Module descriptor + *****************************************************************************/ +vlc_module_begin() + set_shortname( "Zip" ) + set_category( CAT_INPUT ) + set_subcategory( SUBCAT_INPUT_STREAM_FILTER ) + set_description( _( "Zip files filter" ) ) + set_capability( "stream_filter", 1 ) + set_callbacks( StreamOpen, StreamClose ) + add_submodule() + set_subcategory( SUBCAT_INPUT_ACCESS ) + set_description( _( "Zip access" ) ) + set_capability( "access", 70 ) + add_shortcut( "unzip" ) + add_shortcut( "zip" ) + set_callbacks( AccessOpen, AccessClose ) +vlc_module_end() + +/** ************************************************************************* + * Local prototypes + ****************************************************************************/ +static int Read ( stream_t *, void *p_read, unsigned int i_read ); +static int Peek ( stream_t *, const uint8_t **pp_peek, unsigned int i_peek ); +static int Control( stream_t *, int i_query, va_list ); + +typedef struct node node; +typedef struct item item; + +static int CreatePlaylist( stream_t *s, char **pp_buffer ); +static int GetFilesInZip( stream_t*, unzFile, vlc_array_t*, vlc_array_t* ); +static node* findOrCreateParentNode( node *root, const char *fullpath ); +static int WriteXSPF( char **pp_buffer, vlc_array_t *p_filenames, + const char *psz_zippath ); +static int nodeToXSPF( char **pp_buffer, node *n, bool b_root ); +static node* findOrCreateParentNode( node *root, const char *fullpath ); + +/** ************************************************************************** + * Struct definitions + *****************************************************************************/ +struct stream_sys_t +{ + /* zlib / unzip members */ + unzFile zipFile; + zlib_filefunc_def *fileFunctions; + char *psz_path; + + /* xspf data */ + char *psz_xspf; + size_t i_len; + size_t i_pos; +}; + +struct item { + int id; + item *next; +}; + +struct node { + char *name; + item *media; + node *child; + node *next; +}; + +/** ************************************************************************** + * Some helpers + *****************************************************************************/ + +inline static node* new_node( char *name ) +{ + node *n = (node*) calloc( 1, sizeof(node) ); + n->name = convert_xml_special_chars( name ); + return n; +} + +inline static item* new_item( int id ) +{ + item *media = (item*) calloc( 1, sizeof(item) ); + media->id = id; + return media; +} + +inline static void free_all_node( node *root ) +{ + while( root ) + { + free_all_node( root->child ); + free( root->name ); + node *tmp = root->next; + free( root ); + root = tmp; + } +} + +/* Allocate strcat and format */ +static int astrcatf( char **ppsz_dest, const char *psz_fmt_src, ... ) +{ + va_list args; + va_start( args, psz_fmt_src ); + + char *psz_tmp; + int i_ret = vasprintf( &psz_tmp, psz_fmt_src, args ); + if( i_ret == -1 ) return -1; + + va_end( args ); + + int i_len = strlen( *ppsz_dest ) + strlen( psz_tmp ) + 1; + char *psz_out = realloc( *ppsz_dest, i_len ); + if( !psz_out ) return -1; + + strcat( psz_out, psz_tmp ); + free( psz_tmp ); + + *ppsz_dest = psz_out; + return i_len; +} + +/** ************************************************************************** + * Zip file identifier + *****************************************************************************/ +static const uint8_t p_zip_marker[] = { 0x50, 0x4b, 0x03, 0x04 }; // "PK^C^D" +static const int i_zip_marker = 4; + + +/** ************************************************************************** + * Open + *****************************************************************************/ +int StreamOpen( vlc_object_t *p_this ) +{ + stream_t *s = (stream_t*) p_this; + stream_sys_t *p_sys; + + /* Verify file format */ + const uint8_t *p_peek; + if( stream_Peek( s->p_source, &p_peek, i_zip_marker ) < i_zip_marker ) + return VLC_EGENERIC; + if( memcmp( p_peek, p_zip_marker, i_zip_marker ) ) + return VLC_EGENERIC; + + s->p_sys = p_sys = calloc( sizeof( *p_sys ), 1 ); + if( !p_sys ) + return VLC_ENOMEM; + + s->pf_read = Read; + s->pf_peek = Peek; + s->pf_control = Control; + + p_sys->fileFunctions = ( zlib_filefunc_def * ) + calloc( 1, sizeof( zlib_filefunc_def ) ); + if( !p_sys->fileFunctions ) + { + free( p_sys ); + return VLC_ENOMEM; + } + p_sys->fileFunctions->zopen_file = ZipIO_Open; + p_sys->fileFunctions->zread_file = ZipIO_Read; + p_sys->fileFunctions->zwrite_file = ZipIO_Write; + p_sys->fileFunctions->ztell_file = ZipIO_Tell; + p_sys->fileFunctions->zseek_file = ZipIO_Seek; + p_sys->fileFunctions->zclose_file = ZipIO_Close; + p_sys->fileFunctions->zerror_file = ZipIO_Error; + p_sys->fileFunctions->opaque = ( void * ) s; + p_sys->zipFile = unzOpen2( NULL /* path */, p_sys->fileFunctions ); + if( !p_sys->zipFile ) + { + msg_Warn( s, "unable to open file" ); + free( p_sys ); + free( p_sys->fileFunctions ); + return VLC_EGENERIC; + } + + /* Find the stream uri */ + /* FIXME FIXME FIXME */ + input_thread_t *p_input_thread = (input_thread_t*) + vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT ); + if( !p_input_thread ) + { + free( p_sys ); + free( p_sys->fileFunctions ); + return VLC_EGENERIC; + } + input_item_t *p_input_item = input_GetItem( p_input_thread ); + if( !p_input_item ) + { + free( p_sys ); + free( p_sys->fileFunctions ); + return VLC_EGENERIC; + } + s->p_sys->psz_path = strdup( p_input_item->psz_uri ); + vlc_gc_decref( p_input_item ); +// vlc_object_release( p_input_thread ); + /* FIXME FIXME FIXME */ + + return VLC_SUCCESS; +} + +/** ************************************************************************* + * Close + ****************************************************************************/ +void StreamClose( vlc_object_t *p_this ) +{ + stream_t *s = (stream_t*)p_this; + stream_sys_t *p_sys = s->p_sys; + + free( p_sys->fileFunctions ); + free( p_sys->psz_xspf ); + free( p_sys->psz_path ); + free( p_sys ); +} + +/** ************************************************************************* + * Stream filters functions + ****************************************************************************/ + +/** ************************************************************************* + * Read + ****************************************************************************/ +static int Read( stream_t *s, void *p_read, unsigned int i_read ) +{ + stream_sys_t *p_sys = s->p_sys; + + if( !p_read ) return 0; + + /* Fill the buffer */ + if( p_sys->psz_xspf == NULL ) + { + int i_ret = CreatePlaylist( s, &p_sys->psz_xspf ); + if( i_ret < 0 ) + return -1; + p_sys->i_len = strlen( p_sys->psz_xspf ); + p_sys->i_pos = 0; + } + + /* Read the buffer */ + int i_len = __MIN( i_read, p_sys->i_len - p_sys->i_pos ); + memcpy( p_read, p_sys->psz_xspf + p_sys->i_pos, i_len ); + p_sys->i_pos += i_len; + + return i_len; +} + +/** ************************************************************************* + * Peek + ****************************************************************************/ +static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned int i_peek ) +{ + stream_sys_t *p_sys = s->p_sys; + + /* Fill the buffer */ + if( p_sys->psz_xspf == NULL ) + { + int i_ret = CreatePlaylist( s, &p_sys->psz_xspf ); + if( i_ret < 0 ) + return -1; + p_sys->i_len = strlen( p_sys->psz_xspf ); + p_sys->i_pos = 0; + } + + + /* Point to the buffer */ + int i_len = __MIN( i_peek, p_sys->i_len - p_sys->i_pos ); + *pp_peek = (uint8_t*) p_sys->psz_xspf + p_sys->i_pos; + + return i_len; +} + +/** ************************************************************************* + * Control + ****************************************************************************/ +static int Control( stream_t *s, int i_query, va_list args ) +{ + stream_sys_t *p_sys = s->p_sys; + + switch( i_query ) + { + case STREAM_SET_POSITION: + { + int64_t i_position = (int64_t)va_arg( args, int64_t ); + if( i_position >= p_sys->i_len ) + return VLC_EGENERIC; + else + { + p_sys->i_len = (size_t) i_position; + return VLC_SUCCESS; + } + } + + case STREAM_GET_POSITION: + { + int64_t *pi_position = (int64_t*)va_arg( args, int64_t* ); + *pi_position = p_sys->i_pos; + return VLC_SUCCESS; + } + + case STREAM_GET_SIZE: + { + int64_t *pi_size = (int64_t*)va_arg( args, int64_t* ); + *pi_size = (int64_t) p_sys->i_len; + return VLC_SUCCESS; + } + + case STREAM_GET_MTU: + { + int *pi_mtu = (int*)va_arg( args, int* ); + *pi_mtu = 0; + return VLC_SUCCESS; + } + + case STREAM_GET_CONTENT_TYPE: + return VLC_EGENERIC; + + case STREAM_UPDATE_SIZE: + case STREAM_CONTROL_ACCESS: + case STREAM_CAN_SEEK: + case STREAM_CAN_FASTSEEK: + case STREAM_SET_RECORD_STATE: + return stream_vaControl( s->p_source, i_query, args ); + + default: + return VLC_EGENERIC; + } +} + +static int CreatePlaylist( stream_t *s, char **pp_buffer ) +{ + /* Get some infos about zip archive */ + int i_ret = 0; + unzFile file = s->p_sys->zipFile; + vlc_array_t *p_filenames = vlc_array_new(); /* Will contain char* */ + + /* List all file names in Zip archive */ + i_ret = GetFilesInZip( s, file, p_filenames, NULL ); + if( i_ret < 0 ) + { + unzClose( file ); + i_ret = -1; + goto exit; + } + + // msg_Dbg( s, "%d files in Zip", vlc_array_count( p_filenames ) ); + + /* Close archive */ + unzClose( file ); + s->p_sys->zipFile = NULL; + + /* Construct the xspf playlist */ + i_ret = WriteXSPF( pp_buffer, p_filenames, s->p_sys->psz_path ); + if( i_ret > 0 ) + i_ret = 1; + else if( i_ret < 0 ) + i_ret = -1; + +exit: + for( int i = 0; i < vlc_array_count( p_filenames ); i++ ) + { + free( vlc_array_item_at_index( p_filenames, i ) ); + } + vlc_array_destroy( p_filenames ); + return i_ret; +} + + +/** ************************************************************************** + * Zip utility functions + *****************************************************************************/ + +/** ************************************************************************** + * \brief List files in zip and append their names to p_array + * \param p_this + * \param file Opened zip file + * \param p_array vlc_array_t which will receive all filenames + * + * In case of error, returns VLC_EGENERIC. + * In case of success, returns number of files found, and goes back to first file. + *****************************************************************************/ +static int GetFilesInZip( stream_t *p_this, unzFile file, + vlc_array_t *p_filenames, vlc_array_t *p_fileinfos ) +{ + if( !p_filenames || !p_this ) + return VLC_EGENERIC; + + int i_ret = 0; + + /* Get global info */ + unz_global_info info; + + if( unzGetGlobalInfo( file, &info ) != UNZ_OK ) + { + msg_Warn( p_this, "this is not a valid zip archive" ); + return VLC_EGENERIC; + } + + /* Go to first file in archive */ + unzGoToFirstFile( file ); + + /* Get info about each file */ + for( unsigned long i = 0; i < info.number_entry; i++ ) + { + char *psz_fileName = calloc( ZIP_FILENAME_LEN, 1 ); + unz_file_info *p_fileInfo = calloc( 1, sizeof( unz_file_info ) ); + + if( !p_fileInfo || !psz_fileName ) + { + msg_Warn( p_this, "not enough memory" ); + return VLC_ENOMEM; + } + + if( unzGetCurrentFileInfo( file, p_fileInfo, psz_fileName, + ZIP_FILENAME_LEN, NULL, 0, NULL, 0 ) + != UNZ_OK ) + { + msg_Warn( p_this, "can't get info about file in zip" ); + return VLC_EGENERIC; + } + + if( p_filenames ) + vlc_array_append( p_filenames, strdup( psz_fileName ) ); + free( psz_fileName ); + + if( p_fileinfos ) + vlc_array_append( p_fileinfos, p_fileInfo ); + else + free( p_fileInfo ); + + if( i < ( info.number_entry - 1 ) ) + { + /* Go the next file in the archive */ + if( unzGoToNextFile( file ) != UNZ_OK ) + { + msg_Warn( p_this, "can't go to next file in zip" ); + return VLC_EGENERIC; + } + } + + i_ret++; + } + + /* i_ret should be equal to info.number_entry */ + unzGoToFirstFile( file ); + return i_ret; +} + + +/** ************************************************************************** + * XSPF generation functions + *****************************************************************************/ + +/** ************************************************************************** + * \brief Write the XSPF playlist given the list of files + *****************************************************************************/ +static int WriteXSPF( char **pp_buffer, vlc_array_t *p_filenames, + const char *psz_zippath ) +{ + char *psz_zip = strrchr( psz_zippath, DIR_SEP_CHAR ); + psz_zip = convert_xml_special_chars( psz_zip ? (psz_zip+1) : psz_zippath ); + + if( asprintf( pp_buffer, "\n" + "\n" + " %s\n" + " \n", psz_zip ) == -1) + return -1; + + /* Root node */ + node *playlist = new_node( psz_zip ); + + /* Web-Encode the URI and append '!' */ + char *psz_pathtozip = vlc_UrlEncode( psz_zippath ); + if( astrcatf( &psz_pathtozip, ZIP_SEP ) < 0 ) return -1; + + int i_track = 0; + for( int i = 0; i < vlc_array_count( p_filenames ); ++i ) + { + char *psz_name = (char*) vlc_array_item_at_index( p_filenames, i ); + int i_len = strlen( psz_name ); + + if( !i_len ) continue; + + /* Is it a folder ? */ + if( psz_name[i_len-1] == '/' ) + { + /* Do nothing */ + } + else /* File */ + { + /* Extract file name */ + char *psz_file = strrchr( psz_name, '/' ); + psz_file = convert_xml_special_chars( psz_file ? + (psz_file+1) : psz_name ); + + /* Build full MRL */ + char *psz_path = strdup( psz_pathtozip ); + if( astrcatf( &psz_path, psz_name ) < 0 ) return -1; + + /* Double url-encode */ + char *psz_tmp = psz_path; + psz_path = vlc_UrlEncode( psz_tmp ); + free( psz_tmp ); + + /* Track information */ + if( astrcatf( pp_buffer, + " \n" + " zip://%s\n" + " %s\n" + " \n" + " %d\n" + " \n" + " \n", + psz_path, psz_file, i_track ) < 0 ) return -1; + + free( psz_file ); + free( psz_path ); + + /* Find the parent node */ + node *parent = findOrCreateParentNode( playlist, psz_name ); + assert( parent ); + + /* Add the item to this node */ + item *tmp = parent->media; + if( !tmp ) + { + parent->media = new_item( i_track ); + } + else + { + while( tmp->next ) + { + tmp = tmp->next; + } + tmp->next = new_item( i_track ); + } + + ++i_track; + } + } + + free( psz_pathtozip ); + + /* Close tracklist, open the extension */ + if( astrcatf( pp_buffer, + " \n" + " \n" + ) < 0 ) return -1; + + /* Write the tree */ + if( nodeToXSPF( pp_buffer, playlist, true ) < 0 ) return -1; + + /* Close extension and playlist */ + if( astrcatf( pp_buffer, " \n\n" ) < 0 ) return -1; + + /* printf( "%s", *pp_buffer ); */ + + free_all_node( playlist ); + + return VLC_SUCCESS; +} + +/** ************************************************************************** + * \brief Recursively convert a node to its XSPF representation + *****************************************************************************/ +static int nodeToXSPF( char **pp_buffer, node *n, bool b_root ) +{ + if( !b_root ) + { + if( astrcatf( pp_buffer, " \n", n->name ) < 0 ) + return -1; + } + if( n->child ) + nodeToXSPF( pp_buffer, n->child, false ); + item *i = n->media; + while( i ) + { + if( astrcatf( pp_buffer, " \n", i->id ) < 0 ) + return -1; + i = i->next; + } + if( !b_root ) + { + if( astrcatf( pp_buffer, " \n" ) < 0 ) + return -1; + } + return VLC_SUCCESS; +} + +/** ************************************************************************** + * \brief Either create or find the parent node of the item + *****************************************************************************/ +static node* findOrCreateParentNode( node *root, const char *fullpath ) +{ + char *folder; + char *path = strdup( fullpath ); + folder = path; + + assert( root ); + + char *sep = strchr( folder, '/' ); + if( !sep ) + { + free( path ); + return root; + } + + *sep = '\0'; + ++sep; + + node *current = root->child; + + while( current ) + { + if( !strcmp( current->name, folder ) ) + { + /* We found the folder, go recursively deeper */ + return findOrCreateParentNode( current, sep ); + } + current = current->next; + } + + /* If we are here, then it means that we did not find the parent */ + node *ret = new_node( folder ); + if( !root->child ) + root->child = ret; + else + { + current = root->child; + while( current->next ) + { + current = current->next; + } + current->next = ret; + } + + /* And now, create the subfolders */ + ret = findOrCreateParentNode( ret, sep ); + + free( path ); + return ret; +} + + +/** ************************************************************************** + * ZipIO function definitions : how to use vlc_stream to read the zip + *****************************************************************************/ + +/** ************************************************************************** + * \brief interface for unzip module to open a file using a vlc_stream + * \param opaque + * \param filename + * \param mode how to open the file (read/write ?). We support only read + * \return opaque + *****************************************************************************/ +static void ZCALLBACK *ZipIO_Open( void *opaque, const char *file, int mode ) +{ + (void) file; + stream_t *s = (stream_t*) opaque; + if( mode & ( ZLIB_FILEFUNC_MODE_CREATE | ZLIB_FILEFUNC_MODE_WRITE ) ) + { + msg_Dbg( s, "ZipIO_Open: we cannot write into zip files" ); + return NULL; + } + return s; +} + +/** ************************************************************************** + * \brief read something from stream into buffer + * \param opaque should be the stream + * \param stream stream created by ZipIO_Open + * \param buf buffer to read the file + * \param size length of this buffer + * \return return the number of bytes read (<= size) + *****************************************************************************/ +static unsigned long ZCALLBACK ZipIO_Read( void *opaque, void *stream, + void *buf, unsigned long size ) +{ + (void) stream; + stream_t *s = (stream_t*) opaque; + return (unsigned long) stream_Read( s->p_source, buf, (int) size ); +} + +/** ************************************************************************** + * \brief tell size of stream + * \param opaque should be the stream + * \param stream stream created by ZipIO_Open + * \return size of the file / stream + * ATTENTION: this is not stream_Tell, but stream_Size ! + *****************************************************************************/ +static long ZCALLBACK ZipIO_Tell( void *opaque, void *stream ) +{ + (void) stream; + stream_t *s = (stream_t*) opaque; + return (long) stream_Size( s->p_source ); /* /!\ not stream_Tell /!\ */ +} + +/** ************************************************************************** + * \brief seek in the stream + * \param opaque should be the stream + * \param stream stream created by ZipIO_Open + * \param offset positive offset to seek + * \param origin current position in stream + * \return ¿ VLC_SUCCESS or an error code ? + *****************************************************************************/ +static long ZCALLBACK ZipIO_Seek ( void *opaque, void *stream, + unsigned long offset, int origin ) +{ + (void) stream; + stream_t *s = (stream_t*) opaque; + long l_ret; + + uint64_t pos = offset + origin; + l_ret = (long) stream_Seek( s->p_source, pos ); + return l_ret; +} + +/** ************************************************************************** + * \brief close the stream + * \param opaque should be the stream + * \param stream stream created by ZipIO_Open + * \return always VLC_SUCCESS + * This closes zip archive + *****************************************************************************/ +static int ZCALLBACK ZipIO_Close ( void *opaque, void *stream ) +{ + (void) stream; + (void) opaque; +// stream_t *s = (stream_t*) opaque; +// if( p_demux->p_sys && p_demux->p_sys->zipFile ) +// p_demux->p_sys->zipFile = NULL; +// stream_Seek( s->p_source, 0 ); + return VLC_SUCCESS; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: write (assert insteadof segfault) + *****************************************************************************/ +static uLong ZCALLBACK ZipIO_Write( void* opaque, void* stream, + const void* buf, uLong size ) +{ + (void)opaque; (void)stream; (void)buf; (void)size; + int ERROR_zip_cannot_write_this_should_not_happen = 0; + assert( ERROR_zip_cannot_write_this_should_not_happen ); + return 0; +} + +/** ************************************************************************** + * \brief I/O functions for the ioapi: test error (man 3 ferror) + *****************************************************************************/ +static int ZCALLBACK ZipIO_Error( void* opaque, void* stream ) +{ + (void)opaque; + (void)stream; + //msg_Dbg( p_access, "error" ); + return 0; +} + -- 2.39.2