1 /*****************************************************************************
2 * theme_loader.cpp
3 *****************************************************************************
4 * Copyright (C) 2003 the VideoLAN team
5 * $Id: d018b4e92eb389897b02f72888dd79e67726a3a9 $
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fstream>
33 #include <memory>
34
35 #include <vlc_common.h>
36 #include <vlc_fs.h>
37 #include <vlc_url.h>
38 #include <vlc_stream_extractor.h>
39
40 #include "theme_loader.hpp"
41 #include "theme.hpp"
42 #include "../parser/builder.hpp"
43 #include "../parser/skin_parser.hpp"
44 #include "../src/os_factory.hpp"
45 #include "../src/vlcproc.hpp"
46 #include "../src/window_manager.hpp"
47
48 #define DEFAULT_XML_FILE "theme.xml"
49 #define WINAMP2_XML_FILE "winamp2.xml"
50
51 /* Recursive make directory
52 * Abort if you get an ENOENT errno somewhere in the middle
53 * e.g. ignore error "mkdir on existing directory"
54 *
55 * return 1 if OK, 0 on error
56 */
makedir(const char * newdir)57 static int makedir( const char *newdir )
58 {
59 char *p, *buffer = strdup( newdir );
60 int len = strlen( buffer );
61
62 if( len <= 0 )
63 {
64 free( buffer );
65 return 0;
66 }
67
68 if( buffer[len-1] == '/' )
69 {
70 buffer[len-1] = '\0';
71 }
72
73 if( vlc_mkdir( buffer, 0775 ) == 0 )
74 {
75 free( buffer );
76 return 1;
77 }
78
79 p = buffer + 1;
80 while( 1 )
81 {
82 char hold;
83
84 while( *p && *p != '\\' && *p != '/' ) p++;
85 hold = *p;
86 *p = 0;
87 if( ( vlc_mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
88 {
89 fprintf( stderr, "couldn't create directory %s\n", buffer );
90 free( buffer );
91 return 0;
92 }
93 if( hold == 0 ) break;
94 *p++ = hold;
95 }
96 free( buffer );
97 return 1;
98 }
99
load(const std::string & fileName)100 bool ThemeLoader::load( const std::string &fileName )
101 {
102 std::string path = getFilePath( fileName );
103
104 //Before all, let's see if the file is present
105 struct stat p_stat;
106 if( vlc_stat( fileName.c_str(), &p_stat ) )
107 return false;
108
109 // First, we try to un-targz the file, and if it fails we hope it's a XML
110 // file...
111
112 if( ! extract( fileName ) && ! parse( path, fileName ) )
113 return false;
114
115 Theme *pNewTheme = getIntf()->p_sys->p_theme;
116 if( !pNewTheme )
117 return false;
118
119 // Restore the theme configuration
120 getIntf()->p_sys->p_theme->loadConfig();
121
122 // Retain new loaded skins in config
123 config_PutPsz( getIntf(), "skins2-last", fileName.c_str() );
124
125 return true;
126 }
127
extract(const std::string & fileName)128 bool ThemeLoader::extract( const std::string &fileName )
129 {
130 bool result = true;
131 std::string tempPath = getTmpDir();
132 if( tempPath.empty() )
133 return false;
134
135 if( unarchive( fileName, tempPath ) == false )
136 {
137 msg_Err( getIntf(), "extraction from %s failed", fileName.c_str() );
138 return false;
139 }
140
141 std::string path;
142 std::string xmlFile;
143 OSFactory *pOsFactory = OSFactory::instance( getIntf() );
144 // Find the XML file in the theme
145 if( findFile( tempPath, DEFAULT_XML_FILE, xmlFile ) )
146 {
147 path = getFilePath( xmlFile );
148 }
149 else
150 {
151 // No XML file, check if it is a winamp2 skin
152 std::string mainBmp;
153 if( findFile( tempPath, "main.bmp", mainBmp ) )
154 {
155 msg_Dbg( getIntf(), "trying to load a winamp2 skin" );
156 path = getFilePath( mainBmp );
157
158 // Look for winamp2.xml in the resource path
159 std::list<std::string> resPath = pOsFactory->getResourcePath();
160 std::list<std::string>::const_iterator it;
161 for( it = resPath.begin(); it != resPath.end(); ++it )
162 {
163 if( findFile( *it, WINAMP2_XML_FILE, xmlFile ) )
164 break;
165 }
166 }
167 }
168
169 if( !xmlFile.empty() )
170 {
171 // Parse the XML file
172 if (! parse( path, xmlFile ) )
173 {
174 msg_Err( getIntf(), "error while parsing %s", xmlFile.c_str() );
175 result = false;
176 }
177 }
178 else
179 {
180 msg_Err( getIntf(), "no XML found in theme %s", fileName.c_str() );
181 result = false;
182 }
183
184 // Clean-up
185 deleteTempFiles( tempPath );
186 return result;
187 }
188
unarchive(const std::string & fileName,const std::string & tempPath)189 bool ThemeLoader::unarchive( const std::string& fileName, const std::string &tempPath )
190 {
191 #define UPTR_HELPER(type,deleter) []( type * data ) { \
192 return std::unique_ptr< type, decltype( deleter )> ( data, deleter ); }
193
194 auto make_input_node_ptr = UPTR_HELPER( input_item_node_t, &input_item_node_Delete );
195 auto make_input_item_ptr = UPTR_HELPER( input_item_t, &input_item_Release );
196 auto make_stream_ptr = UPTR_HELPER( stream_t, &vlc_stream_Delete );
197 auto make_cstr_ptr = UPTR_HELPER( char, &std::free );
198
199 #undef UPTR_HELPER
200
201 auto uri = make_cstr_ptr( vlc_path2uri( fileName.c_str(), "file" ) );
202 if( !uri )
203 {
204 msg_Err( getIntf(), "unable to convert %s to local URI",
205 fileName.c_str() );
206 return false;
207 }
208
209 auto input = make_stream_ptr( vlc_stream_NewURL( getIntf(), uri.get() ) );
210 if( !input )
211 {
212 msg_Err( getIntf(), "unable to open %s", uri.get() );
213 return false;
214 }
215
216 stream_t* stream = input.get();
217 if( vlc_stream_directory_Attach( &stream, NULL ) )
218 {
219 msg_Err( getIntf(), "unable to attach stream_directory, treat as XML!" );
220 }
221 else
222 {
223 input.release();
224 input.reset( stream );
225
226 auto item = make_input_item_ptr( input_item_New( "vlc://dummy", "vlc://dummy" ) );
227 auto node = make_input_node_ptr( (input_item_node_t*)std::calloc( 1, sizeof( input_item_node_t ) ) );
228
229 if( !item || !node )
230 return false;
231
232 input_item_AddOption( item.get(), "ignore-filetypes=\"\"", VLC_INPUT_OPTION_TRUSTED );
233 input_item_AddOption( item.get(), "extractor-flatten", VLC_INPUT_OPTION_TRUSTED );
234 node->p_item = item.release();
235
236 if( vlc_stream_ReadDir( input.get(), node.get() ) )
237 {
238 msg_Err( getIntf(), "unable to read items in %s", uri.get() );
239 return false;
240 }
241
242 for( int i = 0; i < node->i_children; ++i )
243 {
244 auto child = node->pp_children[i]->p_item;
245 auto child_stream = make_stream_ptr( vlc_stream_NewMRL( getIntf(), child->psz_uri ) );
246 if( !child_stream )
247 {
248 msg_Err( getIntf(), "unable to open %s for reading", child->psz_name );
249 return false;
250 }
251
252 auto out_path = tempPath + "/" + child->psz_name;
253
254 { /* create directory tree */
255 auto out_directory = out_path.substr( 0, out_path.find_last_of( '/' ) );
256
257 if( makedir( out_directory.c_str() ) == false )
258 {
259 msg_Err( getIntf(), "failed to create directory tree for %s (%s)",
260 out_path.c_str(), out_directory.c_str() );
261
262 return false;
263 }
264 }
265
266 { /* write data to disk */
267 std::string contents;
268
269 char buf[1024];
270 ssize_t n;
271
272 while( ( n = vlc_stream_Read( child_stream.get(), buf, sizeof buf ) ) > 0 )
273 contents.append( buf, n );
274
275 std::ofstream out_stream( out_path, std::ios::binary );
276
277 if( out_stream.write( contents.data(), contents.size() ) )
278 {
279 msg_Dbg( getIntf(), "finished writing %zu bytes to %s",
280 size_t{ contents.size() }, out_path.c_str() );
281 }
282 else
283 {
284 msg_Err( getIntf(), "unable to write %zu bytes to %s",
285 size_t{ contents.size() }, out_path.c_str() );
286 return false;
287 }
288 }
289 }
290 }
291
292 return true;
293 }
294
deleteTempFiles(const std::string & path)295 void ThemeLoader::deleteTempFiles( const std::string &path )
296 {
297 OSFactory::instance( getIntf() )->rmDir( path );
298 }
299
parse(const std::string & path,const std::string & xmlFile)300 bool ThemeLoader::parse( const std::string &path, const std::string &xmlFile )
301 {
302 // File loaded
303 msg_Dbg( getIntf(), "using skin file: %s", xmlFile.c_str() );
304
305 // Start the parser
306 SkinParser parser( getIntf(), xmlFile, path );
307 if( ! parser.parse() )
308 return false;
309
310 // Build and store the theme
311 Builder builder( getIntf(), parser.getData(), path );
312 getIntf()->p_sys->p_theme = builder.build();
313
314 return true;
315 }
316
317
getFilePath(const std::string & rFullPath)318 std::string ThemeLoader::getFilePath( const std::string &rFullPath )
319 {
320 OSFactory *pOsFactory = OSFactory::instance( getIntf() );
321 const std::string &sep = pOsFactory->getDirSeparator();
322 // Find the last separator ('/' or '\')
323 std::string::size_type p = rFullPath.rfind( sep, rFullPath.size() );
324 std::string basePath;
325 if( p != std::string::npos )
326 {
327 if( p < rFullPath.size() - 1)
328 {
329 basePath = rFullPath.substr( 0, p );
330 }
331 else
332 {
333 basePath = rFullPath;
334 }
335 }
336 return basePath;
337 }
338
findFile(const std::string & rootDir,const std::string & rFileName,std::string & themeFilePath)339 bool ThemeLoader::findFile( const std::string &rootDir, const std::string &rFileName,
340 std::string &themeFilePath )
341 {
342 // Path separator
343 const std::string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
344
345 const char *pszDirContent;
346
347 // Open the dir
348 DIR *pCurrDir = vlc_opendir( rootDir.c_str() );
349
350 if( pCurrDir == NULL )
351 {
352 // An error occurred
353 msg_Dbg( getIntf(), "cannot open directory %s", rootDir.c_str() );
354 return false;
355 }
356
357 // While we still have entries in the directory
358 while( ( pszDirContent = vlc_readdir( pCurrDir ) ) != NULL )
359 {
360 std::string newURI = rootDir + sep + pszDirContent;
361
362 // Skip . and ..
363 if( std::string( pszDirContent ) != "." &&
364 std::string( pszDirContent ) != ".." )
365 {
366 #if defined( S_ISDIR )
367 struct stat stat_data;
368
369 if( ( vlc_stat( newURI.c_str(), &stat_data ) == 0 )
370 && S_ISDIR(stat_data.st_mode) )
371 #elif defined( DT_DIR )
372 if( pDirContent->d_type & DT_DIR )
373 #else
374 if( 0 )
375 #endif
376 {
377 // Can we find the file in this subdirectory?
378 if( findFile( newURI, rFileName, themeFilePath ) )
379 {
380 closedir( pCurrDir );
381 return true;
382 }
383 }
384 else
385 {
386 // Found the theme file?
387 if( rFileName == std::string( pszDirContent ) )
388 {
389 themeFilePath = newURI;
390 closedir( pCurrDir );
391 return true;
392 }
393 }
394 }
395 }
396
397 closedir( pCurrDir );
398 return false;
399 }
400
401 // FIXME: could become a skins2 OS factory function or a vlc core function
getTmpDir()402 std::string ThemeLoader::getTmpDir( )
403 {
404 #if defined( _WIN32 )
405 wchar_t *tmpdir = _wtempnam( NULL, L"vlt" );
406 if( tmpdir == NULL )
407 return "";
408 char* utf8 = FromWide( tmpdir );
409 free( tmpdir );
410 std::string tempPath( utf8 ? utf8 : "" );
411 free( utf8 );
412 return tempPath;
413
414 #elif defined( __OS2__ )
415 char *tmpdir = tempnam( NULL, "vlt" );
416 if( tmpdir == NULL )
417 return "";
418 std::string tempPath( sFromLocale( tmpdir ));
419 free( tmpdir );
420 return tempPath;
421
422 #else
423 char templ[] = "/tmp/vltXXXXXX";
424 char *tmpdir = mkdtemp( templ );
425 return std::string( tmpdir ? tmpdir : "");
426 #endif
427 }
428
429
430