[libgit2-glib] Added writing blob using output stream



commit b528200f9cc74a0bf4d2a1f00d58626f620b2f2e
Author: Jesse van den Kieboom <jessevdk gmail com>
Date:   Sat Jun 29 17:19:28 2013 +0200

    Added writing blob using output stream

 libgit2-glib/Makefile.am               |    2 +
 libgit2-glib/ggit-blob-output-stream.c |  372 ++++++++++++++++++++++++++++++++
 libgit2-glib/ggit-blob-output-stream.h |   73 +++++++
 libgit2-glib/ggit-repository.c         |   19 ++
 libgit2-glib/ggit-repository.h         |    5 +
 libgit2-glib/ggit-types.h              |    7 +
 libgit2-glib/ggit.h                    |    1 +
 tests/repository.c                     |   61 ++++++
 8 files changed, 540 insertions(+), 0 deletions(-)
---
diff --git a/libgit2-glib/Makefile.am b/libgit2-glib/Makefile.am
index 1c6a5e4..9cd2224 100644
--- a/libgit2-glib/Makefile.am
+++ b/libgit2-glib/Makefile.am
@@ -19,6 +19,7 @@ libgit2_glib_1_0_la_LIBADD = $(LIBGIT2_GLIB_LIBS)
 
 INST_H_FILES =                         \
        ggit-blob.h                     \
+       ggit-blob-output-stream.h       \
        ggit-branch.h                   \
        ggit-clone-options.h            \
        ggit-commit.h                   \
@@ -68,6 +69,7 @@ NOINST_H_FILES =                      \
 
 C_FILES =                              \
        ggit-blob.c                     \
+       ggit-blob-output-stream.c       \
        ggit-branch.c                   \
        ggit-clone-options.c            \
        ggit-commit.c                   \
