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