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