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 *****************************************************************************/
25 #include "theme_loader.hpp"
27 #include "../parser/builder.hpp"
28 #include "../parser/skin_parser.hpp"
29 #include "../src/os_factory.hpp"
30 #include "../src/vlcproc.hpp"
31 #include "../src/window_manager.hpp"
36 #ifdef HAVE_SYS_STAT_H
37 # include <sys/stat.h>
41 #elif defined( WIN32 ) && !defined( UNDER_CE )
50 #if defined( HAVE_ZLIB_H )
53 int gzopen_frontend ( char *pathname, int oflags, int mode );
54 int gzclose_frontend( int );
55 int gzread_frontend ( int, void *, size_t );
56 int gzwrite_frontend( int, const void *, size_t );
57 #if defined( HAVE_LIBTAR_H )
61 int tar_open ( TAR **t, char *pathname, int oflags );
62 int tar_extract_all ( TAR *t, char *prefix );
63 int tar_close ( TAR *t );
64 int getoct( char *p, int width );
66 int makedir( const char *newdir );
69 #define DEFAULT_XML_FILE "theme.xml"
70 #define WINAMP2_XML_FILE "winamp2.xml"
71 #define ZIP_BUFFER_SIZE 4096
74 bool ThemeLoader::load( const string &fileName )
76 // First, we try to un-targz the file, and if it fails we hope it's a XML
78 string path = getFilePath( fileName );
79 #if defined( HAVE_ZLIB_H )
80 if( ! extract( fileName ) && ! parse( path, fileName ) )
83 if( ! parse( path, fileName ) )
87 Theme *pNewTheme = getIntf()->p_sys->p_theme;
93 // Check if the skin to load is in the config file, to load its config
94 char *skin_last = config_GetPsz( getIntf(), "skins2-last" );
95 if( skin_last != NULL && fileName == (string)skin_last )
97 // Restore the theme configuration
98 getIntf()->p_sys->p_theme->loadConfig();
99 // Used to anchor the windows at the beginning
100 pNewTheme->getWindowManager().stopMove();
104 config_PutPsz( getIntf(), "skins2-last", fileName.c_str() );
106 pNewTheme->getWindowManager().showAll( true );
108 if( skin_last ) free( skin_last );
110 // The new theme cannot embed a video output yet
111 VlcProc::instance( getIntf() )->dropVout();
117 #if defined( HAVE_ZLIB_H )
118 bool ThemeLoader::extractTarGz( const string &tarFile, const string &rootDir )
121 #if defined( HAVE_LIBTAR_H )
122 tartype_t gztype = { (openfunc_t) gzopen_frontend,
123 (closefunc_t) gzclose_frontend,
124 (readfunc_t) gzread_frontend,
125 (writefunc_t) gzwrite_frontend };
127 if( tar_open( &t, (char *)tarFile.c_str(), &gztype, O_RDONLY, 0,
130 if( tar_open( &t, (char *)tarFile.c_str(), O_RDONLY ) == -1 )
136 if( tar_extract_all( t, (char *)rootDir.c_str() ) != 0 )
142 if( tar_close( t ) != 0 )
151 bool ThemeLoader::extractZip( const string &zipFile, const string &rootDir )
153 // Try to open the ZIP file
154 unzFile file = unzOpen( zipFile.c_str() );
155 unz_global_info info;
157 if( unzGetGlobalInfo( file, &info ) != UNZ_OK )
161 // Extract all the files in the archive
162 for( unsigned long i = 0; i < info.number_entry; i++ )
164 if( !extractFileInZip( file, rootDir ) )
166 msg_Warn( getIntf(), "Error while unzipping %s",
172 if( i < info.number_entry - 1 )
174 // Go the next file in the archive
175 if( unzGoToNextFile( file ) !=UNZ_OK )
177 msg_Warn( getIntf(), "Error while unzipping %s",
189 bool ThemeLoader::extractFileInZip( unzFile file, const string &rootDir )
191 // Read info for the current file
192 char filenameInZip[256];
193 unz_file_info fileInfo;
194 if( unzGetCurrentFileInfo( file, &fileInfo, filenameInZip,
195 sizeof( filenameInZip), NULL, 0, NULL, 0 )
201 // Convert the file name to lower case, because some winamp skins
202 // use the wrong case...
203 for( size_t i=0; i< strlen( filenameInZip ); i++)
205 filenameInZip[i] = tolower( filenameInZip[i] );
208 // Allocate the buffer
209 void *pBuffer = malloc( ZIP_BUFFER_SIZE );
212 msg_Err( getIntf(), "Failed to allocate memory" );
216 // Get the path of the file
217 OSFactory *pOsFactory = OSFactory::instance( getIntf() );
218 string fullPath = rootDir
219 + pOsFactory->getDirSeparator()
220 + fixDirSeparators( filenameInZip );
221 string basePath = getFilePath( fullPath );
223 // Extract the file if is not a directory
224 if( basePath != fullPath )
226 if( unzOpenCurrentFile( file ) )
231 makedir( basePath.c_str() );
232 FILE *fout = fopen( fullPath.c_str(), "wb" );
235 msg_Err( getIntf(), "Error opening %s", fullPath.c_str() );
240 // Extract the current file
244 n = unzReadCurrentFile( file, pBuffer, ZIP_BUFFER_SIZE );
247 msg_Err( getIntf(), "Error while reading zip file" );
253 if( fwrite( pBuffer, n , 1, fout) != 1 )
255 msg_Err( getIntf(), "Error while writing %s",
265 if( unzCloseCurrentFile( file ) != UNZ_OK )
277 bool ThemeLoader::extract( const string &fileName )
280 char *tmpdir = tempnam( NULL, "vlt" );
281 string tempPath = tmpdir;
284 // Extract the file in a temporary directory
285 if( ! extractTarGz( fileName, tempPath ) &&
286 ! extractZip( fileName, tempPath ) )
288 deleteTempFiles( tempPath );
294 OSFactory *pOsFactory = OSFactory::instance( getIntf() );
295 // Find the XML file in the theme
296 if( findFile( tempPath, DEFAULT_XML_FILE, xmlFile ) )
298 path = getFilePath( xmlFile );
302 // No XML file, check if it is a winamp2 skin
304 if( findFile( tempPath, "main.bmp", mainBmp ) )
306 msg_Dbg( getIntf(), "Try to load a winamp2 skin" );
307 path = getFilePath( mainBmp );
309 // Look for winamp2.xml in the resource path
310 list<string> resPath = pOsFactory->getResourcePath();
311 list<string>::const_iterator it;
312 for( it = resPath.begin(); it != resPath.end(); it++ )
314 if( findFile( *it, WINAMP2_XML_FILE, xmlFile ) )
320 if( !xmlFile.empty() )
322 // Parse the XML file
323 if (! parse( path, xmlFile ) )
325 msg_Err( getIntf(), "Error while parsing %s", xmlFile.c_str() );
331 msg_Err( getIntf(), "No XML found in theme %s", fileName.c_str() );
336 deleteTempFiles( tempPath );
341 void ThemeLoader::deleteTempFiles( const string &path )
343 OSFactory::instance( getIntf() )->rmDir( path );
345 #endif // HAVE_ZLIB_H
348 bool ThemeLoader::parse( const string &path, const string &xmlFile )
351 msg_Dbg( getIntf(), "Using skin file: %s", xmlFile.c_str() );
354 SkinParser parser( getIntf(), xmlFile, path );
355 if( ! parser.parse() )
357 msg_Err( getIntf(), "Failed to parse %s", xmlFile.c_str() );
361 // Build and store the theme
362 Builder builder( getIntf(), parser.getData(), path );
363 getIntf()->p_sys->p_theme = builder.build();
369 string ThemeLoader::getFilePath( const string &rFullPath )
371 OSFactory *pOsFactory = OSFactory::instance( getIntf() );
372 const string &sep = pOsFactory->getDirSeparator();
373 // Find the last separator ('/' or '\')
374 string::size_type p = rFullPath.rfind( sep, rFullPath.size() );
376 if( p != string::npos )
378 if( p < rFullPath.size() - 1)
380 basePath = rFullPath.substr( 0, p );
384 basePath = rFullPath;
391 string ThemeLoader::fixDirSeparators( const string &rPath )
393 OSFactory *pOsFactory = OSFactory::instance( getIntf() );
394 const string &sep = pOsFactory->getDirSeparator();
395 string::size_type p = rPath.find( "/", 0 );
396 string newPath = rPath;
397 while( p != string::npos )
399 newPath = newPath.replace( p, 1, sep );
400 p = newPath.find( "/", p + 1 );
406 bool ThemeLoader::findFile( const string &rootDir, const string &rFileName,
407 string &themeFilePath )
410 const string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
413 struct dirent *pDirContent;
416 pCurrDir = opendir( rootDir.c_str() );
418 if( pCurrDir == NULL )
421 msg_Dbg( getIntf(), "Cannot open directory %s", rootDir.c_str() );
425 // Get the first directory entry
426 pDirContent = (dirent*)readdir( pCurrDir );
428 // While we still have entries in the directory
429 while( pDirContent != NULL )
431 string newURI = rootDir + sep + pDirContent->d_name;
434 if( string( pDirContent->d_name ) != "." &&
435 string( pDirContent->d_name ) != ".." )
437 #if defined( S_ISDIR )
438 struct stat stat_data;
439 stat( newURI.c_str(), &stat_data );
440 if( S_ISDIR(stat_data.st_mode) )
441 #elif defined( DT_DIR )
442 if( pDirContent->d_type & DT_DIR )
447 // Can we find the file in this subdirectory?
448 if( findFile( newURI, rFileName, themeFilePath ) )
450 closedir( pCurrDir );
456 // Found the theme file?
457 if( rFileName == string( pDirContent->d_name ) )
459 themeFilePath = newURI;
460 closedir( pCurrDir );
466 pDirContent = (dirent*)readdir( pCurrDir );
469 closedir( pCurrDir );
474 #if !defined( HAVE_LIBTAR_H ) && defined( HAVE_ZLIB_H )
476 /* Values used in typeflag field */
477 #define REGTYPE '0' /* regular file */
478 #define AREGTYPE '\0' /* regular file */
479 #define DIRTYPE '5' /* directory */
481 #define BLOCKSIZE 512
485 char name[100]; /* 0 */
486 char mode[8]; /* 100 */
487 char uid[8]; /* 108 */
488 char gid[8]; /* 116 */
489 char size[12]; /* 124 */
490 char mtime[12]; /* 136 */
491 char chksum[8]; /* 148 */
492 char typeflag; /* 156 */
493 char linkname[100]; /* 157 */
494 char magic[6]; /* 257 */
495 char version[2]; /* 263 */
496 char uname[32]; /* 265 */
497 char gname[32]; /* 297 */
498 char devmajor[8]; /* 329 */
499 char devminor[8]; /* 337 */
500 char prefix[155]; /* 345 */
506 char buffer[BLOCKSIZE];
507 struct tar_header header;
512 int tar_open( TAR **t, char *pathname, int oflags )
514 gzFile f = gzopen( pathname, "rb" );
517 fprintf( stderr, "Couldn't gzopen %s\n", pathname );
521 *t = (gzFile *)malloc( sizeof(gzFile) );
527 int tar_extract_all( TAR *t, char *prefix )
529 union tar_buffer buffer;
530 int len, err, getheader = 1, remaining = 0;
531 FILE *outfile = NULL;
532 char fname[BLOCKSIZE + PATH_MAX];
536 len = gzread( *t, &buffer, BLOCKSIZE );
539 fprintf( stderr, "%s\n", gzerror(*t, &err) );
543 * Always expect complete blocks to process
544 * the tar information.
546 if( len != 0 && len != BLOCKSIZE )
548 fprintf( stderr, "gzread: incomplete block read\n" );
553 * If we have to get a tar header
558 * If we met the end of the tar
559 * or the end-of-tar block, we are done
561 if( (len == 0) || (buffer.header.name[0] == 0) )
566 sprintf( fname, "%s/%s", prefix, buffer.header.name );
568 /* Check magic value in header */
569 if( strncmp( buffer.header.magic, "GNUtar", 6 ) &&
570 strncmp( buffer.header.magic, "ustar", 5 ) )
572 //fprintf(stderr, "not a tar file\n");
576 switch( buffer.header.typeflag )
583 remaining = getoct( buffer.header.size, 12 );
586 outfile = fopen( fname, "wb" );
587 if( outfile == NULL )
589 /* try creating directory */
590 char *p = strrchr( fname, '/' );
596 outfile = fopen( fname, "wb" );
599 fprintf( stderr, "tar couldn't create %s\n",
608 * could have no contents
610 getheader = (remaining) ? 0 : 1;
618 unsigned int bytes = (remaining > BLOCKSIZE)?BLOCKSIZE:remaining;
620 if( outfile != NULL )
622 if( fwrite( &buffer, sizeof(char), bytes, outfile ) != bytes )
624 fprintf( stderr, "error writing %s skipping...\n", fname );
633 if( outfile != NULL )
646 int tar_close( TAR *t )
648 if( gzclose( *t ) != Z_OK ) fprintf( stderr, "failed gzclose\n" );
654 /* helper functions */
655 int getoct( char *p, int width )
667 result = result * 8 + (c - '0');
675 # define mkdir(dirname,mode) _mkdir(dirname)
678 /* Recursive make directory
679 * Abort if you get an ENOENT errno somewhere in the middle
680 * e.g. ignore error "mkdir on existing directory"
682 * return 1 if OK, 0 on error
684 int makedir( const char *newdir )
686 char *p, *buffer = strdup( newdir );
687 int len = strlen( buffer );
695 if( buffer[len-1] == '/' )
697 buffer[len-1] = '\0';
700 if( mkdir( buffer, 0775 ) == 0 )
711 while( *p && *p != '\\' && *p != '/' ) p++;
714 if( ( mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
716 fprintf( stderr, "couldn't create directory %s\n", buffer );
720 if( hold == 0 ) break;
729 static int currentGzFd = -1;
730 static void * currentGzVp = NULL;
732 int gzopen_frontend( char *pathname, int oflags, int mode )
751 gzf = gzopen( pathname, gzflags );
765 int gzclose_frontend( int fd )
767 if( currentGzVp != NULL && fd != -1 )
769 void *toClose = currentGzVp;
770 currentGzVp = NULL; currentGzFd = -1;
771 return gzclose( toClose );
776 int gzread_frontend( int fd, void *p_buffer, size_t i_length )
778 if( currentGzVp != NULL && fd != -1 )
780 return gzread( currentGzVp, p_buffer, i_length );
785 int gzwrite_frontend( int fd, const void * p_buffer, size_t i_length )
787 if( currentGzVp != NULL && fd != -1 )
789 return gzwrite( currentGzVp, const_cast<void*>(p_buffer), i_length );