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