]> git.sesse.net Git - vlc/blob - modules/gui/skins2/src/theme_loader.cpp
added a function to minimize VLC (check win32 compilation)
[vlc] / modules / gui / skins2 / src / theme_loader.cpp
1 /*****************************************************************************
2  * theme_loader.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 VideoLAN
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., 59 Temple Place - Suite 330, Boston, MA  02111, 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/window_manager.hpp"
31
32 #ifdef HAVE_FCNTL_H
33 #   include <fcntl.h>
34 #endif
35 #ifdef HAVE_SYS_STAT_H
36 #   include <sys/stat.h>
37 #endif
38 #ifdef HAVE_UNISTD_H
39 #   include <unistd.h>
40 #elif defined( WIN32 )
41 #   include <direct.h>
42 #endif
43
44 #if (!defined( WIN32 ) || defined(__MINGW32__))
45 /* Mingw has its own version of dirent */
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 #if defined( HAVE_LIBTAR_H )
55 #   include <libtar.h>
56 #else
57 typedef gzFile TAR;
58 int tar_open        ( TAR **t, char *pathname, int oflags );
59 int tar_extract_all ( TAR *t, char *prefix );
60 int tar_close       ( TAR *t );
61 #endif
62 #endif
63
64 #define DEFAULT_XML_FILE "theme.xml"
65
66
67 bool ThemeLoader::load( const string &fileName )
68 {
69     // First, we try to un-targz the file, and if it fails we hope it's a XML
70     // file...
71 #if defined( HAVE_ZLIB_H )
72     if( ! extract( fileName ) && ! parse( fileName ) )
73         return false;
74 #else
75     if( ! parse( fileName ) )
76         return false;
77 #endif
78
79     Theme *pNewTheme = getIntf()->p_sys->p_theme;
80     if( !pNewTheme )
81     {
82         return false;
83     }
84
85     // Check if the skin to load is in the config file, to load its config
86     char *skin_last = config_GetPsz( getIntf(), "skins2-last" );
87     if( skin_last != NULL && fileName == (string)skin_last )
88     {
89         // Restore the theme configuration
90         getIntf()->p_sys->p_theme->loadConfig();
91         // Used to anchor the windows at the beginning
92         pNewTheme->getWindowManager().stopMove();
93     }
94     else
95     {
96         config_PutPsz( getIntf(), "skins2-last", fileName.c_str() );
97         // Show the windows
98         pNewTheme->getWindowManager().showAll();
99     }
100
101     return true;
102 }
103
104
105 #if defined( HAVE_ZLIB_H )
106 bool ThemeLoader::extractTarGz( const string &tarFile, const string &rootDir )
107 {
108     TAR *t;
109 #if defined( HAVE_LIBTAR_H )
110     tartype_t gztype = { (openfunc_t) gzopen_frontend, (closefunc_t) gzclose,
111         (readfunc_t) gzread, (writefunc_t) gzwrite };
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::extract( const string &fileName )
138 {
139     char *tmpdir = tempnam( NULL, "vlt" );
140     string tempPath = tmpdir;
141     free( tmpdir );
142
143     // Extract the file in a temporary directory
144     if( ! extractTarGz( fileName, tempPath ) )
145         return false;
146
147     // Find the XML file and parse it
148     string xmlFile;
149     if( ! findThemeFile( tempPath, xmlFile ) || ! parse( xmlFile ) )
150     {
151         msg_Err( getIntf(), "%s doesn't contain a " DEFAULT_XML_FILE " file",
152                  fileName.c_str() );
153         deleteTempFiles( tempPath );
154         return false;
155     }
156
157     // Clean-up
158     deleteTempFiles( tempPath );
159     return true;
160 }
161
162
163 void ThemeLoader::deleteTempFiles( const string &path )
164 {
165     OSFactory::instance( getIntf() )->rmDir( path );
166 }
167 #endif // HAVE_ZLIB_H
168
169
170 bool ThemeLoader::parse( const string &xmlFile )
171 {
172     // File loaded
173     msg_Dbg( getIntf(), "Using skin file: %s", xmlFile.c_str() );
174
175     // Extract the path of the XML file
176     string path;
177     const string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
178     unsigned int p = xmlFile.rfind( sep, xmlFile.size() );
179     if( p != string::npos )
180     {
181         path = xmlFile.substr( 0, p + 1 );
182     }
183     else
184     {
185         path = "";
186     }
187
188     // Start the parser
189     SkinParser parser( getIntf(), xmlFile, path );
190     if( ! parser.parse() )
191     {
192         msg_Err( getIntf(), "Failed to parse %s", xmlFile.c_str() );
193         return false;
194     }
195
196     // Build and store the theme
197     Builder builder( getIntf(), parser.getData() );
198     getIntf()->p_sys->p_theme = builder.build();
199
200     return true;
201 }
202
203
204 bool ThemeLoader::findThemeFile( const string &rootDir, string &themeFilePath )
205 {
206     // Path separator
207     const string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
208
209     DIR *pCurrDir;
210     struct dirent *pDirContent;
211
212     // Open the dir
213     pCurrDir = opendir( rootDir.c_str() );
214
215     if( pCurrDir == NULL )
216     {
217         // An error occurred
218         msg_Dbg( getIntf(), "Cannot open directory %s", rootDir.c_str() );
219         return false;
220     }
221
222     // Get the first directory entry
223     pDirContent = readdir( pCurrDir );
224
225     // While we still have entries in the directory
226     while( pDirContent != NULL )
227     {
228         string newURI = rootDir + sep + pDirContent->d_name;
229
230         // Skip . and ..
231         if( string( pDirContent->d_name ) != "." &&
232             string( pDirContent->d_name ) != ".." )
233         {
234 #if defined( S_ISDIR )
235             struct stat stat_data;
236             stat( newURI.c_str(), &stat_data );
237             if( S_ISDIR(stat_data.st_mode) )
238 #elif defined( DT_DIR )
239             if( pDirContent->d_type & DT_DIR )
240 #else
241             if( 0 )
242 #endif
243             {
244                 // Can we find the theme file in this subdirectory?
245                 if( findThemeFile( newURI, themeFilePath ) )
246                 {
247                     return true;
248                 }
249             }
250             else
251             {
252                 // Found the theme file?
253                 if( string( DEFAULT_XML_FILE ) ==
254                     string( pDirContent->d_name ) )
255                 {
256                     themeFilePath = newURI;
257                     return true;
258                 }
259             }
260         }
261
262         pDirContent = readdir( pCurrDir );
263     }
264
265     return false;
266 }
267
268
269 #if !defined( HAVE_LIBTAR_H ) && defined( HAVE_ZLIB_H )
270
271 #ifdef WIN32
272 #  define mkdir(dirname,mode) _mkdir(dirname)
273 #endif
274
275 /* Values used in typeflag field */
276 #define REGTYPE  '0'            /* regular file */
277 #define AREGTYPE '\0'           /* regular file */
278 #define DIRTYPE  '5'            /* directory */
279
280 #define BLOCKSIZE 512
281
282 struct tar_header
283 {                               /* byte offset */
284     char name[100];             /*   0 */
285     char mode[8];               /* 100 */
286     char uid[8];                /* 108 */
287     char gid[8];                /* 116 */
288     char size[12];              /* 124 */
289     char mtime[12];             /* 136 */
290     char chksum[8];             /* 148 */
291     char typeflag;              /* 156 */
292     char linkname[100];         /* 157 */
293     char magic[6];              /* 257 */
294     char version[2];            /* 263 */
295     char uname[32];             /* 265 */
296     char gname[32];             /* 297 */
297     char devmajor[8];           /* 329 */
298     char devminor[8];           /* 337 */
299     char prefix[155];           /* 345 */
300                                 /* 500 */
301 };
302
303
304 union tar_buffer {
305     char              buffer[BLOCKSIZE];
306     struct tar_header header;
307 };
308
309
310 /* helper functions */
311 int getoct( char *p, int width );
312 int makedir( char *newdir );
313
314 int tar_open( TAR **t, char *pathname, int oflags )
315 {
316     gzFile f = gzopen( pathname, "rb" );
317     if( f == NULL )
318     {
319         fprintf( stderr, "Couldn't gzopen %s\n", pathname );
320         return -1;
321     }
322
323     *t = (gzFile *)malloc( sizeof(gzFile) );
324     **t = f;
325     return 0;
326 }
327
328
329 int tar_extract_all( TAR *t, char *prefix )
330 {
331     union tar_buffer buffer;
332     int   len, err, getheader = 1, remaining = 0;
333     FILE  *outfile = NULL;
334     char  fname[BLOCKSIZE + PATH_MAX];
335
336     while( 1 )
337     {
338         len = gzread( *t, &buffer, BLOCKSIZE );
339         if( len < 0 )
340         {
341             fprintf( stderr, "%s\n", gzerror(*t, &err) );
342         }
343
344         /*
345          * Always expect complete blocks to process
346          * the tar information.
347          */
348         if( len != 0 && len != BLOCKSIZE )
349         {
350             fprintf( stderr, "gzread: incomplete block read\n" );
351             return -1;
352         }
353
354         /*
355          * If we have to get a tar header
356          */
357         if( getheader == 1 )
358         {
359             /*
360              * If we met the end of the tar
361              * or the end-of-tar block, we are done
362              */
363             if( (len == 0) || (buffer.header.name[0] == 0) )
364             {
365                 break;
366             }
367
368             sprintf( fname, "%s/%s", prefix, buffer.header.name );
369
370             /* Check magic value in header */
371             if( strncmp( buffer.header.magic, "GNUtar", 6 ) &&
372                 strncmp( buffer.header.magic, "ustar", 5 ) )
373             {
374                 //fprintf(stderr, "not a tar file\n");
375                 return -1;
376             }
377
378             switch( buffer.header.typeflag )
379             {
380                 case DIRTYPE:
381                     makedir( fname );
382                     break;
383                 case REGTYPE:
384                 case AREGTYPE:
385                     remaining = getoct( buffer.header.size, 12 );
386                     if( remaining )
387                     {
388                         outfile = fopen( fname, "wb" );
389                         if( outfile == NULL )
390                         {
391                             /* try creating directory */
392                             char *p = strrchr( fname, '/' );
393                             if( p != NULL )
394                             {
395                                 *p = '\0';
396                                 makedir( fname );
397                                 *p = '/';
398                                 outfile = fopen( fname, "wb" );
399                                 if( !outfile )
400                                 {
401                                     fprintf( stderr, "tar couldn't create %s\n",
402                                              fname );
403                                 }
404                             }
405                         }
406                     }
407                     else outfile = NULL;
408
409                 /*
410                  * could have no contents
411                  */
412                 getheader = (remaining) ? 0 : 1;
413                 break;
414             default:
415                 break;
416             }
417         }
418         else
419         {
420             unsigned int bytes = (remaining > BLOCKSIZE)?BLOCKSIZE:remaining;
421
422             if( outfile != NULL )
423             {
424                 if( fwrite( &buffer, sizeof(char), bytes, outfile ) != bytes )
425                 {
426                     fprintf( stderr, "error writing %s skipping...\n", fname );
427                     fclose( outfile );
428                     unlink( fname );
429                 }
430             }
431             remaining -= bytes;
432             if( remaining == 0 )
433             {
434                 getheader = 1;
435                 if( outfile != NULL )
436                 {
437                     fclose(outfile);
438                     outfile = NULL;
439                 }
440             }
441         }
442     }
443
444     return 0;
445 }
446
447
448 int tar_close( TAR *t )
449 {
450     if( gzclose( *t ) != Z_OK ) fprintf( stderr, "failed gzclose\n" );
451     free( t );
452     return 0;
453 }
454
455
456 /* helper functions */
457 int getoct( char *p, int width )
458 {
459     int result = 0;
460     char c;
461
462     while( width-- )
463     {
464         c = *p++;
465         if( c == ' ' )
466             continue;
467         if( c == 0 )
468             break;
469         result = result * 8 + (c - '0');
470     }
471     return result;
472 }
473
474
475 /* Recursive make directory
476  * Abort if you get an ENOENT errno somewhere in the middle
477  * e.g. ignore error "mkdir on existing directory"
478  *
479  * return 1 if OK, 0 on error
480  */
481 int makedir( char *newdir )
482 {
483     char *p, *buffer = strdup( newdir );
484     int  len = strlen( buffer );
485
486     if( len <= 0 )
487     {
488         free( buffer );
489         return 0;
490     }
491
492     if( buffer[len-1] == '/' )
493     {
494         buffer[len-1] = '\0';
495     }
496
497     if( mkdir( buffer, 0775 ) == 0 )
498     {
499         free( buffer );
500         return 1;
501     }
502
503     p = buffer + 1;
504     while( 1 )
505     {
506         char hold;
507
508         while( *p && *p != '\\' && *p != '/' ) p++;
509         hold = *p;
510         *p = 0;
511         if( ( mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
512         {
513             fprintf( stderr, "couldn't create directory %s\n", buffer );
514             free( buffer );
515             return 0;
516         }
517         if( hold == 0 ) break;
518         *p++ = hold;
519     }
520     free( buffer );
521     return 1;
522 }
523 #endif
524
525 #ifdef HAVE_ZLIB_H
526 int gzopen_frontend( char *pathname, int oflags, int mode )
527 {
528     char *gzflags;
529     gzFile gzf;
530
531     switch( oflags & O_ACCMODE )
532     {
533         case O_WRONLY:
534             gzflags = "wb";
535             break;
536         case O_RDONLY:
537             gzflags = "rb";
538             break;
539         case O_RDWR:
540         default:
541             errno = EINVAL;
542             return -1;
543     }
544
545     gzf = gzopen( pathname, gzflags );
546     if( !gzf )
547     {
548         errno = ENOMEM;
549         return -1;
550     }
551
552     return (int)gzf;
553 }
554 #endif