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