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