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