1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2003 the VideoLAN team
7 * Authors: Cyril Deguet <asmax@via.ecp.fr>
8 * Olivier Teulière <ipkiss@via.ecp.fr>
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.
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.
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 *****************************************************************************/
33 #include <vlc_common.h>
36 #include "theme_loader.hpp"
38 #include "../parser/builder.hpp"
39 #include "../parser/skin_parser.hpp"
40 #include "../src/os_factory.hpp"
41 #include "../src/vlcproc.hpp"
42 #include "../src/window_manager.hpp"
44 #if defined( HAVE_ZLIB_H )
47 int gzopen_frontend ( const char *pathname
, int oflags
, int mode
);
48 int gzclose_frontend( int );
49 int gzread_frontend ( int, void *, size_t );
50 int gzwrite_frontend( int, const void *, size_t );
51 #if defined( HAVE_LIBTAR_H )
55 int tar_open ( TAR
**t
, char *pathname
, int oflags
);
56 int tar_extract_all ( TAR
*t
, char *prefix
);
57 int tar_close ( TAR
*t
);
58 int getoct( char *p
, int width
);
60 int makedir( const char *newdir
);
63 #define DEFAULT_XML_FILE "theme.xml"
64 #define WINAMP2_XML_FILE "winamp2.xml"
65 #define ZIP_BUFFER_SIZE 4096
68 bool ThemeLoader::load( const string
&fileName
)
70 string path
= getFilePath( fileName
);
72 //Before all, let's see if the file is present
74 if( vlc_stat( fileName
.c_str(), &p_stat
) )
77 // First, we try to un-targz the file, and if it fails we hope it's a XML
80 #if defined( HAVE_ZLIB_H )
81 if( ! extract( fileName
) && ! parse( path
, fileName
) )
84 if( ! parse( path
, fileName
) )
88 Theme
*pNewTheme
= getIntf()->p_sys
->p_theme
;
92 // Restore the theme configuration
93 getIntf()->p_sys
->p_theme
->loadConfig();
95 // Retain new loaded skins in config
96 config_PutPsz( getIntf(), "skins2-last", fileName
.c_str() );
102 #if defined( HAVE_ZLIB_H )
103 bool ThemeLoader::extractTarGz( const string
&tarFile
, const string
&rootDir
)
106 #if defined( HAVE_LIBTAR_H )
107 tartype_t gztype
= { (openfunc_t
) gzopen_frontend
,
108 (closefunc_t
) gzclose_frontend
,
109 (readfunc_t
) gzread_frontend
,
110 (writefunc_t
) gzwrite_frontend
};
112 if( tar_open( &t
, (char *)tarFile
.c_str(), &gztype
, O_RDONLY
, 0,
115 if( tar_open( &t
, (char *)tarFile
.c_str(), O_RDONLY
) == -1 )
118 msg_Dbg( getIntf(), "failed to open %s as a gzip tar file",
123 if( tar_extract_all( t
, (char *)rootDir
.c_str() ) != 0 )
129 if( tar_close( t
) != 0 )
137 static voidpf ZCALLBACK
open_vlc( voidpf opaque
, const char *filename
, int mode
)
140 intf_thread_t
*pIntf
= (intf_thread_t
*)opaque
;
142 FILE *stream
= vlc_fopen( filename
, "rb" );
144 msg_Dbg( pIntf
, "vlc_fopen failed for %s", filename
);
148 bool ThemeLoader::extractZip( const string
&zipFile
, const string
&rootDir
)
150 bool b_isWsz
= strstr( zipFile
.c_str(), ".wsz" );
152 // Try to open the ZIP file
153 zlib_filefunc_def descr
;
154 fill_fopen_filefunc( &descr
);
155 descr
.zopen_file
= open_vlc
;
156 descr
.opaque
= getIntf();
158 unzFile file
= unzOpen2( zipFile
.c_str(), &descr
);
161 msg_Dbg( getIntf(), "failed to open %s as a zip file",
165 unz_global_info info
;
166 if( unzGetGlobalInfo( file
, &info
) != UNZ_OK
)
168 msg_Dbg( getIntf(), "failed to read zip info from %s",
173 // Extract all the files in the archive
174 for( unsigned long i
= 0; i
< info
.number_entry
; i
++ )
176 if( !extractFileInZip( file
, rootDir
, b_isWsz
) )
178 msg_Warn( getIntf(), "error while unzipping %s",
184 if( i
< info
.number_entry
- 1 )
186 // Go the next file in the archive
187 if( unzGoToNextFile( file
) != UNZ_OK
)
189 msg_Warn( getIntf(), "error while unzipping %s",
201 bool ThemeLoader::extractFileInZip( unzFile file
, const string
&rootDir
,
204 // Read info for the current file
205 char filenameInZip
[256];
206 unz_file_info fileInfo
;
207 if( unzGetCurrentFileInfo( file
, &fileInfo
, filenameInZip
,
208 sizeof( filenameInZip
), NULL
, 0, NULL
, 0 )
214 // Convert the file name to lower case, because some winamp skins
215 // use the wrong case...
217 for( size_t i
= 0; i
< strlen( filenameInZip
); i
++ )
218 filenameInZip
[i
] = tolower( (unsigned char)filenameInZip
[i
] );
220 // Allocate the buffer
221 void *pBuffer
= malloc( ZIP_BUFFER_SIZE
);
225 // Get the path of the file
226 OSFactory
*pOsFactory
= OSFactory::instance( getIntf() );
227 string fullPath
= rootDir
228 + pOsFactory
->getDirSeparator()
229 + fixDirSeparators( filenameInZip
);
230 string basePath
= getFilePath( fullPath
);
232 // Extract the file if is not a directory
233 if( basePath
!= fullPath
)
235 if( unzOpenCurrentFile( file
) )
240 makedir( basePath
.c_str() );
241 FILE *fout
= fopen( fullPath
.c_str(), "wb" );
244 msg_Err( getIntf(), "error opening %s", fullPath
.c_str() );
249 // Extract the current file
253 n
= unzReadCurrentFile( file
, pBuffer
, ZIP_BUFFER_SIZE
);
256 msg_Err( getIntf(), "error while reading zip file" );
263 if( fwrite( pBuffer
, n
, 1, fout
) != 1 )
265 msg_Err( getIntf(), "error while writing %s",
276 if( unzCloseCurrentFile( file
) != UNZ_OK
)
288 bool ThemeLoader::extract( const string
&fileName
)
291 char *tmpdir
= tempnam( NULL
, "vlt" );
292 string tempPath
= sFromLocale( tmpdir
);
295 // Extract the file in a temporary directory
296 if( ! extractTarGz( fileName
, tempPath
) &&
297 ! extractZip( fileName
, tempPath
) )
299 deleteTempFiles( tempPath
);
305 OSFactory
*pOsFactory
= OSFactory::instance( getIntf() );
306 // Find the XML file in the theme
307 if( findFile( tempPath
, DEFAULT_XML_FILE
, xmlFile
) )
309 path
= getFilePath( xmlFile
);
313 // No XML file, check if it is a winamp2 skin
315 if( findFile( tempPath
, "main.bmp", mainBmp
) )
317 msg_Dbg( getIntf(), "trying to load a winamp2 skin" );
318 path
= getFilePath( mainBmp
);
320 // Look for winamp2.xml in the resource path
321 list
<string
> resPath
= pOsFactory
->getResourcePath();
322 list
<string
>::const_iterator it
;
323 for( it
= resPath
.begin(); it
!= resPath
.end(); ++it
)
325 if( findFile( *it
, WINAMP2_XML_FILE
, xmlFile
) )
331 if( !xmlFile
.empty() )
333 // Parse the XML file
334 if (! parse( path
, xmlFile
) )
336 msg_Err( getIntf(), "error while parsing %s", xmlFile
.c_str() );
342 msg_Err( getIntf(), "no XML found in theme %s", fileName
.c_str() );
347 deleteTempFiles( tempPath
);
352 void ThemeLoader::deleteTempFiles( const string
&path
)
354 OSFactory::instance( getIntf() )->rmDir( path
);
356 #endif // HAVE_ZLIB_H
359 bool ThemeLoader::parse( const string
&path
, const string
&xmlFile
)
362 msg_Dbg( getIntf(), "using skin file: %s", xmlFile
.c_str() );
365 SkinParser
parser( getIntf(), xmlFile
, path
);
366 if( ! parser
.parse() )
369 // Build and store the theme
370 Builder
builder( getIntf(), parser
.getData(), path
);
371 getIntf()->p_sys
->p_theme
= builder
.build();
377 string
ThemeLoader::getFilePath( const string
&rFullPath
)
379 OSFactory
*pOsFactory
= OSFactory::instance( getIntf() );
380 const string
&sep
= pOsFactory
->getDirSeparator();
381 // Find the last separator ('/' or '\')
382 string::size_type p
= rFullPath
.rfind( sep
, rFullPath
.size() );
384 if( p
!= string::npos
)
386 if( p
< rFullPath
.size() - 1)
388 basePath
= rFullPath
.substr( 0, p
);
392 basePath
= rFullPath
;
399 string
ThemeLoader::fixDirSeparators( const string
&rPath
)
401 OSFactory
*pOsFactory
= OSFactory::instance( getIntf() );
402 const string
&sep
= pOsFactory
->getDirSeparator();
403 string::size_type p
= rPath
.find( "/", 0 );
404 string newPath
= rPath
;
405 while( p
!= string::npos
)
407 newPath
= newPath
.replace( p
, 1, sep
);
408 p
= newPath
.find( "/", p
+ 1 );
414 bool ThemeLoader::findFile( const string
&rootDir
, const string
&rFileName
,
415 string
&themeFilePath
)
418 const string
&sep
= OSFactory::instance( getIntf() )->getDirSeparator();
420 const char *pszDirContent
;
423 DIR *pCurrDir
= vlc_opendir( rootDir
.c_str() );
425 if( pCurrDir
== NULL
)
428 msg_Dbg( getIntf(), "cannot open directory %s", rootDir
.c_str() );
432 // While we still have entries in the directory
433 while( ( pszDirContent
= vlc_readdir( pCurrDir
) ) != NULL
)
435 string newURI
= rootDir
+ sep
+ pszDirContent
;
438 if( string( pszDirContent
) != "." &&
439 string( pszDirContent
) != ".." )
441 #if defined( S_ISDIR )
442 struct stat stat_data
;
444 if( ( vlc_stat( newURI
.c_str(), &stat_data
) == 0 )
445 && S_ISDIR(stat_data
.st_mode
) )
446 #elif defined( DT_DIR )
447 if( pDirContent
->d_type
& DT_DIR
)
452 // Can we find the file in this subdirectory?
453 if( findFile( newURI
, rFileName
, themeFilePath
) )
455 closedir( pCurrDir
);
461 // Found the theme file?
462 if( rFileName
== string( pszDirContent
) )
464 themeFilePath
= newURI
;
465 closedir( pCurrDir
);
472 closedir( pCurrDir
);
477 #if !defined( HAVE_LIBTAR_H ) && defined( HAVE_ZLIB_H )
479 /* Values used in typeflag field */
480 #define REGTYPE '0' /* regular file */
481 #define AREGTYPE '\0' /* regular file */
482 #define DIRTYPE '5' /* directory */
484 #define BLOCKSIZE 512
488 char name
[100]; /* 0 */
489 char mode
[8]; /* 100 */
490 char uid
[8]; /* 108 */
491 char gid
[8]; /* 116 */
492 char size
[12]; /* 124 */
493 char mtime
[12]; /* 136 */
494 char chksum
[8]; /* 148 */
495 char typeflag
; /* 156 */
496 char linkname
[100]; /* 157 */
497 char magic
[6]; /* 257 */
498 char version
[2]; /* 263 */
499 char uname
[32]; /* 265 */
500 char gname
[32]; /* 297 */
501 char devmajor
[8]; /* 329 */
502 char devminor
[8]; /* 337 */
503 char prefix
[155]; /* 345 */
509 char buffer
[BLOCKSIZE
];
510 struct tar_header header
;
515 int tar_open( TAR
**t
, char *pathname
, int oflags
)
519 gzFile f
= gzopen( pathname
, "rb" );
522 fprintf( stderr
, "Couldn't gzopen %s\n", pathname
);
526 *t
= (gzFile
*)malloc( sizeof(gzFile
) );
532 int tar_extract_all( TAR
*t
, char *prefix
)
534 union tar_buffer buffer
;
535 int len
, err
, getheader
= 1, remaining
= 0;
536 FILE *outfile
= NULL
;
537 char fname
[BLOCKSIZE
+ PATH_MAX
];
541 len
= gzread( *t
, &buffer
, BLOCKSIZE
);
544 fprintf( stderr
, "%s\n", gzerror(*t
, &err
) );
548 * Always expect complete blocks to process
549 * the tar information.
551 if( len
!= 0 && len
!= BLOCKSIZE
)
553 fprintf( stderr
, "gzread: incomplete block read\n" );
558 * If we have to get a tar header
563 * If we met the end of the tar
564 * or the end-of-tar block, we are done
566 if( (len
== 0) || (buffer
.header
.name
[0] == 0) )
571 sprintf( fname
, "%s/%s", prefix
, buffer
.header
.name
);
573 /* Check magic value in header */
574 if( strncmp( buffer
.header
.magic
, "GNUtar", 6 ) &&
575 strncmp( buffer
.header
.magic
, "ustar", 5 ) )
577 //fprintf(stderr, "not a tar file\n");
581 switch( buffer
.header
.typeflag
)
588 remaining
= getoct( buffer
.header
.size
, 12 );
589 if( !remaining
) outfile
= NULL
; else
591 outfile
= fopen( fname
, "wb" );
592 if( outfile
== NULL
)
594 /* try creating directory */
595 char *p
= strrchr( fname
, '/' );
601 outfile
= fopen( fname
, "wb" );
604 fprintf( stderr
, "tar couldn't create %s\n",
612 * could have no contents
614 getheader
= (remaining
) ? 0 : 1;
622 unsigned int bytes
= (remaining
> BLOCKSIZE
)?BLOCKSIZE
:remaining
;
624 if( outfile
!= NULL
)
626 if( fwrite( &buffer
, sizeof(char), bytes
, outfile
) != bytes
)
628 fprintf( stderr
, "error writing %s skipping...\n", fname
);
638 if( outfile
!= NULL
)
651 int tar_close( TAR
*t
)
653 if( gzclose( *t
) != Z_OK
) fprintf( stderr
, "failed gzclose\n" );
659 /* helper functions */
660 int getoct( char *p
, int width
)
672 result
= result
* 8 + (c
- '0');
679 /* Recursive make directory
680 * Abort if you get an ENOENT errno somewhere in the middle
681 * e.g. ignore error "mkdir on existing directory"
683 * return 1 if OK, 0 on error
685 int makedir( const char *newdir
)
687 char *p
, *buffer
= strdup( newdir
);
688 int len
= strlen( buffer
);
696 if( buffer
[len
-1] == '/' )
698 buffer
[len
-1] = '\0';
701 if( vlc_mkdir( buffer
, 0775 ) == 0 )
712 while( *p
&& *p
!= '\\' && *p
!= '/' ) p
++;
715 if( ( vlc_mkdir( buffer
, 0775 ) == -1 ) && ( errno
== ENOENT
) )
717 fprintf( stderr
, "couldn't create directory %s\n", buffer
);
721 if( hold
== 0 ) break;
730 static int currentGzFd
= -1;
731 static void * currentGzVp
= NULL
;
733 int gzopen_frontend( const char *pathname
, int oflags
, int mode
)
754 gzf
= gzopen( pathname
, gzflags
);
768 int gzclose_frontend( int fd
)
770 if( currentGzVp
!= NULL
&& fd
!= -1 )
772 void *toClose
= currentGzVp
;
773 currentGzVp
= NULL
; currentGzFd
= -1;
774 return gzclose( (gzFile
) toClose
);
779 int gzread_frontend( int fd
, void *p_buffer
, size_t i_length
)
781 if( currentGzVp
!= NULL
&& fd
!= -1 )
783 return gzread( (gzFile
) currentGzVp
, p_buffer
, i_length
);
788 int gzwrite_frontend( int fd
, const void * p_buffer
, size_t i_length
)
790 if( currentGzVp
!= NULL
&& fd
!= -1 )
792 return gzwrite( (gzFile
) currentGzVp
, const_cast<void*>(p_buffer
), i_length
);