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