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