]> git.sesse.net Git - vlc/blob - modules/gui/skins2/src/theme_loader.cpp
utf8_* -> vlc_* (sed roxxors)
[vlc] / modules / gui / skins2 / src / theme_loader.cpp
1 /*****************************************************************************
2  * theme_loader.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@via.ecp.fr>
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 #include "theme_loader.hpp"
26 #include "theme.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"
32
33 #ifdef HAVE_FCNTL_H
34 #   include <fcntl.h>
35 #endif
36 #ifdef HAVE_SYS_STAT_H
37 #   include <sys/stat.h>
38 #endif
39 #ifdef HAVE_UNISTD_H
40 #   include <unistd.h>
41 #elif defined( WIN32 ) && !defined( UNDER_CE )
42 #   include <direct.h>
43 #endif
44
45 #ifdef HAVE_DIRENT_H
46 #   include <dirent.h>
47 #endif
48
49
50 #if defined( HAVE_ZLIB_H )
51 #   include <zlib.h>
52 #   include <errno.h>
53 int gzopen_frontend ( const 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 )
58 #   include <libtar.h>
59 #else
60 typedef gzFile TAR;
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 );
65 #endif
66 int makedir( const char *newdir );
67 #endif
68
69 #define DEFAULT_XML_FILE "theme.xml"
70 #define WINAMP2_XML_FILE "winamp2.xml"
71 #define ZIP_BUFFER_SIZE 4096
72
73
74 bool ThemeLoader::load( const string &fileName )
75 {
76     string path = getFilePath( fileName );
77
78     //Before all, let's see if the file is present
79     struct stat p_stat;
80     if( vlc_stat( path.c_str(), &p_stat ) )
81         return false;
82
83     // First, we try to un-targz the file, and if it fails we hope it's a XML
84     // file...
85
86 #if defined( HAVE_ZLIB_H )
87     if( ! extract( sToLocale( fileName ) ) && ! parse( path, fileName ) )
88         return false;
89 #else
90     if( ! parse( path, fileName ) )
91         return false;
92 #endif
93
94     Theme *pNewTheme = getIntf()->p_sys->p_theme;
95     if( !pNewTheme )
96     {
97         return false;
98     }
99
100     // Check if the skin to load is in the config file, to load its config
101     char *skin_last = config_GetPsz( getIntf(), "skins2-last" );
102     if( skin_last != NULL && fileName == (string)skin_last )
103     {
104         // Restore the theme configuration
105         getIntf()->p_sys->p_theme->loadConfig();
106         // Used to anchor the windows at the beginning
107         pNewTheme->getWindowManager().stopMove();
108     }
109     else
110     {
111         config_PutPsz( getIntf(), "skins2-last", fileName.c_str() );
112         // Show the windows
113         pNewTheme->getWindowManager().showAll( true );
114     }
115     free( skin_last );
116
117     return true;
118 }
119
120
121 #if defined( HAVE_ZLIB_H )
122 bool ThemeLoader::extractTarGz( const string &tarFile, const string &rootDir )
123 {
124     TAR *t;
125 #if defined( HAVE_LIBTAR_H )
126     tartype_t gztype = { (openfunc_t) gzopen_frontend,
127                          (closefunc_t) gzclose_frontend,
128                          (readfunc_t) gzread_frontend,
129                          (writefunc_t) gzwrite_frontend };
130
131     if( tar_open( &t, (char *)tarFile.c_str(), &gztype, O_RDONLY, 0,
132                   TAR_GNU ) == -1 )
133 #else
134     if( tar_open( &t, (char *)tarFile.c_str(), O_RDONLY ) == -1 )
135 #endif
136     {
137         return false;
138     }
139
140     if( tar_extract_all( t, (char *)rootDir.c_str() ) != 0 )
141     {
142         tar_close( t );
143         return false;
144     }
145
146     if( tar_close( t ) != 0 )
147     {
148         return false;
149     }
150
151     return true;
152 }
153
154
155 bool ThemeLoader::extractZip( const string &zipFile, const string &rootDir )
156 {
157     // Try to open the ZIP file
158     unzFile file = unzOpen( zipFile.c_str() );
159     unz_global_info info;
160
161     if( unzGetGlobalInfo( file, &info ) != UNZ_OK )
162     {
163         return false;
164     }
165     // Extract all the files in the archive
166     for( unsigned long i = 0; i < info.number_entry; i++ )
167     {
168         if( !extractFileInZip( file, rootDir ) )
169         {
170             msg_Warn( getIntf(), "error while unzipping %s",
171                       zipFile.c_str() );
172             unzClose( file );
173             return false;
174         }
175
176         if( i < info.number_entry - 1 )
177         {
178             // Go the next file in the archive
179             if( unzGoToNextFile( file ) !=UNZ_OK )
180             {
181                 msg_Warn( getIntf(), "error while unzipping %s",
182                           zipFile.c_str() );
183                 unzClose( file );
184                 return false;
185             }
186         }
187     }
188     unzClose( file );
189     return true;
190 }
191
192
193 bool ThemeLoader::extractFileInZip( unzFile file, const string &rootDir )
194 {
195     // Read info for the current file
196     char filenameInZip[256];
197     unz_file_info fileInfo;
198     if( unzGetCurrentFileInfo( file, &fileInfo, filenameInZip,
199                                sizeof( filenameInZip), NULL, 0, NULL, 0 )
200         != UNZ_OK )
201     {
202         return false;
203     }
204
205 #ifdef WIN32
206
207     // Convert the file name to lower case, because some winamp skins
208     // use the wrong case...
209     for( size_t i=0; i< strlen( filenameInZip ); i++)
210     {
211         filenameInZip[i] = tolower( filenameInZip[i] );
212     }
213
214 #endif
215
216     // Allocate the buffer
217     void *pBuffer = malloc( ZIP_BUFFER_SIZE );
218     if( !pBuffer )
219         return false;
220
221     // Get the path of the file
222     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
223     string fullPath = rootDir
224         + pOsFactory->getDirSeparator()
225         + fixDirSeparators( filenameInZip );
226     string basePath = getFilePath( fullPath );
227
228     // Extract the file if is not a directory
229     if( basePath != fullPath )
230     {
231         if( unzOpenCurrentFile( file ) )
232         {
233             free( pBuffer );
234             return false;
235         }
236         makedir( basePath.c_str() );
237         FILE *fout = fopen( fullPath.c_str(), "wb" );
238         if( fout == NULL )
239         {
240             msg_Err( getIntf(), "error opening %s", fullPath.c_str() );
241             free( pBuffer );
242             return false;
243         }
244
245         // Extract the current file
246         int n;
247         do
248         {
249             n = unzReadCurrentFile( file, pBuffer, ZIP_BUFFER_SIZE );
250             if( n < 0 )
251             {
252                 msg_Err( getIntf(), "error while reading zip file" );
253                 free( pBuffer );
254                 return false;
255             }
256             else if( n > 0 )
257             {
258                 if( fwrite( pBuffer, n , 1, fout) != 1 )
259                 {
260                     msg_Err( getIntf(), "error while writing %s",
261                              fullPath.c_str() );
262                     free( pBuffer );
263                     return false;
264                 }
265             }
266         } while( n > 0 );
267
268         fclose(fout);
269
270         if( unzCloseCurrentFile( file ) != UNZ_OK )
271         {
272             free( pBuffer );
273             return false;
274         }
275     }
276
277     free( pBuffer );
278     return true;
279 }
280
281
282 bool ThemeLoader::extract( const string &fileName )
283 {
284     bool result = true;
285     char *tmpdir = tempnam( NULL, "vlt" );
286     string tempPath = sFromLocale( tmpdir );
287     free( tmpdir );
288
289     // Extract the file in a temporary directory
290     if( ! extractTarGz( fileName, tempPath ) &&
291         ! extractZip( fileName, tempPath ) )
292     {
293         deleteTempFiles( tempPath );
294         return false;
295     }
296
297     string path;
298     string xmlFile;
299     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
300     // Find the XML file in the theme
301     if( findFile( tempPath, DEFAULT_XML_FILE, xmlFile ) )
302     {
303         path = getFilePath( xmlFile );
304     }
305     else
306     {
307         // No XML file, check if it is a winamp2 skin
308         string mainBmp;
309         if( findFile( tempPath, "main.bmp", mainBmp ) )
310         {
311             msg_Dbg( getIntf(), "trying to load a winamp2 skin" );
312             path = getFilePath( mainBmp );
313
314             // Look for winamp2.xml in the resource path
315             list<string> resPath = pOsFactory->getResourcePath();
316             list<string>::const_iterator it;
317             for( it = resPath.begin(); it != resPath.end(); it++ )
318             {
319                 if( findFile( *it, WINAMP2_XML_FILE, xmlFile ) )
320                     break;
321             }
322         }
323     }
324
325     if( !xmlFile.empty() )
326     {
327         // Parse the XML file
328         if (! parse( path, xmlFile ) )
329         {
330             msg_Err( getIntf(), "error while parsing %s", xmlFile.c_str() );
331             result = false;
332         }
333     }
334     else
335     {
336         msg_Err( getIntf(), "no XML found in theme %s", fileName.c_str() );
337         result = false;
338     }
339
340     // Clean-up
341     deleteTempFiles( tempPath );
342     return result;
343 }
344
345
346 void ThemeLoader::deleteTempFiles( const string &path )
347 {
348     OSFactory::instance( getIntf() )->rmDir( path );
349 }
350 #endif // HAVE_ZLIB_H
351
352
353 bool ThemeLoader::parse( const string &path, const string &xmlFile )
354 {
355     // File loaded
356     msg_Dbg( getIntf(), "using skin file: %s", xmlFile.c_str() );
357
358     // Start the parser
359     SkinParser parser( getIntf(), xmlFile, path );
360     if( ! parser.parse() )
361         return false;
362
363     // Build and store the theme
364     Builder builder( getIntf(), parser.getData(), path );
365     getIntf()->p_sys->p_theme = builder.build();
366
367     return true;
368 }
369
370
371 string ThemeLoader::getFilePath( const string &rFullPath )
372 {
373     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
374     const string &sep = pOsFactory->getDirSeparator();
375     // Find the last separator ('/' or '\')
376     string::size_type p = rFullPath.rfind( sep, rFullPath.size() );
377     string basePath;
378     if( p != string::npos )
379     {
380         if( p < rFullPath.size() - 1)
381         {
382             basePath = rFullPath.substr( 0, p );
383         }
384         else
385         {
386             basePath = rFullPath;
387         }
388     }
389     return basePath;
390 }
391
392
393 string ThemeLoader::fixDirSeparators( const string &rPath )
394 {
395     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
396     const string &sep = pOsFactory->getDirSeparator();
397     string::size_type p = rPath.find( "/", 0 );
398     string newPath = rPath;
399     while( p != string::npos )
400     {
401         newPath = newPath.replace( p, 1, sep );
402         p = newPath.find( "/", p + 1 );
403     }
404     return newPath;
405 }
406
407
408 bool ThemeLoader::findFile( const string &rootDir, const string &rFileName,
409                             string &themeFilePath )
410 {
411     // Path separator
412     const string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
413
414     DIR *pCurrDir;
415     char *pszDirContent;
416
417     // Open the dir
418     pCurrDir = vlc_opendir( rootDir.c_str() );
419
420     if( pCurrDir == NULL )
421     {
422         // An error occurred
423         msg_Dbg( getIntf(), "cannot open directory %s", rootDir.c_str() );
424         return false;
425     }
426
427     // While we still have entries in the directory
428     while( ( pszDirContent = vlc_readdir( pCurrDir ) ) != NULL )
429     {
430         string newURI = rootDir + sep + pszDirContent;
431
432         // Skip . and ..
433         if( string( pszDirContent ) != "." &&
434             string( pszDirContent ) != ".." )
435         {
436 #if defined( S_ISDIR )
437             struct stat stat_data;
438
439             if( ( vlc_stat( newURI.c_str(), &stat_data ) == 0 )
440              && S_ISDIR(stat_data.st_mode) )
441 #elif defined( DT_DIR )
442             if( pDirContent->d_type & DT_DIR )
443 #else
444             if( 0 )
445 #endif
446             {
447                 // Can we find the file in this subdirectory?
448                 if( findFile( newURI, rFileName, themeFilePath ) )
449                 {
450                     free( pszDirContent );
451                     closedir( pCurrDir );
452                     return true;
453                 }
454             }
455             else
456             {
457                 // Found the theme file?
458                 if( rFileName == string( pszDirContent ) )
459                 {
460                     themeFilePath = newURI;
461                     free( pszDirContent );
462                     closedir( pCurrDir );
463                     return true;
464                 }
465             }
466         }
467
468         free( pszDirContent );
469     }
470
471     closedir( pCurrDir );
472     return false;
473 }
474
475
476 #if !defined( HAVE_LIBTAR_H ) && defined( HAVE_ZLIB_H )
477
478 /* Values used in typeflag field */
479 #define REGTYPE  '0'            /* regular file */
480 #define AREGTYPE '\0'           /* regular file */
481 #define DIRTYPE  '5'            /* directory */
482
483 #define BLOCKSIZE 512
484
485 struct tar_header
486 {                               /* byte offset */
487     char name[100];             /*   0 */
488     char mode[8];               /* 100 */
489     char uid[8];                /* 108 */
490     char gid[8];                /* 116 */
491     char size[12];              /* 124 */
492     char mtime[12];             /* 136 */
493     char chksum[8];             /* 148 */
494     char typeflag;              /* 156 */
495     char linkname[100];         /* 157 */
496     char magic[6];              /* 257 */
497     char version[2];            /* 263 */
498     char uname[32];             /* 265 */
499     char gname[32];             /* 297 */
500     char devmajor[8];           /* 329 */
501     char devminor[8];           /* 337 */
502     char prefix[155];           /* 345 */
503                                 /* 500 */
504 };
505
506
507 union tar_buffer {
508     char              buffer[BLOCKSIZE];
509     struct tar_header header;
510 };
511
512
513
514 int tar_open( TAR **t, char *pathname, int oflags )
515 {
516     gzFile f = gzopen( pathname, "rb" );
517     if( f == NULL )
518     {
519         fprintf( stderr, "Couldn't gzopen %s\n", pathname );
520         return -1;
521     }
522
523     *t = (gzFile *)malloc( sizeof(gzFile) );
524     **t = f;
525     return 0;
526 }
527
528
529 int tar_extract_all( TAR *t, char *prefix )
530 {
531     union tar_buffer buffer;
532     int   len, err, getheader = 1, remaining = 0;
533     FILE  *outfile = NULL;
534     char  fname[BLOCKSIZE + PATH_MAX];
535
536     while( 1 )
537     {
538         len = gzread( *t, &buffer, BLOCKSIZE );
539         if( len < 0 )
540         {
541             fprintf( stderr, "%s\n", gzerror(*t, &err) );
542         }
543
544         /*
545          * Always expect complete blocks to process
546          * the tar information.
547          */
548         if( len != 0 && len != BLOCKSIZE )
549         {
550             fprintf( stderr, "gzread: incomplete block read\n" );
551             return -1;
552         }
553
554         /*
555          * If we have to get a tar header
556          */
557         if( getheader == 1 )
558         {
559             /*
560              * If we met the end of the tar
561              * or the end-of-tar block, we are done
562              */
563             if( (len == 0) || (buffer.header.name[0] == 0) )
564             {
565                 break;
566             }
567
568             sprintf( fname, "%s/%s", prefix, buffer.header.name );
569
570             /* Check magic value in header */
571             if( strncmp( buffer.header.magic, "GNUtar", 6 ) &&
572                 strncmp( buffer.header.magic, "ustar", 5 ) )
573             {
574                 //fprintf(stderr, "not a tar file\n");
575                 return -1;
576             }
577
578             switch( buffer.header.typeflag )
579             {
580             case DIRTYPE:
581                 makedir( fname );
582                 break;
583             case REGTYPE:
584             case AREGTYPE:
585                 remaining = getoct( buffer.header.size, 12 );
586                 if( !remaining ) outfile = NULL; else
587                 {
588                     outfile = fopen( fname, "wb" );
589                     if( outfile == NULL )
590                     {
591                         /* try creating directory */
592                         char *p = strrchr( fname, '/' );
593                         if( p != NULL )
594                         {
595                             *p = '\0';
596                             makedir( fname );
597                             *p = '/';
598                             outfile = fopen( fname, "wb" );
599                             if( !outfile )
600                             {
601                                 fprintf( stderr, "tar couldn't create %s\n",
602                                          fname );
603                             }
604                         }
605                     }
606                 }
607
608                 /*
609                  * could have no contents
610                  */
611                 getheader = (remaining) ? 0 : 1;
612                 break;
613             default:
614                 break;
615             }
616         }
617         else
618         {
619             unsigned int bytes = (remaining > BLOCKSIZE)?BLOCKSIZE:remaining;
620
621             if( outfile != NULL )
622             {
623                 if( fwrite( &buffer, sizeof(char), bytes, outfile ) != bytes )
624                 {
625                     fprintf( stderr, "error writing %s skipping...\n", fname );
626                     fclose( outfile );
627                     outfile = NULL;
628                     unlink( fname );
629                 }
630             }
631             remaining -= bytes;
632             if( remaining == 0 )
633             {
634                 getheader = 1;
635                 if( outfile != NULL )
636                 {
637                     fclose(outfile);
638                     outfile = NULL;
639                 }
640             }
641         }
642     }
643
644     return 0;
645 }
646
647
648 int tar_close( TAR *t )
649 {
650     if( gzclose( *t ) != Z_OK ) fprintf( stderr, "failed gzclose\n" );
651     free( t );
652     return 0;
653 }
654
655
656 /* helper functions */
657 int getoct( char *p, int width )
658 {
659     int result = 0;
660     char c;
661
662     while( width-- )
663     {
664         c = *p++;
665         if( c == ' ' )
666             continue;
667         if( c == 0 )
668             break;
669         result = result * 8 + (c - '0');
670     }
671     return result;
672 }
673
674 #endif
675
676 #ifdef WIN32
677 #  define mkdir(dirname,mode) _mkdir(dirname)
678 #endif
679
680 /* Recursive make directory
681  * Abort if you get an ENOENT errno somewhere in the middle
682  * e.g. ignore error "mkdir on existing directory"
683  *
684  * return 1 if OK, 0 on error
685  */
686 int makedir( const char *newdir )
687 {
688     char *p, *buffer = strdup( newdir );
689     int  len = strlen( buffer );
690
691     if( len <= 0 )
692     {
693         free( buffer );
694         return 0;
695     }
696
697     if( buffer[len-1] == '/' )
698     {
699         buffer[len-1] = '\0';
700     }
701
702     if( mkdir( buffer, 0775 ) == 0 )
703     {
704         free( buffer );
705         return 1;
706     }
707
708     p = buffer + 1;
709     while( 1 )
710     {
711         char hold;
712
713         while( *p && *p != '\\' && *p != '/' ) p++;
714         hold = *p;
715         *p = 0;
716         if( ( mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
717         {
718             fprintf( stderr, "couldn't create directory %s\n", buffer );
719             free( buffer );
720             return 0;
721         }
722         if( hold == 0 ) break;
723         *p++ = hold;
724     }
725     free( buffer );
726     return 1;
727 }
728
729 #ifdef HAVE_ZLIB_H
730
731 static int currentGzFd = -1;
732 static void * currentGzVp = NULL;
733
734 int gzopen_frontend( const char *pathname, int oflags, int mode )
735 {
736     const char *gzflags;
737     gzFile gzf;
738
739     switch( oflags )
740     {
741     case O_WRONLY:
742         gzflags = "wb";
743         break;
744     case O_RDONLY:
745         gzflags = "rb";
746         break;
747     case O_RDWR:
748     default:
749         errno = EINVAL;
750         return -1;
751     }
752
753     gzf = gzopen( pathname, gzflags );
754     if( !gzf )
755     {
756         errno = ENOMEM;
757         return -1;
758     }
759
760     /** Hum ... */
761     currentGzFd = 42;
762     currentGzVp = gzf;
763
764     return currentGzFd;
765 }
766
767 int gzclose_frontend( int fd )
768 {
769     if( currentGzVp != NULL && fd != -1 )
770     {
771         void *toClose = currentGzVp;
772         currentGzVp = NULL;  currentGzFd = -1;
773         return gzclose( toClose );
774     }
775     return -1;
776 }
777
778 int gzread_frontend( int fd, void *p_buffer, size_t i_length )
779 {
780     if( currentGzVp != NULL && fd != -1 )
781     {
782         return gzread( currentGzVp, p_buffer, i_length );
783     }
784     return -1;
785 }
786
787 int gzwrite_frontend( int fd, const void * p_buffer, size_t i_length )
788 {
789     if( currentGzVp != NULL && fd != -1 )
790     {
791         return gzwrite( currentGzVp, const_cast<void*>(p_buffer), i_length );
792     }
793     return -1;
794 }
795
796 #endif