>From bdf641fc1ab60234ad1a362bc87ebac4c9b0356e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Filip=20Ros=C3=A9en?= <filip@atch.se>
Date: Fri, 16 Dec 2016 13:28:34 +0100
Subject: [PATCH 3/8] gui/skins2: add ThemeLoader::unarchive
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Given the newly introduced libarchive based stream-extractor, we do
not need duplicate functionality implemented within this module. First
step in the transition to using the stream-extractor is to implement
a function that does the work (which is what these changes are about).

--

REVISION #1:

This implementation could be written in a cleaner manner, though the
best approach would be to have changes made to the core (to simplify
the API used). I will submit an independent patch for a refactor of
this function at a later time.

On 2016-01-13 12:47, Rémi Denis-Courmont wrote:

> Does C++ file I/O handle the atomic close on exec flag correctly?

I have moved the writing through std::ofstream to a place where `exec`
related functions are guaranteed not to be called in order to
circumvent the issue of The C++ Standard not specifying the behavior
for handles used together with `exec`-related functions.

Good catch, thanks!

> Does it handle character sets correctly (especially on Windows)?

The ISO C++ Standard does not specifiy how characters sets are handled
in terms of `std::ofstream` (as such is it platform dependent, as you
correctly hints in your message).

It is however worth pointing out that the filename handling in the
relevant module is the same at all places, meaning that even if a
destination file ends up having a "weird" name ("physically" on disk),
it would consitantly be weird - so it will still be read/found as it
should.

> And even if it does, do we want yet another file I/O API?

In the future the entire "extraction" of files within a theme-archive
should be rewritten, for starters there is nothing that mandates the
extracted files to be written to disc (we should keep them in memory
instead of writing to disk, only to remove them when we are done).

I have a toy implementation that extracts the relevant files into
memory; but the changes are more intrusive as so much code currently
relies on the data being on disk (so for simplicity I will propose
these changes at a later time).

REVISION #2:

Given the split of stream-extractor capabilities, this patch now uses
vlc_stream_directory_Attach instead of vlc_stream_extractor_Attach.
---
 modules/gui/skins2/src/theme_loader.cpp | 118 ++++++++++++++++++++++++++++++++
 modules/gui/skins2/src/theme_loader.hpp |   2 +
 2 files changed, 120 insertions(+)

diff --git a/modules/gui/skins2/src/theme_loader.cpp b/modules/gui/skins2/src/theme_loader.cpp
index 31f1c550df..f6c5f80690 100644
--- a/modules/gui/skins2/src/theme_loader.cpp
+++ b/modules/gui/skins2/src/theme_loader.cpp
@@ -29,9 +29,13 @@
 #include <fcntl.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <fstream>
+#include <memory>
 
 #include <vlc_common.h>
 #include <vlc_fs.h>
+#include <vlc_url.h>
+#include <vlc_stream_extractor.h>
 
 #include "theme_loader.hpp"
 #include "theme.hpp"
@@ -351,6 +355,120 @@ bool ThemeLoader::extract( const std::string &fileName )
     return result;
 }
 
