[gexiv2/wip/phako/gio: 12/12] wip: GIO write support



commit 49036260188bc6625e0636ab6f3308aa76586a58
Author: Jens Georg <mail jensge org>
Date:   Wed Jan 2 15:58:08 2019 +0100

    wip: GIO write support

 gexiv2/gexiv2-metadata-gio.cpp | 333 +++++++++++++++++++++++++++++++++++++++++
 gexiv2/gexiv2-metadata-gio.h   | 165 ++++++++++++++++++++
 gexiv2/gexiv2-metadata.cpp     | 210 +++-----------------------
 gexiv2/gexiv2-metadata.h       |  12 ++
 gexiv2/meson.build             |   2 +
 tools/gexiv2-copy.vala         |  43 ++++++
 tools/meson.build              |   6 +
 7 files changed, 581 insertions(+), 190 deletions(-)
---
diff --git a/gexiv2/gexiv2-metadata-gio.cpp b/gexiv2/gexiv2-metadata-gio.cpp
new file mode 100644
index 0000000..c30133b
--- /dev/null
+++ b/gexiv2/gexiv2-metadata-gio.cpp
@@ -0,0 +1,333 @@
+#include "gexiv2-metadata-gio.h"
+
+namespace GExiv2 {
+namespace Internal {
+GioIo::GioIo(GInputStream *is)
+    : BasicIo ()
+    , _is (G_INPUT_STREAM (g_object_ref (is)))
+    , _seekable(G_IS_SEEKABLE(_is) ? G_SEEKABLE (_is) : nullptr)
+    , _error{nullptr}
+    , _eof{false}
+{}
+
+GioIo::~GioIo()
+{
+    g_clear_object (&_is);
+    g_clear_error (&_error);
+    _seekable = nullptr;
+}
+
+int GioIo::open()
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    return this->seek(0, Exiv2::BasicIo::beg);
+}
+
+Exiv2::DataBuf GioIo::read(long rcount)
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    Exiv2::DataBuf b{rcount};
+
+    long bytes_read = this->read(b.pData_, rcount);
+    b.size_ = bytes_read;
+
+    return b;
+}
+
+long GioIo::read(Exiv2::byte *buf, long rcount)
+{
+    g_debug ("%s %ld", __PRETTY_FUNCTION__, rcount);
+    GError *error = NULL;
+    gssize result = 0;
+
+    result = g_input_stream_read(_is, reinterpret_cast<void *>(buf), rcount, NULL, &error);
+    if (error != NULL) {
+        g_critical ("Error reading from stream: %d %s", error->code, error->message);
+        g_clear_error (&_error);
+        _error = error;
+
+#if EXIV2_TEST_VERSION(0,27,0)
+        throw Exiv2::Error(Exiv2::ErrorCode::kerFailedToReadImageData);
+#else
+        throw Exiv2::Error(2);
+#endif
+        return 0;
+    }
+
+    if (result == 0) {
+        _eof = true;
+    } else {
+        _eof = false;
+    }
+
+    return result;
+}
+
+int GioIo::getb()
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    Exiv2::byte b;
+    return this->read (&b, 1) == 1 ? b : EOF;
+}
+
+int GioIo::seek(seek_offset_t offset, Exiv2::BasicIo::Position position)
+{
+    g_debug ("%s %ld %d", __PRETTY_FUNCTION__, offset, (int)position);
+    if (_seekable != NULL && g_seekable_can_seek (_seekable)) {
+        GSeekType t = G_SEEK_SET;
+        switch (position) {
+            case Exiv2::BasicIo::cur:
+                t = G_SEEK_CUR;
+                break;
+            case Exiv2::BasicIo::beg:
+                t = G_SEEK_SET;
+                break;
+            case Exiv2::BasicIo::end:
+                t = G_SEEK_END;
+                break;
+            default:
+                g_assert_not_reached ();
+                break;
+        }
+
+        GError *error = NULL;
+        g_seekable_seek (_seekable, offset, t, NULL, &error);
+        if (error != NULL) {
+            g_clear_error(&_error);
+            g_critical ("Failed to seek: %s", error->message);
+            _error = error;
+
+            return -1;
+        }
+
+        return 0;
+    } else {
+        // Can only seek forward here...
+        if (position != Exiv2::BasicIo::cur) {
+            return -1;
+        }
+
+        GError *error = NULL;
+        g_input_stream_skip (_is, offset, NULL, &error);
+        if (error != NULL) {
+            g_clear_error(&_error);
+            _error = error;
+            g_critical("Failed to seek forward: %s", error->message);
+
+            return -1;
+        }
+
+        return 0;
+    }
+}
+
+long GioIo::tell() const
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    if (_seekable != NULL && g_seekable_can_seek (_seekable)) {
+        return static_cast<long>(g_seekable_tell (_seekable));
+    } else {
+        return -1;
+    }
+}
+
+GioRwIo::GioRwIo(GIOStream *is)
+    : BasicIo ()
+    , _is (G_IO_STREAM (g_object_ref (is)))
+    , _seekable(G_IS_SEEKABLE(_is) ? G_SEEKABLE (_is) : nullptr)
+    , _error{nullptr}
+    , _eof{false}
+{
+    if (not _seekable) {
+        _seekable = G_SEEKABLE (g_io_stream_get_input_stream (_is));
+    }
+    g_debug ("%s %p", __PRETTY_FUNCTION__, _seekable);
+}
+
+GioRwIo::~GioRwIo()
+{
+    g_clear_object (&_is);
+    g_clear_error (&_error);
+    _seekable = nullptr;
+}
+
+int GioRwIo::open()
+{
+    g_output_stream_flush (g_io_stream_get_output_stream (_is), nullptr, nullptr);
+    return this->seek(0, Exiv2::BasicIo::beg);
+}
+
+int GioRwIo::close()
+{
+    g_output_stream_flush (g_io_stream_get_output_stream (_is), nullptr, nullptr);
+    return 0;
+}
+
+
+Exiv2::DataBuf GioRwIo::read(long rcount)
+{
+    g_debug ("%s %ld", __PRETTY_FUNCTION__, rcount);
+    Exiv2::DataBuf b{rcount};
+
+    long bytes_read = this->read(b.pData_, rcount);
+    b.size_ = bytes_read;
+
+    return b;
+}
+
+long GioRwIo::read(Exiv2::byte *buf, long rcount)
+{
+    g_debug ("%s %ld", __PRETTY_FUNCTION__, rcount);
+    GError *error = NULL;
+    gssize result = 0;
+    GInputStream *is = g_io_stream_get_input_stream (_is);
+
+    result = g_input_stream_read(is, reinterpret_cast<void *>(buf), rcount, NULL, &error);
+    if (error != NULL) {
+        g_critical ("Error reading from stream: %d %s", error->code, error->message);
+        g_clear_error (&_error);
+        _error = error;
+
+#if EXIV2_TEST_VERSION(0,27,0)
+        throw Exiv2::Error(Exiv2::ErrorCode::kerFailedToReadImageData);
+#else
+        throw Exiv2::Error(2);
+#endif
+        return 0;
+    }
+
+    if (result == 0) {
+        _eof = true;
+    } else {
+        _eof = false;
+    }
+
+    return result;
+}
+
+int GioRwIo::getb()
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    Exiv2::byte b;
+    return this->read (&b, 1) == 1 ? b : EOF;
+}
+
+int GioRwIo::seek(seek_offset_t offset, Exiv2::BasicIo::Position position)
+{
+    g_debug ("%s %ld %d", __PRETTY_FUNCTION__, offset, (int)position);
+    if (_seekable != NULL && g_seekable_can_seek (_seekable)) {
+        GSeekType t = G_SEEK_SET;
+        switch (position) {
+            case Exiv2::BasicIo::cur:
+                t = G_SEEK_CUR;
+                break;
+            case Exiv2::BasicIo::beg:
+                t = G_SEEK_SET;
+                break;
+            case Exiv2::BasicIo::end:
+                t = G_SEEK_END;
+                break;
+            default:
+                g_assert_not_reached ();
+                break;
+        }
+
+        GError *error = NULL;
+        auto r = g_seekable_seek (_seekable, offset, t, NULL, &error);
+        g_debug ("Seeking success: %d", r);
+        if (error != NULL) {
+            g_clear_error(&_error);
+            g_critical ("Failed to seek: %s", error->message);
+            _error = error;
+
+            return -1;
+        }
+
+        return 0;
+    } else {
+        g_warning ("No seekable!");
+        return -1;
+    }
+}
+
+long GioRwIo::tell() const
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    if (_seekable != NULL && g_seekable_can_seek (_seekable)) {
+        return static_cast<long>(g_seekable_tell (_seekable));
+    } else {
+        return -1;
+    }
+}
+
+long GioRwIo::write(const Exiv2::byte *data, long wcount)
+{
+    g_debug ("%s: %ld", __PRETTY_FUNCTION__, wcount);
+    return EOF;
+}
+
+long GioRwIo::write(BasicIo &src)
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    return 0;
+}
+
+int GioRwIo::putb(Exiv2::byte data)
+{
+    g_debug ("%s", __PRETTY_FUNCTION__);
+    return this->write(&data, 1);
+}
+
+void GioRwIo::transfer(Exiv2::BasicIo &src)
+{
+    g_debug ("%s %s %ld %ld ", __PRETTY_FUNCTION__,
+             typeid(src).name(),
+             src.size(),
+             src.tell());
+    GioRwIo *gio = dynamic_cast<GioRwIo *>(&src);
+    GError *error = nullptr;
+
+    // rewind everything
+    src.open();
+    this->open();
+    g_input_stream_close (g_io_stream_get_input_stream (_is), nullptr, &error);
+
+    if (gio != nullptr) {
+        // Shortcut, use GIO's stream splicing for this
+        g_output_stream_splice (g_io_stream_get_output_stream (_is),
+                g_io_stream_get_input_stream (gio->_is),
+                G_OUTPUT_STREAM_SPLICE_NONE,
+                nullptr,
+                &error);
+
+        if (error != nullptr) {
+            g_warning ("Failed to splice: %s", error->message);
+            g_clear_error (&_error);
+            _error = error;
+        }
+    } else {
+        auto os = g_io_stream_get_output_stream (_is);
+        while (not src.eof()) {
+            auto buf = src.read(4096);
+            if (buf.size_ > 0) {
+                g_debug ("Got %ld bytes, writing", buf.size_);
+                g_output_stream_write_all (os, buf.pData_, buf.size_, nullptr, nullptr, &error);
+                if (error != nullptr) {
+                    g_warning ("Failed to write: %s", error->message);
+                    g_clear_error (&_error);
+                    _error = error;
+                    break;
+                }
+            } else {
+                g_debug ("No more data");
+                break;
+            }
+        }
+    }
+    g_output_stream_flush (g_io_stream_get_output_stream (_is), nullptr, nullptr);
+}
+
+
+
+} // namespace Internal
+} // namespace GExiv2
diff --git a/gexiv2/gexiv2-metadata-gio.h b/gexiv2/gexiv2-metadata-gio.h
new file mode 100644
index 0000000..fe71428
--- /dev/null
+++ b/gexiv2/gexiv2-metadata-gio.h
@@ -0,0 +1,165 @@
+#ifndef GEXIV2_METADATA_GIO
+#define GEXIV2_METADATA_GIO
+
+#include <gio/gio.h>
+#include <exiv2/exiv2.hpp>
+
+namespace GExiv2 {
+namespace Internal {
+class GioIo : public Exiv2::BasicIo {
+public:
+    GioIo (GInputStream *is);
+    ~GioIo();
+
+#if defined(_MSC_VER)
+    using seek_offset_t = int64_t;
+#else
+    using seek_offset_t = long;
+#endif
+
+#if EXIV2_TEST_VERSION(0,27,99)
+    using ptr_type = Exiv2::BasicIo::UniquePtr;
+#else
+    using ptr_type = Exiv2::BasicIo::AutoPtr;
+#endif
+
+    int open() override;
+    int close() override { return 0; }
+
+    // Writing is not supported
+    long write(const Exiv2::byte *data, long wcount) override { return 0; }
+    long write(BasicIo &src) override { return 0; }
+    int putb(Exiv2::byte data) override { return EOF; }
+
+    Exiv2::DataBuf read(long rcount) override;
+    long read(Exiv2::byte *buf, long rcount) override ;
+    int getb() override;
+
+    // Does not seem necessary for Read-only support
+    void transfer(Exiv2::BasicIo &src) override { }
+
+    int seek(seek_offset_t offset, Exiv2::BasicIo::Position position) override;
+
+
+    // MMapping not supported
+    Exiv2::byte *mmap(bool writable) override { return nullptr; }
+    int munmap() override { return 0; }
+
+    long tell() const override;
+    // Cannot tell the size from a random stream - also it does not
+    // seem to be necessary for what we are doing
+    size_t size() const override { return -1; }
+
+    bool isopen() const override { return true; }
+
+    int error() const override { return _error == nullptr ? 0 : 1; }
+
+    bool eof() const override { return _eof; }
+
+    std::string path() const { return "GIO Wrapper"; }
+
+#if EXIV2_TEST_VERSION(0,27,99)
+    Exiv2::BasicIo::UniquePtr temporary() const {
+        return Exiv2::BasicIo::UniquePtr(nullptr);
+    }
+#else
+    Exiv2::BasicIo::AutoPtr temporary() const {
+        return Exiv2::BasicIo::AutoPtr(nullptr);
+    }
+#endif
+
+
+private:
+    GInputStream *_is;
+    GSeekable *_seekable;
+    GError *_error;
+    bool _eof;
+}; // class GioIo
+
+class GioRwIo : public Exiv2::BasicIo {
+public:
+    GioRwIo (GIOStream *ios);
+    ~GioRwIo();
+
+#if defined(_MSC_VER)
+    using seek_offset_t = int64_t;
+#else
+    using seek_offset_t = long;
+#endif
+
+#if EXIV2_TEST_VERSION(0,27,99)
+    using ptr_type = Exiv2::BasicIo::UniquePtr;
+#else
+    using ptr_type = Exiv2::BasicIo::AutoPtr;
+#endif
+
+    int open() override;
+    int close() override;
+
+    long write(const Exiv2::byte *data, long wcount) override;
+    long write(BasicIo &src) override;
+    int putb(Exiv2::byte data) override;
+
+    Exiv2::DataBuf read(long rcount) override;
+    long read(Exiv2::byte *buf, long rcount) override ;
+    int getb() override;
+
+    // Does not seem necessary for Read-only support
+    void transfer(Exiv2::BasicIo &src) override;
+
+    int seek(seek_offset_t offset, Exiv2::BasicIo::Position position) override;
+
+
+    // MMapping not supported
+    Exiv2::byte *mmap(bool writable) override {
+        g_debug(__PRETTY_FUNCTION__);
+        return nullptr; }
+    int munmap() override { return 0; }
+
+    long tell() const override;
+    // Cannot tell the size from a random stream - also it does not
+    // seem to be necessary for what we are doing
+    size_t size() const override {
+        g_debug(__PRETTY_FUNCTION__);
+        return -1; }
+
+    bool isopen() const override {
+        g_debug(__PRETTY_FUNCTION__);
+        return true; }
+
+    int error() const override {
+        g_debug(__PRETTY_FUNCTION__);
+        return _error == nullptr ? 0 : 1; }
+
+    bool eof() const override {
+        g_debug(__PRETTY_FUNCTION__);
+        return _eof; }
+
+    std::string path() const {
+        g_debug(__PRETTY_FUNCTION__);
+        return "GIO Wrapper"; }
+
+#if EXIV2_TEST_VERSION(0,27,99)
+    Exiv2::BasicIo::UniquePtr temporary() const {
+        g_debug(__PRETTY_FUNCTION__);
+        return Exiv2::BasicIo::UniquePtr(nullptr);
+    }
+#else
+    Exiv2::BasicIo::AutoPtr temporary() const {
+        g_debug(__PRETTY_FUNCTION__);
+        return Exiv2::BasicIo::AutoPtr(nullptr);
+    }
+#endif
+
+
+private:
+    GIOStream *_is;
+    GSeekable *_seekable;
+    GError *_error;
+    bool _eof;
+}; // class GioIo
+
+} // namespace Internal
+} // namespace GExiv2
+
+#endif // GEXIV2_METADATA_GIO
diff --git a/gexiv2/gexiv2-metadata.cpp b/gexiv2/gexiv2-metadata.cpp
index c520ede..60bd870 100644
--- a/gexiv2/gexiv2-metadata.cpp
+++ b/gexiv2/gexiv2-metadata.cpp
@@ -10,6 +10,7 @@
 
 #include "gexiv2-metadata.h"
 #include "gexiv2-metadata-private.h"
