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