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