+#include "gexiv2-metadata-gio.h"
 #include "gexiv2-stream-io.h"
 #include "gexiv2-managed-stream.h"
 #include "gexiv2-preview-properties.h"
@@ -31,195 +32,6 @@ using image_ptr = Exiv2::Image::UniquePtr;
 using image_ptr = Exiv2::Image::AutoPtr;
 #endif
 
-namespace {
-class GioIo : public Exiv2::BasicIo {
-public:
-    GioIo (GInputStream *is)
-        : BasicIo ()
-        , _is (G_INPUT_STREAM (g_object_ref (is)))
-        , _seekable(G_IS_SEEKABLE(_is) ? G_SEEKABLE (_is) : NULL)
-        , _error{nullptr}
-        , _eof{false}
-        {}
-
-    ~GioIo() { g_clear_object (&_is); g_clear_error (&_error); _seekable = NULL;}
-#if defined(_MSC_VER)
-    typedef int64_t seek_offset_t;
-#else
-    typedef long seek_offset_t;
-#endif
-
-#if EXIV2_TEST_VERSION(0,27,99)
-    using ptr_type = Exiv2::BasicIo::UniquePtr;
-#else
-    using ptr_type = Exiv2::BasicIo::AutoPtr;
-#endif
-
-    int open() {
-        return 0;
-    }
-
-    int close() {
-        return 0;
-    }
-
-    // Writing is not supported
-    long write(const Exiv2::byte *data, long wcount) { return 0; }
-    long write(BasicIo &src) { return 0; }
-    int putb(Exiv2::byte data) { return EOF; }
-
-
-    Exiv2::DataBuf read(long rcount) {
-        Exiv2::DataBuf b{rcount};
-
-        long bytes_read = this->read(b.pData_, rcount);
-        if (bytes_read > 0 && bytes_read != rcount) {
-            b.reset({b.pData_, bytes_read});
-        }
-
-        return b;
-    }
-
-    long read(Exiv2::byte *buf, long rcount) {
-        GError *error = NULL;
-        gssize result = 0;
-
-        result = g_input_stream_read(_is, reinterpret_cast<void *>(buf), rcount, NULL, &error);
-        if (error != NULL) {
-            g_critical ("Error reading from stream: %d %s", error->code, error->message);
-            g_clear_error (&_error);
-            _error = error;
-
-#if EXIV2_TEST_VERSION(0,27,0)
-            throw Exiv2::Error(Exiv2::ErrorCode::kerFailedToReadImageData);
-#else
-            throw Exiv2::Error(2);
-#endif
-            return 0;
-        }
-
-        if (result == 0) {
-            _eof = true;
-        } else {
-            _eof = false;
-        }
-
-        return result;
-    }
-
-    int getb() {
-        Exiv2::byte b;
-        return this->read (&b, 1) == 1 ? b : EOF;
-    }
-
-    void transfer(Exiv2::BasicIo &src) {
-        // Does not seem necessary for Read-only support
-    }
-
-    int seek(seek_offset_t offset, Exiv2::BasicIo::Position position) {
-        if (_seekable != NULL && g_seekable_can_seek (_seekable)) {
-            GSeekType t = G_SEEK_SET;
-            switch (position) {
-                case Exiv2::BasicIo::cur:
-                    t = G_SEEK_CUR;
-                    break;
-                case Exiv2::BasicIo::beg:
-                    t = G_SEEK_SET;
-                    break;
-                case Exiv2::BasicIo::end:
-                    t = G_SEEK_END;
-                    break;
-                default:
-                    g_assert_not_reached ();
-                    break;
-            }
-
-            GError *error = NULL;
-            g_seekable_seek (_seekable, offset, t, NULL, &error);
-            if (error != NULL) {
-                g_clear_error(&_error);
-                g_critical ("Failed to seek: %s", error->message);
-                _error = error;
-
-                return -1;
-            }
-
-            return 0;
-        } else {
-            // Can only seek forward here...
-            if (position != Exiv2::BasicIo::cur) {
-                return -1;
-            }
-
-            GError *error = NULL;
-            g_input_stream_skip (_is, offset, NULL, &error);
-            if (error != NULL) {
-                g_clear_error(&_error);
-                _error = error;
-                g_critical("Failed to seek forward: %s", error->message);
-
-                return -1;
-            }
-
-            return 0;
-        }
-    }
-
-    Exiv2::byte *mmap(bool writable) {
-        return NULL;
-    }
-
-    int munmap() {
-        return 0;
-    }
-
-    long tell() const {
-        if (_seekable != NULL && g_seekable_can_seek (_seekable)) {
-            return static_cast<long>(g_seekable_tell (_seekable));
-        } else {
-            return -1;
-        }
-    }
-
-    size_t size() const {
-        return -1;
-    }
-
-    bool isopen() const {
-        return true;
-    }
-
-    int error() const {
-        return _error == nullptr ? 0 : 1;
-    }
-
-    bool eof() const {
-        return _eof;
-    }
-
-    std::string path() const {
-        return "GIO Wrapper";
-    }
-
-#if EXIV2_TEST_VERSION(0,27,99)
-    Exiv2::BasicIo::UniquePtr temporary() const {
-        return Exiv2::BasicIo::UniquePtr(nullptr);
-    }
-#else
-    Exiv2::BasicIo::AutoPtr temporary() const {
-        return Exiv2::BasicIo::AutoPtr(nullptr);
-    }
-#endif
-
-
-private:
-    GInputStream *_is;
-    GSeekable *_seekable;
-    GError *_error;
-    bool _eof;
-}; // class GioIo
-} // Anonymous namespace
-
 G_BEGIN_DECLS
 
 G_DEFINE_TYPE_WITH_CODE (GExiv2Metadata, gexiv2_metadata, G_TYPE_OBJECT, G_ADD_PRIVATE (GExiv2Metadata));
