1 /*****************************************************************************
3 *****************************************************************************
4 * Copyright (C) 2003 the VideoLAN team
6 * Authors: Cyril Deguet <asmax@via.ecp.fr>
7 * Olivier Teulière <ipkiss@via.ecp.fr>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
34 #include <vlc_common.h>
37 #include <vlc_stream_extractor.h>
39 #include "theme_loader.hpp"
41 #include "../parser/builder.hpp"
42 #include "../parser/skin_parser.hpp"
43 #include "../src/os_factory.hpp"
44 #include "../src/vlcproc.hpp"
45 #include "../src/window_manager.hpp"
47 #define DEFAULT_XML_FILE "theme.xml"
48 #define WINAMP2_XML_FILE "winamp2.xml"
50 /* Recursive make directory
51 * Abort if you get an ENOENT errno somewhere in the middle
52 * e.g. ignore error "mkdir on existing directory"
54 * return 1 if OK, 0 on error
56 static int makedir( const char *newdir
)
58 char *p
, *buffer
= strdup( newdir
);
59 int len
= strlen( buffer
);
67 if( buffer
[len
-1] == '/' )
72 if( vlc_mkdir( buffer
, 0775 ) == 0 )
83 while( *p
&& *p
!= '\\' && *p
!= '/' ) p
++;
86 if( ( vlc_mkdir( buffer
, 0775 ) == -1 ) && ( errno
== ENOENT
) )
88 fprintf( stderr
, "couldn't create directory %s\n", buffer
);
92 if( hold
== 0 ) break;
99 bool ThemeLoader::load( const std::string
&fileName
)
101 std::string path
= getFilePath( fileName
);
103 //Before all, let's see if the file is present
105 if( vlc_stat( fileName
.c_str(), &p_stat
) )
108 // First, we try to un-targz the file, and if it fails we hope it's a XML
111 if( ! extract( fileName
) && ! parse( path
, fileName
) )
114 Theme
*pNewTheme
= getIntf()->p_sys
->p_theme
;
118 // Restore the theme configuration
119 getIntf()->p_sys
->p_theme
->loadConfig();
121 // Retain new loaded skins in config
122 config_PutPsz( "skins2-last", fileName
.c_str() );
127 bool ThemeLoader::extract( const std::string
&fileName
)
130 std::string tempPath
= getTmpDir();
131 if( tempPath
.empty() )
134 if( unarchive( fileName
, tempPath
) == false )
136 msg_Err( getIntf(), "extraction from %s failed", fileName
.c_str() );
142 OSFactory
*pOsFactory
= OSFactory::instance( getIntf() );
143 // Find the XML file in the theme
144 if( findFile( tempPath
, DEFAULT_XML_FILE
, xmlFile
) )
146 path
= getFilePath( xmlFile
);
150 // No XML file, check if it is a winamp2 skin
152 if( findFile( tempPath
, "main.bmp", mainBmp
) )
154 msg_Dbg( getIntf(), "trying to load a winamp2 skin" );
155 path
= getFilePath( mainBmp
);
157 // Look for winamp2.xml in the resource path
158 std::list
<std::string
> resPath
= pOsFactory
->getResourcePath();
159 std::list
<std::string
>::const_iterator it
;
160 for( it
= resPath
.begin(); it
!= resPath
.end(); ++it
)
162 if( findFile( *it
, WINAMP2_XML_FILE
, xmlFile
) )
168 if( !xmlFile
.empty() )
170 // Parse the XML file
171 if (! parse( path
, xmlFile
) )
173 msg_Err( getIntf(), "error while parsing %s", xmlFile
.c_str() );
179 msg_Err( getIntf(), "no XML found in theme %s", fileName
.c_str() );
184 deleteTempFiles( tempPath
);
188 bool ThemeLoader::unarchive( const std::string
& fileName
, const std::string
&tempPath
)
190 #define UPTR_HELPER(type,deleter) []( type * data ) { \
191 return std::unique_ptr< type, decltype( deleter )> ( data, deleter ); }
193 auto make_input_node_ptr
= UPTR_HELPER( input_item_node_t
, &input_item_node_Delete
);
194 auto make_input_item_ptr
= UPTR_HELPER( input_item_t
, &input_item_Release
);
195 auto make_stream_ptr
= UPTR_HELPER( stream_t
, &vlc_stream_Delete
);
196 auto make_cstr_ptr
= UPTR_HELPER( char, &std::free
);
200 auto uri
= make_cstr_ptr( vlc_path2uri( fileName
.c_str(), "file" ) );
203 msg_Err( getIntf(), "unable to convert %s to local URI",
208 auto input
= make_stream_ptr( vlc_stream_NewURL( getIntf(), uri
.get() ) );
211 msg_Err( getIntf(), "unable to open %s", uri
.get() );
215 stream_t
* stream
= input
.get();
216 if( vlc_stream_directory_Attach( &stream
, NULL
) )
218 msg_Err( getIntf(), "unable to attach stream_directory, treat as XML!" );
223 input
.reset( stream
);
225 auto item
= make_input_item_ptr( input_item_New( "vlc://dummy", "vlc://dummy" ) );
226 auto node
= make_input_node_ptr( (input_item_node_t
*)std::calloc( 1, sizeof( input_item_node_t
) ) );
231 input_item_AddOption( item
.get(), "ignore-filetypes=\"\"", VLC_INPUT_OPTION_TRUSTED
);
232 input_item_AddOption( item
.get(), "extractor-flatten", VLC_INPUT_OPTION_TRUSTED
);
233 node
->p_item
= item
.release();
235 if( vlc_stream_ReadDir( input
.get(), node
.get() ) )
237 msg_Err( getIntf(), "unable to read items in %s", uri
.get() );
241 for( int i
= 0; i
< node
->i_children
; ++i
)
243 auto child
= node
->pp_children
[i
]->p_item
;
244 auto child_stream
= make_stream_ptr( vlc_stream_NewMRL( getIntf(), child
->psz_uri
) );
247 msg_Err( getIntf(), "unable to open %s for reading", child
->psz_name
);
251 auto out_path
= tempPath
+ "/" + child
->psz_name
;
253 { /* create directory tree */
254 auto out_directory
= out_path
.substr( 0, out_path
.find_last_of( '/' ) );
256 if( makedir( out_directory
.c_str() ) == false )
258 msg_Err( getIntf(), "failed to create directory tree for %s (%s)",
259 out_path
.c_str(), out_directory
.c_str() );
265 { /* write data to disk */
266 std::string contents
;
271 while( ( n
= vlc_stream_Read( child_stream
.get(), buf
, sizeof buf
) ) > 0 )
272 contents
.append( buf
, n
);
274 std::ofstream
out_stream( out_path
, std::ios::binary
);
276 if( out_stream
.write( contents
.data(), contents
.size() ) )
278 msg_Dbg( getIntf(), "finished writing %zu bytes to %s",
279 size_t{ contents
.size() }, out_path
.c_str() );
283 msg_Err( getIntf(), "unable to write %zu bytes to %s",
284 size_t{ contents
.size() }, out_path
.c_str() );
294 void ThemeLoader::deleteTempFiles( const std::string
&path
)
296 OSFactory::instance( getIntf() )->rmDir( path
);
299 bool ThemeLoader::parse( const std::string
&path
, const std::string
&xmlFile
)
302 msg_Dbg( getIntf(), "using skin file: %s", xmlFile
.c_str() );
305 SkinParser
parser( getIntf(), xmlFile
, path
);
306 if( ! parser
.parse() )
309 // Build and store the theme
310 Builder
builder( getIntf(), parser
.getData(), path
);
311 getIntf()->p_sys
->p_theme
= builder
.build();
317 std::string
ThemeLoader::getFilePath( const std::string
&rFullPath
)
319 OSFactory
*pOsFactory
= OSFactory::instance( getIntf() );
320 const std::string
&sep
= pOsFactory
->getDirSeparator();
321 // Find the last separator ('/' or '\')
322 std::string::size_type p
= rFullPath
.rfind( sep
, rFullPath
.size() );
323 std::string basePath
;
324 if( p
!= std::string::npos
)
326 if( p
< rFullPath
.size() - 1)
328 basePath
= rFullPath
.substr( 0, p
);
332 basePath
= rFullPath
;
338 bool ThemeLoader::findFile( const std::string
&rootDir
, const std::string
&rFileName
,
339 std::string
&themeFilePath
)
342 const std::string
&sep
= OSFactory::instance( getIntf() )->getDirSeparator();
344 const char *pszDirContent
;
347 DIR *pCurrDir
= vlc_opendir( rootDir
.c_str() );
349 if( pCurrDir
== NULL
)
352 msg_Dbg( getIntf(), "cannot open directory %s", rootDir
.c_str() );
356 // While we still have entries in the directory
357 while( ( pszDirContent
= vlc_readdir( pCurrDir
) ) != NULL
)
359 std::string newURI
= rootDir
+ sep
+ pszDirContent
;
362 if( std::string( pszDirContent
) != "." &&
363 std::string( pszDirContent
) != ".." )
365 #if defined( S_ISDIR )
366 struct stat stat_data
;
368 if( ( vlc_stat( newURI
.c_str(), &stat_data
) == 0 )
369 && S_ISDIR(stat_data
.st_mode
) )
370 #elif defined( DT_DIR )
371 if( pDirContent
->d_type
& DT_DIR
)
376 // Can we find the file in this subdirectory?
377 if( findFile( newURI
, rFileName
, themeFilePath
) )
379 closedir( pCurrDir
);
385 // Found the theme file?
386 if( rFileName
== std::string( pszDirContent
) )
388 themeFilePath
= newURI
;
389 closedir( pCurrDir
);
396 closedir( pCurrDir
);
400 // FIXME: could become a skins2 OS factory function or a vlc core function
401 std::string
ThemeLoader::getTmpDir( )
403 #if defined( _WIN32 )
404 wchar_t *tmpdir
= _wtempnam( NULL
, L
"vlt" );
407 char* utf8
= FromWide( tmpdir
);
409 std::string
tempPath( utf8
? utf8
: "" );
413 #elif defined( __OS2__ )
414 char *tmpdir
= tempnam( NULL
, "vlt" );
417 std::string
tempPath( sFromLocale( tmpdir
));
422 char templ
[] = "/tmp/vltXXXXXX";
423 char *tmpdir
= mkdtemp( templ
);
424 return std::string( tmpdir
? tmpdir
: "");