skins2(Win): fix multibyte issue for vlt filename (zip format)
[vlc.git] / modules / gui / skins2 / src / theme_loader.cpp
blob083fa793a986af3bf3d025615c98034043b27c97
1 /*****************************************************************************
2 * theme_loader.cpp
3 *****************************************************************************
4 * Copyright (C) 2003 the VideoLAN team
5 * $Id$
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 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
33 #include <vlc_common.h>
34 #include <vlc_fs.h>
36 #include "theme_loader.hpp"
37 #include "theme.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 )
45 # include <zlib.h>
46 # include <errno.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 )
52 # include <libtar.h>
53 #else
54 typedef gzFile TAR;
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 );
59 #endif
60 int makedir( const char *newdir );
61 #endif
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
73 struct stat p_stat;
74 if( vlc_stat( fileName.c_str(), &p_stat ) )
75 return false;
77 // First, we try to un-targz the file, and if it fails we hope it's a XML
78 // file...
80 #if defined( HAVE_ZLIB_H )
81 if( ! extract( fileName ) && ! parse( path, fileName ) )
82 return false;
83 #else
84 if( ! parse( path, fileName ) )
85 return false;
86 #endif
88 Theme *pNewTheme = getIntf()->p_sys->p_theme;
89 if( !pNewTheme )
90 return false;
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() );
98 return true;
102 #if defined( HAVE_ZLIB_H )
103 bool ThemeLoader::extractTarGz( const string &tarFile, const string &rootDir )
105 TAR *t;
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,
113 TAR_GNU ) == -1 )
114 #else
115 if( tar_open( &t, (char *)tarFile.c_str(), O_RDONLY ) == -1 )
116 #endif
118 msg_Dbg( getIntf(), "failed to open %s as a gzip tar file",
119 tarFile.c_str() );
120 return false;
123 if( tar_extract_all( t, (char *)rootDir.c_str() ) != 0 )
125 tar_close( t );
126 return false;
129 if( tar_close( t ) != 0 )
131 return false;
134 return true;
137 static voidpf ZCALLBACK open_vlc( voidpf opaque, const char *filename, int mode)
139 (void)mode;
140 intf_thread_t *pIntf = (intf_thread_t *)opaque;
142 FILE *stream = vlc_fopen( filename, "rb" );
143 if( stream == NULL )
144 msg_Dbg( pIntf, "vlc_fopen failed for %s", filename );
145 return stream;
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 );
159 if( file == 0 )
161 msg_Dbg( getIntf(), "failed to open %s as a zip file",
162 zipFile.c_str() );
163 return false;
165 unz_global_info info;
166 if( unzGetGlobalInfo( file, &info ) != UNZ_OK )
168 msg_Dbg( getIntf(), "failed to read zip info from %s",
169 zipFile.c_str() );
170 unzClose( file );
171 return false;
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",
179 zipFile.c_str() );
180 unzClose( file );
181 return false;
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",
190 zipFile.c_str() );
191 unzClose( file );
192 return false;
196 unzClose( file );
197 return true;
201 bool ThemeLoader::extractFileInZip( unzFile file, const string &rootDir,
202 bool isWsz )
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 )
209 != UNZ_OK )
211 return false;
214 // Convert the file name to lower case, because some winamp skins
215 // use the wrong case...
216 if( isWsz )
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 );
222 if( !pBuffer )
223 return false;
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 ) )
237 free( pBuffer );
238 return false;
240 makedir( basePath.c_str() );
241 FILE *fout = fopen( fullPath.c_str(), "wb" );
242 if( fout == NULL )
244 msg_Err( getIntf(), "error opening %s", fullPath.c_str() );
245 free( pBuffer );
246 return false;
249 // Extract the current file
250 int n;
253 n = unzReadCurrentFile( file, pBuffer, ZIP_BUFFER_SIZE );
254 if( n < 0 )
256 msg_Err( getIntf(), "error while reading zip file" );
257 fclose(fout);
258 free( pBuffer );
259 return false;
261 else if( n > 0 )
263 if( fwrite( pBuffer, n , 1, fout) != 1 )
265 msg_Err( getIntf(), "error while writing %s",
266 fullPath.c_str() );
267 fclose(fout);
268 free( pBuffer );
269 return false;
272 } while( n > 0 );
274 fclose(fout);
276 if( unzCloseCurrentFile( file ) != UNZ_OK )
278 free( pBuffer );
279 return false;
283 free( pBuffer );
284 return true;
288 bool ThemeLoader::extract( const string &fileName )
290 bool result = true;
291 char *tmpdir = tempnam( NULL, "vlt" );
292 string tempPath = sFromLocale( tmpdir );
293 free( tmpdir );
295 // Extract the file in a temporary directory
296 if( ! extractTarGz( fileName, tempPath ) &&
297 ! extractZip( fileName, tempPath ) )
299 deleteTempFiles( tempPath );
300 return false;
303 string path;
304 string xmlFile;
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 );
311 else
313 // No XML file, check if it is a winamp2 skin
314 string mainBmp;
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 ) )
326 break;
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() );
337 result = false;
340 else
342 msg_Err( getIntf(), "no XML found in theme %s", fileName.c_str() );
343 result = false;
346 // Clean-up
347 deleteTempFiles( tempPath );
348 return result;
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 )
361 // File loaded
362 msg_Dbg( getIntf(), "using skin file: %s", xmlFile.c_str() );
364 // Start the parser
365 SkinParser parser( getIntf(), xmlFile, path );
366 if( ! parser.parse() )
367 return false;
369 // Build and store the theme
370 Builder builder( getIntf(), parser.getData(), path );
371 getIntf()->p_sys->p_theme = builder.build();
373 return true;
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() );
383 string basePath;
384 if( p != string::npos )
386 if( p < rFullPath.size() - 1)
388 basePath = rFullPath.substr( 0, p );
390 else
392 basePath = rFullPath;
395 return basePath;
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 );
410 return newPath;
414 bool ThemeLoader::findFile( const string &rootDir, const string &rFileName,
415 string &themeFilePath )
417 // Path separator
418 const string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
420 const char *pszDirContent;
422 // Open the dir
423 DIR *pCurrDir = vlc_opendir( rootDir.c_str() );
425 if( pCurrDir == NULL )
427 // An error occurred
428 msg_Dbg( getIntf(), "cannot open directory %s", rootDir.c_str() );
429 return false;
432 // While we still have entries in the directory
433 while( ( pszDirContent = vlc_readdir( pCurrDir ) ) != NULL )
435 string newURI = rootDir + sep + pszDirContent;
437 // Skip . and ..
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 )
448 #else
449 if( 0 )
450 #endif
452 // Can we find the file in this subdirectory?
453 if( findFile( newURI, rFileName, themeFilePath ) )
455 closedir( pCurrDir );
456 return true;
459 else
461 // Found the theme file?
462 if( rFileName == string( pszDirContent ) )
464 themeFilePath = newURI;
465 closedir( pCurrDir );
466 return true;
472 closedir( pCurrDir );
473 return false;
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
486 struct tar_header
487 { /* byte offset */
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 */
504 /* 500 */
508 union tar_buffer {
509 char buffer[BLOCKSIZE];
510 struct tar_header header;
515 int tar_open( TAR **t, char *pathname, int oflags )
517 (void)oflags;
519 gzFile f = gzopen( pathname, "rb" );
520 if( f == NULL )
522 fprintf( stderr, "Couldn't gzopen %s\n", pathname );
523 return -1;
526 *t = (gzFile *)malloc( sizeof(gzFile) );
527 **t = f;
528 return 0;
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];
539 while( 1 )
541 len = gzread( *t, &buffer, BLOCKSIZE );
542 if( len < 0 )
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" );
554 return -1;
558 * If we have to get a tar header
560 if( getheader == 1 )
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) )
568 break;
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");
578 return -1;
581 switch( buffer.header.typeflag )
583 case DIRTYPE:
584 makedir( fname );
585 break;
586 case REGTYPE:
587 case AREGTYPE:
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, '/' );
596 if( p != NULL )
598 *p = '\0';
599 makedir( fname );
600 *p = '/';
601 outfile = fopen( fname, "wb" );
602 if( !outfile )
604 fprintf( stderr, "tar couldn't create %s\n",
605 fname );
612 * could have no contents
614 getheader = (remaining) ? 0 : 1;
615 break;
616 default:
617 break;
620 else
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 );
629 fclose( outfile );
630 outfile = NULL;
631 unlink( fname );
634 remaining -= bytes;
635 if( remaining == 0 )
637 getheader = 1;
638 if( outfile != NULL )
640 fclose(outfile);
641 outfile = NULL;
647 return 0;
651 int tar_close( TAR *t )
653 if( gzclose( *t ) != Z_OK ) fprintf( stderr, "failed gzclose\n" );
654 free( t );
655 return 0;
659 /* helper functions */
660 int getoct( char *p, int width )
662 int result = 0;
663 char c;
665 while( width-- )
667 c = *p++;
668 if( c == ' ' )
669 continue;
670 if( c == 0 )
671 break;
672 result = result * 8 + (c - '0');
674 return result;
677 #endif
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 );
690 if( len <= 0 )
692 free( buffer );
693 return 0;
696 if( buffer[len-1] == '/' )
698 buffer[len-1] = '\0';
701 if( vlc_mkdir( buffer, 0775 ) == 0 )
703 free( buffer );
704 return 1;
707 p = buffer + 1;
708 while( 1 )
710 char hold;
712 while( *p && *p != '\\' && *p != '/' ) p++;
713 hold = *p;
714 *p = 0;
715 if( ( vlc_mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
717 fprintf( stderr, "couldn't create directory %s\n", buffer );
718 free( buffer );
719 return 0;
721 if( hold == 0 ) break;
722 *p++ = hold;
724 free( buffer );
725 return 1;
728 #ifdef HAVE_ZLIB_H
730 static int currentGzFd = -1;
731 static void * currentGzVp = NULL;
733 int gzopen_frontend( const char *pathname, int oflags, int mode )
735 (void)mode;
737 const char *gzflags;
738 gzFile gzf;
740 switch( oflags )
742 case O_WRONLY:
743 gzflags = "wb";
744 break;
745 case O_RDONLY:
746 gzflags = "rb";
747 break;
748 case O_RDWR:
749 default:
750 errno = EINVAL;
751 return -1;
754 gzf = gzopen( pathname, gzflags );
755 if( !gzf )
757 errno = ENOMEM;
758 return -1;
761 /** Hum ... */
762 currentGzFd = 42;
763 currentGzVp = gzf;
765 return currentGzFd;
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 );
776 return -1;
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 );
785 return -1;
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 );
794 return -1;
797 #endif