@@ -398,7 +210,7 @@ gboolean gexiv2_metadata_from_stream(GExiv2Metadata *self, GInputStream *stream,
     g_return_val_if_fail (GEXIV2_IS_METADATA (self), FALSE);
 
     try {
-        GioIo::ptr_type gio_ptr{new GioIo (stream)};
+        GExiv2::Internal::GioIo::ptr_type gio_ptr{new GExiv2::Internal::GioIo (stream)};
 #if EXIV2_TEST_VERSION(0,27,99)
         self->priv->image = Exiv2::ImageFactory::open (std::move(gio_ptr));
 #else
@@ -560,6 +372,24 @@ gboolean gexiv2_metadata_save_stream (GExiv2Metadata *self, ManagedStreamCallbac
     return FALSE;
 }
 
+gboolean gexiv2_metadata_to_stream(GExiv2Metadata *self, GIOStream *stream, GError **error) {
+    g_return_val_if_fail (GEXIV2_IS_METADATA (self), FALSE);
+
+    try {
+        GExiv2::Internal::GioRwIo::ptr_type stream_ptr{new GExiv2::Internal::GioRwIo{stream}};
+
+#if EXIV2_TEST_VERSION(0,27,99)
+        return gexiv2_metadata_save_internal (self, Exiv2::ImageFactory::open (std::move(stream_ptr)), 
error);
+#else
+        return gexiv2_metadata_save_internal (self, Exiv2::ImageFactory::open (stream_ptr), error);
+#endif
+    } catch (Exiv2::Error &e) {
+        g_set_error_literal (error, g_quark_from_string ("GExiv2"), e.code (), e.what ());
+    }
+
+    return FALSE;
+}
+
 gboolean gexiv2_metadata_has_tag(GExiv2Metadata *self, const gchar* tag) {
     g_return_val_if_fail(GEXIV2_IS_METADATA(self), FALSE);
     g_return_val_if_fail(tag != NULL, FALSE);
diff --git a/gexiv2/gexiv2-metadata.h b/gexiv2/gexiv2-metadata.h
index 54e0e91..60a6933 100644
--- a/gexiv2/gexiv2-metadata.h
+++ b/gexiv2/gexiv2-metadata.h
@@ -317,6 +317,18 @@ gboolean           gexiv2_metadata_save_file                       (GExiv2Metadata 
*self, const gchar *path,
  */
 gboolean               gexiv2_metadata_save_stream                     (GExiv2Metadata *self, 
ManagedStreamCallbacks* cb, GError **error);
 
+/**
+ * gexiv2_metadata_to_stream:
+ * @self: An instance of #GExiv2Metadata
+ * @ios: A #GIOStream for writing the meta-data to
+ * @error: (allow-none): A return location for a #GError or %NULL
+ *
+ * Saves the metadata to the file represented by #ios.
+ *
+ * Returns: Boolean success indicator.
+ */
+gboolean               gexiv2_metadata_to_stream                       (GExiv2Metadata *self, GIOStream 
*ios, GError **error);
+
 /**
  * gexiv2_metadata_has_tag:
  * @self: An instance of #GExiv2Metadata
diff --git a/gexiv2/meson.build b/gexiv2/meson.build
index 196b298..0af869f 100644
--- a/gexiv2/meson.build
+++ b/gexiv2/meson.build
@@ -47,6 +47,8 @@ gexiv2 = library('gexiv2',
                   'gexiv2-log-private.h',
                   'gexiv2-metadata-private.h',
                   'gexiv2-stream-io.h',
+                  'gexiv2-metadata-gio.h',
+                  'gexiv2-metadata-gio.cpp',
                   'gexiv2-preview-properties-private.h',
                   'gexiv2-preview-image-private.h'] +
                  gexiv2_headers +
diff --git a/tools/gexiv2-copy.vala b/tools/gexiv2-copy.vala
new file mode 100644
index 0000000..eea9e78
--- /dev/null
+++ b/tools/gexiv2-copy.vala
@@ -0,0 +1,43 @@
+/*
+ * gexiv2-dump.vala
+ *
+ * Author(s)
+ *  Jim Nelson <jim yorba org>
+ *
+ * This is free software. See COPYING for details.
+ */
+
+int main(string[] args) {
+    if (args.length != 3 || ("--help" in args) || ("-h" in args)) {
+        usage();
+
+        return 1;
+    }
+
+    var in_file = File.new_for_commandline_arg (args[1]);
+    var out_file = File.new_for_commandline_arg (args[2]);
+
+    var metadata = new GExiv2.Metadata();
+    try {
+        metadata.from_stream (in_file.read());
+    } catch (Error err) {
+        print("Failed to open input file %s: %s\n", args[1], err.message);
+        return 2;
+    }
+
+    print ("Opening destination file for reading");
+    try {
+        var input = out_file.read();
+        var output = out_file.replace(null, false, FileCreateFlags.NONE, null);
+        metadata.to_stream (new SimpleIOStream (input, output));
+    } catch (Error err) {
+        print("Failed write to output file %s: %s\n", args[2], err.message);
+        return 3;
+    }
+
+    return 0;
+}
+
+void usage() {
+    stdout.printf("usage: gexiv2-copy IN-FILE OUT-FILE\n\n");
+}
diff --git a/tools/meson.build b/tools/meson.build
index 4ab697d..f665630 100644
--- a/tools/meson.build
+++ b/tools/meson.build
@@ -4,4 +4,10 @@ if vapi_available
              include_directories : include_directories('..'),
              dependencies : [gobject, vapi, gio],
              link_with : gexiv2)
+
+    executable('gexiv2-copy',
+               'gexiv2-copy.vala',
+               include_directories : include_directories('..'),
+               dependencies : [gobject, vapi, gio],
+               link_with : gexiv2)
 endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]