+bool ThemeLoader::unarchive( const std::string& fileName, const std::string &tempPath )
+{
+#define UPTR_HELPER(type,deleter) []( type * data ) { \
+        return std::unique_ptr< type, decltype( deleter )> ( data, deleter ); }
+
+    auto make_input_node_ptr = UPTR_HELPER( input_item_node_t, &input_item_node_Delete );
+    auto make_input_item_ptr = UPTR_HELPER( input_item_t, &input_item_Release );
+    auto make_stream_ptr = UPTR_HELPER( stream_t, &vlc_stream_Delete );
+    auto make_cstr_ptr = UPTR_HELPER( char, &std::free );
+
+#undef UPTR_HELPER
+
+    auto uri = make_cstr_ptr( vlc_path2uri( fileName.c_str(), "file" ) );
+    if( !uri )
+    {
+        msg_Err( getIntf(), "unable to convert %s to local URI",
+                            fileName.c_str() );
+        return false;
+    }
+
+    auto input = make_stream_ptr( vlc_stream_NewURL( getIntf(), uri.get() ) );
+    if( !input )
+    {
+        msg_Err( getIntf(), "unable to open %s", uri.get() );
+        return false;
+    }
+
+    stream_t* stream = input.get();
+    if( vlc_stream_directory_Attach( &stream, NULL ) )
+    {
+        msg_Err( getIntf(), "unable to attach stream_directory, treat as XML!" );
+    }
+    else
+    {
+        input.release();
+        input.reset( stream );
+
+        auto item = make_input_item_ptr( input_item_New( "vlc://dummy", "vlc://dummy" ) );
+        auto node = make_input_node_ptr( (input_item_node_t*)std::calloc( 1, sizeof( input_item_node_t ) ) );
+
+        if( !item || !node )
+            return false;
+
+        node->p_item = item.release();
+
+        if( vlc_stream_ReadDir( input.get(), node.get() ) )
+        {
+            msg_Err( getIntf(), "unable to read items in %s", uri.get() );
+            return false;
+        }
+
+        for( int i = 0; i < node->i_children; ++i )
+        {
+            auto child = node->pp_children[i]->p_item;
+            auto child_stream = make_stream_ptr( vlc_stream_NewURL( getIntf(), uri.get() ) );
+            if( !child_stream )
+            {
+                msg_Err( getIntf(), "unable to open %s for reading", child->psz_name );
+            }
+
+            stream_t* stream = child_stream.get();
+            if( vlc_stream_extractor_Attach( &stream, child->psz_name, NULL ) )
+            {
+                msg_Err( getIntf(), "unable to locate %s within %s",
+                         child->psz_name, fileName.c_str() );
+
+                return false;
+            }
+
+            child_stream.release();
+            child_stream.reset( stream );
+
+            auto out_path = tempPath + "/" + child->psz_name;
+
+            { /* create directory tree */
+                auto out_directory = out_path.substr( 0, out_path.find_last_of( '/' ) );
+
+                if( makedir( out_directory.c_str() ) == false )
+                {
+                    msg_Err( getIntf(), "failed to create directory tree for %s (%s)",
+                             out_path.c_str(), out_directory.c_str() );
+
+                    return false;
+                }
+            }
+
+            { /* write data to disk */
+                std::string contents;
+
+                char buf[1024];
+                ssize_t n;
+
+                while( ( n = vlc_stream_Read( child_stream.get(), buf, sizeof buf ) ) > 0 )
+                    contents.append( buf, n );
+
+                std::ofstream out_stream( out_path, std::ios::binary );
+
+                if( out_stream.write( contents.data(), contents.size() ) )
+                {
+                    msg_Dbg( getIntf(), "finished writing %zu bytes to %s",
+                        size_t{ contents.size() }, out_path.c_str() );
+                }
+                else
+                {
+                    msg_Err( getIntf(), "unable to write %zu bytes to %s",
+                        size_t{ contents.size() }, out_path.c_str() );
+                    return false;
+                }
+            }
+        }
+    }
+
+    return true;
+}
 
 void ThemeLoader::deleteTempFiles( const std::string &path )
 {
diff --git a/modules/gui/skins2/src/theme_loader.hpp b/modules/gui/skins2/src/theme_loader.hpp
index b922cd367f..e5b35b150f 100644
--- a/modules/gui/skins2/src/theme_loader.hpp
+++ b/modules/gui/skins2/src/theme_loader.hpp
@@ -48,6 +48,8 @@ private:
      */
     bool extract( const std::string &fileName );
 
+    bool unarchive( const std::string &fileName, const std::string &tempPath );
+
     /// Extract files from a tar.gz archive
     /**
      * Expects strings from the current locale.
-- 
2.11.1