diff --git a/libgit2-glib/ggit-blob-output-stream.c b/libgit2-glib/ggit-blob-output-stream.c
new file mode 100644
index 0000000..3d2077e
--- /dev/null
+++ b/libgit2-glib/ggit-blob-output-stream.c
@@ -0,0 +1,372 @@
+/*
+ * ggit-blob-output-stream.c
+ * This file is part of libgit2-glib
+ *
+ * Copyright (C) 2013 - Jesse van den Kieboom
+ *
+ * libgit2-glib is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libgit2-glib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libgit2-glib. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ggit-blob-output-stream.h"
+#include "ggit-repository.h"
+#include "ggit-oid.h"
+#include "ggit-error.h"
+
+#include <git2.h>
+#include <string.h>
+
+#define GGIT_BLOB_OUTPUT_STREAM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), 
GGIT_TYPE_BLOB_OUTPUT_STREAM, GgitBlobOutputStreamPrivate))
+
+struct _GgitBlobOutputStreamPrivate
+{
+       GgitRepository *repository;
+       GThread *thread;
+
+       GMutex mutex;
+       GCond cond;
+
+       GMutex reply_mutex;
+       GCond reply_cond;
+
+       gint written;
+
+       const gchar *writebuf;
+       gsize bufsize;
+
+       gint ret;
+       GgitOId *oid;
+
+       guint closing : 1;
+       guint cancelled : 1;
+};
+
+G_DEFINE_TYPE (GgitBlobOutputStream, ggit_blob_output_stream, G_TYPE_OUTPUT_STREAM)
+
+enum
+{
+       PROP_0,
+       PROP_REPOSITORY
+};
+
+static gboolean
+ggit_blob_output_stream_close (GOutputStream  *object,
+                               GCancellable   *cancellable,
+                               GError        **error)
+{
+       GgitBlobOutputStream *stream = GGIT_BLOB_OUTPUT_STREAM (object);
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+       {
+               return FALSE;
+       }
+
+       g_mutex_lock (&stream->priv->mutex);
+       stream->priv->closing = TRUE;
+
+       g_cond_signal (&stream->priv->cond);
+       g_mutex_lock (&stream->priv->reply_mutex);
+       g_mutex_unlock (&stream->priv->mutex);
+       g_cond_wait (&stream->priv->reply_cond, &stream->priv->reply_mutex);
+       g_mutex_unlock (&stream->priv->reply_mutex);
+
+       g_thread_join (stream->priv->thread);
+       return TRUE;
+}
+
+static gboolean
+ggit_blob_output_stream_flush (GOutputStream  *stream,
+                               GCancellable   *cancellable,
+                               GError        **error)
+{
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+       {
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gssize
+ggit_blob_output_stream_write (GOutputStream  *object,
+                               const void     *buffer,
+                               gsize           count,
+                               GCancellable   *cancellable,
+                               GError        **error)
+{
+       GgitBlobOutputStream *stream = GGIT_BLOB_OUTPUT_STREAM (object);
+
+       if (stream->priv->closing)
+       {
+               return 0;
+       }
+
+       while (TRUE)
+       {
+               g_mutex_lock (&stream->priv->mutex);
+
+               if (g_cancellable_is_cancelled (cancellable))
+               {
+                       stream->priv->cancelled = TRUE;
+               }
+
+               stream->priv->writebuf = buffer;
+               stream->priv->bufsize = count;
+               stream->priv->written = 0;
+
+               g_cond_signal (&stream->priv->cond);
+               g_mutex_lock (&stream->priv->reply_mutex);
+               g_mutex_unlock (&stream->priv->mutex);
+               g_cond_wait (&stream->priv->reply_cond, &stream->priv->reply_mutex);
+
+               if (stream->priv->written == count || stream->priv->cancelled)
+               {
+                       g_mutex_unlock (&stream->priv->reply_mutex);
+                       break;
+               }
+
+               g_mutex_unlock (&stream->priv->reply_mutex);
+       }
+
+       if (stream->priv->cancelled)
+       {
+               g_cancellable_set_error_if_cancelled (cancellable, error);
+               return -1;
+       }
+
+       return stream->priv->written;
+}
+
+static void
+ggit_blob_output_stream_finalize (GObject *object)
+{
+       GgitBlobOutputStream *stream;
+
+       stream = GGIT_BLOB_OUTPUT_STREAM (object);
+
+       G_OBJECT_CLASS (ggit_blob_output_stream_parent_class)->finalize (object);
+
+       g_mutex_clear (&stream->priv->mutex);
+       g_cond_clear (&stream->priv->cond);
+
+       g_mutex_clear (&stream->priv->reply_mutex);
+       g_cond_clear (&stream->priv->reply_cond);
+
+       if (stream->priv->oid)
+       {
+               ggit_oid_free (stream->priv->oid);
+       }
+}
+
+static void
+ggit_blob_output_stream_set_property (GObject      *object,
+                                      guint         prop_id,
+                                      const GValue *value,
+                                      GParamSpec   *pspec)
+{
+       GgitBlobOutputStream *stream = GGIT_BLOB_OUTPUT_STREAM (object);
+
+       switch (prop_id)
+       {
+       case PROP_REPOSITORY:
+               g_clear_object (&stream->priv->repository);
+               stream->priv->repository = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+ggit_blob_output_stream_get_property (GObject    *object,
+                                      guint       prop_id,
+                                      GValue     *value,
+                                      GParamSpec *pspec)
+{
+       switch (prop_id)
+       {
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+ggit_blob_output_stream_class_init (GgitBlobOutputStreamClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
+
+       object_class->finalize = ggit_blob_output_stream_finalize;
+
+       object_class->get_property = ggit_blob_output_stream_get_property;
+       object_class->set_property = ggit_blob_output_stream_set_property;
+
+       stream_class->write_fn = ggit_blob_output_stream_write;
+       stream_class->close_fn = ggit_blob_output_stream_close;
+       stream_class->flush = ggit_blob_output_stream_flush;
+
+       g_object_class_install_property (object_class,
+                                        PROP_REPOSITORY,
+                                        g_param_spec_object ("repository",
+                                                             "Repository",
+                                                             "Repository",
+                                                             GGIT_TYPE_REPOSITORY,
+                                                             G_PARAM_WRITABLE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+
+       g_type_class_add_private (object_class, sizeof (GgitBlobOutputStreamPrivate));
+}
+
+static int
+blob_chunk_cb (char   *content,
+               size_t  maxlen,
+               void   *payload)
+{
+       GgitBlobOutputStream *stream = payload;
+       gint written = 0;
+
+       // block until we got something to write
+       while (written == 0)
+       {
+               g_cond_wait (&stream->priv->cond, &stream->priv->mutex);
+
+               if (stream->priv->closing)
+               {
+                       return 0;
+               }
+
+               if (stream->priv->cancelled)
+               {
+                       return -1;
+               }
+
+               if (stream->priv->bufsize > maxlen)
+               {
+                       stream->priv->written = maxlen;
+               }
+               else
+               {
+                       stream->priv->written = stream->priv->bufsize;
+               }
+
+               if (stream->priv->written > 0)
+               {
+                       memcpy (content, stream->priv->writebuf, stream->priv->written);
+                       written = stream->priv->written;
+               }
+
+               g_mutex_lock (&stream->priv->reply_mutex);
+               g_cond_signal (&stream->priv->reply_cond);
+               g_mutex_unlock (&stream->priv->mutex);
+               g_mutex_unlock (&stream->priv->reply_mutex);
+       }
+
+       return written;
+}
+
+static gpointer
+chunk_blob_in_thread (gpointer data)
+{
+       GgitBlobOutputStream *stream = data;
+       git_oid oid;
+       gint ret;
+
+       ret = git_blob_create_fromchunks (&oid,
+                                         _ggit_native_get (stream->priv->repository),
+                                         NULL,
+                                         blob_chunk_cb,
+                                         data);
+
+       stream->priv->ret = ret;
+       stream->priv->closing = TRUE;
+
+       if (ret == GIT_OK)
+       {
+               stream->priv->oid = _ggit_oid_wrap (&oid);
+       }
+       else
+       {
+               stream->priv->cancelled = TRUE;
+       }
+
+       g_mutex_lock (&stream->priv->reply_mutex);
+       g_cond_signal (&stream->priv->reply_cond);
+
+       g_mutex_unlock (&stream->priv->mutex);
+       g_mutex_unlock (&stream->priv->reply_mutex);
+
+       return NULL;
+}
+
+static void
+ggit_blob_output_stream_init (GgitBlobOutputStream *self)
+{
+       self->priv = GGIT_BLOB_OUTPUT_STREAM_GET_PRIVATE (self);
+
+       g_mutex_init (&self->priv->mutex);
+       g_cond_init (&self->priv->cond);
+
+       g_mutex_init (&self->priv->reply_mutex);
+       g_cond_init (&self->priv->reply_cond);
+
+       g_mutex_lock (&self->priv->mutex);
+
+       self->priv->thread = g_thread_new ("ggit-blob-output-stream",
+                                          chunk_blob_in_thread,
+                                          self);
+}
+
+GgitBlobOutputStream *
+_ggit_blob_output_stream_new (GgitRepository  *repository)
+{
+       GgitBlobOutputStream *s;
+
+       s = g_object_new (GGIT_TYPE_BLOB_OUTPUT_STREAM,
+                         "repository", repository,
+                         NULL);
+
+       return s;
+}
+
+/**
+ * ggit_blob_output_stream_get_id:
+ * @stream: a #GgitBlobOutputStream.
+ * @error: a #GError for error reporting, or %NULL.
+ *
+ * Get the id of the written blob. The blob id is only available after the
+ * stream has been properly closed. If an error occurred while writing the blob,
+ * the %NULL is returned and @error is set accordingly.
+ *
+ * Returns: (transfer full): a #GgitOId or %NULL.
+ *
+ **/
+GgitOId *
+ggit_blob_output_stream_get_id (GgitBlobOutputStream  *stream,
+                                GError               **error)
+{
+       g_return_val_if_fail (GGIT_IS_BLOB_OUTPUT_STREAM (stream), NULL);
+
+       if (stream->priv->ret != GIT_OK)
+       {
+               _ggit_error_set (error, stream->priv->ret);
+               return NULL;
+       }
+
+       return ggit_oid_copy (stream->priv->oid);
+}
+
+/* ex:set ts=8 noet: */
diff --git a/libgit2-glib/ggit-blob-output-stream.h b/libgit2-glib/ggit-blob-output-stream.h
new file mode 100644
index 0000000..76d85f7
--- /dev/null
+++ b/libgit2-glib/ggit-blob-output-stream.h
@@ -0,0 +1,73 @@
+/*
+ * ggit-blob-output-stream.h
+ * This file is part of libgit2-glib
+ *
+ * Copyright (C) 2013 - Jesse van den Kieboom
+ *
+ * libgit2-glib is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * libgit2-glib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libgit2-glib. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef __GGIT_BLOB_OUTPUT_STREAM_H__
+#define __GGIT_BLOB_OUTPUT_STREAM_H__
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+#include "ggit-types.h"
+
+G_BEGIN_DECLS
+
+#define GGIT_TYPE_BLOB_OUTPUT_STREAM           (ggit_blob_output_stream_get_type ())
+#define GGIT_BLOB_OUTPUT_STREAM(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GGIT_TYPE_BLOB_OUTPUT_STREAM, GgitBlobOutputStream))
+#define GGIT_BLOB_OUTPUT_STREAM_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), 
GGIT_TYPE_BLOB_OUTPUT_STREAM, GgitBlobOutputStreamClass))
+#define GGIT_IS_BLOB_OUTPUT_STREAM(obj)                (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GGIT_TYPE_BLOB_OUTPUT_STREAM))
+#define GGIT_IS_BLOB_OUTPUT_STREAM_CLASS(klass)        (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GGIT_TYPE_BLOB_OUTPUT_STREAM))
+#define GGIT_BLOB_OUTPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GGIT_TYPE_BLOB_OUTPUT_STREAM, GgitBlobOutputStreamClass))
+
+typedef struct _GgitBlobOutputStreamClass      GgitBlobOutputStreamClass;
+typedef struct _GgitBlobOutputStreamPrivate    GgitBlobOutputStreamPrivate;
+
+struct _GgitBlobOutputStream
+{
+       /*< private >*/
+       GOutputStream parent;
+
+       GgitBlobOutputStreamPrivate *priv;
+};
+
+/**
+ * GgitBlobOutputStreamClass:
+ * @parent_class: The parent class.
+ *
+ * The class structure for #GgitBlobOutputStreamClass.
+ */
+struct _GgitBlobOutputStreamClass
+{
+       /*< private >*/
+       GOutputStreamClass parent_class;
+};
+
+GType             ggit_blob_output_stream_get_type         (void) G_GNUC_CONST;
+
+GgitBlobOutputStream *_ggit_blob_output_stream_new         (GgitRepository *repository);
+
+GgitOId *ggit_blob_output_stream_get_id (GgitBlobOutputStream  *stream,
+                                         GError               **error);
+
+G_END_DECLS
+
+#endif /* __GGIT_BLOB_OUTPUT_STREAM_H__ */
+
+/* ex:set ts=8 noet: */
diff --git a/libgit2-glib/ggit-repository.c b/libgit2-glib/ggit-repository.c
index 45d6627..bc29255 100644
--- a/libgit2-glib/ggit-repository.c
+++ b/libgit2-glib/ggit-repository.c
@@ -1897,6 +1897,25 @@ ggit_repository_get_ahead_behind (GgitRepository  *repository,
 }
 
 /**
+ * ggit_repository_create_blob:
+ * @repository: a #GgitRepository.
+ *
+ * Create a new blob and return a #GOutputStream to write contents to the blob.
+ * This is an efficient way to create new blobs without copying data. The
+ * blob id can be obtained from the blob output stream using
+ * #ggit_blob_output_stream_get_id, after you close the stream.
+ *
+ * Returns: (transfer full): a #GgitBlobOutputStream.
+ *
+ **/
+GgitBlobOutputStream *
+ggit_repository_create_blob (GgitRepository *repository)
+{
+       g_return_val_if_fail (GGIT_IS_REPOSITORY (repository), NULL);
+       return _ggit_blob_output_stream_new (repository);
+}
+
+/**
  * ggit_repository_create_blob_from_buffer:
  * @repository: a #GgitRepository.
  * @buffer: (array length=size) (element-type guint8): the data.
diff --git a/libgit2-glib/ggit-repository.h b/libgit2-glib/ggit-repository.h
index b7ca515..8e5df44 100644
--- a/libgit2-glib/ggit-repository.h
+++ b/libgit2-glib/ggit-repository.h
@@ -32,6 +32,7 @@
 #include <libgit2-glib/ggit-object.h>
 #include <libgit2-glib/ggit-tree.h>
 #include <libgit2-glib/ggit-branch.h>
+#include <libgit2-glib/ggit-blob-output-stream.h>
 
 G_BEGIN_DECLS
 
@@ -104,6 +105,10 @@ GgitRef            *ggit_repository_create_symbolic_reference
                                                        const gchar           *target,
                                                        GError               **error);
 
+GgitBlobOutputStream *
+                    ggit_repository_create_blob       (GgitRepository        *repository);
+
+
 GgitOId            *ggit_repository_create_blob_from_buffer (
                                                        GgitRepository        *repository,
                                                        gconstpointer          buffer,
diff --git a/libgit2-glib/ggit-types.h b/libgit2-glib/ggit-types.h
index 4a85136..9fc98a3 100644
--- a/libgit2-glib/ggit-types.h
+++ b/libgit2-glib/ggit-types.h
@@ -33,6 +33,13 @@ G_BEGIN_DECLS
 typedef struct _GgitBlob GgitBlob;
 
 /**
+ * GgitBlobOutputStream:
+ *
+ * Represents a blob stream object.
+ */
+typedef struct _GgitBlobOutputStream GgitBlobOutputStream;
+
+/**
  * GgitBranch:
  *
  * Represents a branch object.
diff --git a/libgit2-glib/ggit.h b/libgit2-glib/ggit.h
index 8f6fd52..7bb16fd 100644
--- a/libgit2-glib/ggit.h
+++ b/libgit2-glib/ggit.h
@@ -22,6 +22,7 @@
 #define __GGIT_H__
 
 #include <libgit2-glib/ggit-blob.h>
+#include <libgit2-glib/ggit-blob-output-stream.h>
 #include <libgit2-glib/ggit-branch.h>
 #include <libgit2-glib/ggit-clone-options.h>
 #include <libgit2-glib/ggit-commit.h>
diff --git a/tests/repository.c b/tests/repository.c
index 630f0b0..9e01abc 100644
--- a/tests/repository.c
+++ b/tests/repository.c
@@ -200,6 +200,66 @@ test_repository_init_bare (const gchar *git_dir)
        do_test_init (git_dir, TRUE);
 }
 
+static void
+test_repository_blob_stream (const gchar *git_dir)
+{
+       GFile *f;
+       GgitRepository *repo;
+       GError *err = NULL;
+       GgitBlobOutputStream *stream;
+       GgitOId *oid;
+       const gchar *msg = "hello world\n";
+       gsize written;
+       gsize msglen;
+       GgitBlob *blob;
+       gsize rl;
+       const guchar *content;
+
+       f = g_file_new_for_path (git_dir);
+       repo = ggit_repository_init_repository (f, FALSE, &err);
+       g_object_unref (f);
+
+       g_assert_no_error (err);
+
+       stream = ggit_repository_create_blob (repo);
+
+       msglen = strlen (msg);
+
+       written = g_output_stream_write (G_OUTPUT_STREAM (stream),
+                                        msg,
+                                        msglen,
+                                        NULL,
+                                        &err);
+
+       g_assert_no_error (err);
+       g_assert_cmpint (written, ==, msglen);
+
+       g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, &err);
+
+       g_assert_no_error (err);
+       oid = ggit_blob_output_stream_get_id (stream, &err);
+
+       g_assert_no_error (err);
+       g_assert (oid);
+
+       blob = (GgitBlob *)ggit_repository_lookup (repo,
+                                                  oid,
+                                                  GGIT_TYPE_BLOB,
+                                                  &err);
+
+       g_assert_no_error (err);
+       g_assert (blob);
+
+       content = ggit_blob_get_raw_content (blob, &rl);
+       g_assert_cmpint (rl, ==, msglen);
+       g_assert_cmpint (memcmp (content, msg, msglen), ==, 0);
+
+       ggit_oid_free (oid);
+
+       g_object_unref (stream);
+       g_object_unref (repo);
+}
+
 int
 main (int    argc,
       char **argv)
@@ -215,6 +275,7 @@ main (int    argc,
 
        TEST ("init", init);
        TEST ("init-bare", init_bare);
+       TEST ("blob-stream", blob_stream);
 
        return g_test_run ();
 }


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