[gtksourceview/wip/loader-saver] GtkSourceFileSaver private class, just compilable



commit d2915196bc3685f0c7bd6e9af7ef6d5003816390
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Thu Dec 12 16:00:24 2013 +0100

    GtkSourceFileSaver private class, just compilable
    
    This is only the first step.
    
    First step: compilable.
    Next steps: refactor the code to fit the GtkSourceFile API.
    Final step: use it in GtkSourceFile.

 gtksourceview/Makefile.am                |    2 +
 gtksourceview/gtksourcefile.c            |   13 +
 gtksourceview/gtksourcefile.h            |   13 +
 gtksourceview/gtksourcefilesaver.c       | 1146 ++++++++++++++++++++++++++++++
 gtksourceview/gtksourcefilesaver.h       |  122 ++++
 gtksourceview/gtksourcetypes-private.h   |    1 +
 gtksourceview/gtksourceview-marshal.list |    1 +
 po/POTFILES.in                           |    1 +
 8 files changed, 1299 insertions(+), 0 deletions(-)
---
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index fd4e710..dd3631e 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -59,6 +59,7 @@ libgtksourceview_private_headers = \
        gtksourcecompletion-private.h           \
        gtksourcecontextengine.h                \
        gtksourceengine.h                       \
+       gtksourcefilesaver.h                    \
        gtksourcegutter-private.h               \
        gtksourcegutterrendererlines.h          \
        gtksourcegutterrenderermarks.h          \
@@ -80,6 +81,7 @@ libgtksourceview_private_c_files = \
        gtksourcecompletionmodel.c      \
        gtksourcecontextengine.c        \
        gtksourceengine.c               \
+       gtksourcefilesaver.c            \
        gtksourcegutterrendererlines.c  \
        gtksourcegutterrenderermarks.c  \
        gtksourcelanguage-parser-1.c    \
diff --git a/gtksourceview/gtksourcefile.c b/gtksourceview/gtksourcefile.c
index e52af91..13a619e 100644
--- a/gtksourceview/gtksourcefile.c
+++ b/gtksourceview/gtksourcefile.c
@@ -230,6 +230,19 @@ gtk_source_file_init (GtkSourceFile *self)
        self->priv = gtk_source_file_get_instance_private (self);
 }
 
+GQuark
+gtk_source_file_error_quark (void)
+{
+       static GQuark quark = 0;
+
+       if (G_UNLIKELY (quark == 0))
+       {
+               quark = g_quark_from_static_string ("gtk-source-file-error");
+       }
+
+       return quark;
+}
+
 GtkSourceFile *
 gtk_source_file_new (GFile           *location,
                     GtkSourceBuffer *buffer)
diff --git a/gtksourceview/gtksourcefile.h b/gtksourceview/gtksourcefile.h
index d77ec9e..08717e2 100644
--- a/gtksourceview/gtksourcefile.h
+++ b/gtksourceview/gtksourcefile.h
@@ -78,6 +78,17 @@ typedef enum
        GTK_SOURCE_FILE_SAVE_IGNORE_INVALID_CHARS       = 1 << 3
 } GtkSourceFileSaveFlags;
 
+#define GTK_SOURCE_FILE_ERROR gtk_source_file_error_quark ()
+
+enum
+{
+       GTK_SOURCE_FILE_ERROR_EXTERNALLY_MODIFIED,
+       GTK_SOURCE_FILE_ERROR_CANT_CREATE_BACKUP,
+       GTK_SOURCE_FILE_ERROR_TOO_BIG,
+       GTK_SOURCE_FILE_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+       GTK_SOURCE_FILE_ERROR_CONVERSION_FALLBACK
+};
+
 struct _GtkSourceFile
 {
        GObject parent;
@@ -92,6 +103,8 @@ struct _GtkSourceFileClass
 
 GType                   gtk_source_file_get_type               (void) G_GNUC_CONST;
 
+GQuark                  gtk_source_file_error_quark            (void);
+
 GtkSourceFile          *gtk_source_file_new                    (GFile                   *location,
                                                                 GtkSourceBuffer         *buffer);
 
diff --git a/gtksourceview/gtksourcefilesaver.c b/gtksourceview/gtksourcefilesaver.c
new file mode 100644
index 0000000..445fc9d
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.c
@@ -0,0 +1,1146 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefilesaver.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005-2007 - Paolo Borelli and Paolo Maggi
+ * Copyright (C) 2007 - Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2013 - Sébastien Wilmet
+ *
+ * GtkSourceView 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.
+ *
+ * GtkSourceView 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <glib/gi18n.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include "gtksourcefilesaver.h"
+#include "gtksourcebufferinputstream.h"
+#include "gtksourceencoding.h"
+#include "gtksourceview-marshal.h"
+#include "gtksourceview-typebuiltins.h"
+
+#if 0
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+#define WRITE_CHUNK_SIZE 8192
+
+#define REMOTE_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+                               G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+enum
+{
+       SAVING,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+enum
+{
+       PROP_0,
+       PROP_BUFFER,
+       PROP_LOCATION,
+       PROP_ENCODING,
+       PROP_NEWLINE_TYPE,
+       PROP_COMPRESSION_TYPE,
+       PROP_FLAGS,
+       PROP_ENSURE_TRAILING_NEWLINE
+};
+
+typedef struct
+{
+       GtkSourceFileSaver *saver;
+       gchar buffer[WRITE_CHUNK_SIZE];
+       GCancellable *cancellable;
+       gboolean tried_mount;
+       gssize written;
+       gssize read;
+       GError *error;
+} AsyncData;
+
+struct _GtkSourceFileSaverPrivate
+{
+       GFileInfo *info;
+       GtkTextBuffer *buffer;
+
+       GFile *location;
+       const GtkSourceEncoding *encoding;
+       GtkSourceNewlineType newline_type;
+       GtkSourceCompressionType compression_type;
+       GtkSourceFileSaveFlags flags;
+
+       GTimeVal old_mtime;
+
+       goffset size;
+       goffset bytes_written;
+
+       GCancellable *cancellable;
+       GOutputStream *stream;
+       GInputStream *input;
+
+       GError *error;
+
+       GtkSourceMountOperationFactory mount_operation_factory;
+       gpointer mount_operation_userdata;
+
+       guint used : 1;
+       guint ensure_trailing_newline : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFileSaver, gtk_source_file_saver, G_TYPE_OBJECT)
+
+/* prototype, because they call each other... isn't C lovely */
+static void read_file_chunk (AsyncData *async);
+static void write_file_chunk (AsyncData *async);
+
+static void check_modified_async (AsyncData *async);
+
+static void
+gtk_source_file_saver_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_assert (saver->priv->buffer == NULL);
+                       saver->priv->buffer = g_value_get_object (value);
+                       break;
+
+               case PROP_LOCATION:
+                       g_assert (saver->priv->location == NULL);
+                       saver->priv->location = g_value_dup_object (value);
+                       break;
+
+               case PROP_ENCODING:
+                       g_assert (saver->priv->encoding == NULL);
+                       saver->priv->encoding = g_value_get_boxed (value);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       saver->priv->newline_type = g_value_get_enum (value);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       saver->priv->compression_type = g_value_get_enum (value);
+                       break;
+
+               case PROP_FLAGS:
+                       saver->priv->flags = g_value_get_flags (value);
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       saver->priv->ensure_trailing_newline = g_value_get_boolean (value);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_saver_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       switch (prop_id)
+       {
+               case PROP_BUFFER:
+                       g_value_set_object (value, saver->priv->buffer);
+                       break;
+
+               case PROP_LOCATION:
+                       g_value_set_object (value, saver->priv->location);
+                       break;
+
+               case PROP_ENCODING:
+                       g_value_set_boxed (value, saver->priv->encoding);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       g_value_set_enum (value, saver->priv->newline_type);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       g_value_set_enum (value, saver->priv->compression_type);
+                       break;
+
+               case PROP_FLAGS:
+                       g_value_set_flags (value, saver->priv->flags);
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, saver->priv->ensure_trailing_newline);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_saver_dispose (GObject *object)
+{
+       GtkSourceFileSaverPrivate *priv = GTK_SOURCE_FILE_SAVER (object)->priv;
+
+       if (priv->cancellable != NULL)
+       {
+               g_cancellable_cancel (priv->cancellable);
+               g_clear_object (&priv->cancellable);
+       }
+
+       g_clear_error (&priv->error);
+
+       g_clear_object (&priv->stream);
+       g_clear_object (&priv->input);
+       g_clear_object (&priv->info);
+       g_clear_object (&priv->location);
+
+       G_OBJECT_CLASS (gtk_source_file_saver_parent_class)->dispose (object);
+}
+
+static AsyncData *
+async_data_new (GtkSourceFileSaver *saver)
+{
+       AsyncData *async;
+
+       async = g_slice_new0 (AsyncData);
+       async->saver = saver;
+       async->cancellable = g_object_ref (saver->priv->cancellable);
+
+       return async;
+}
+
+static void
+async_data_free (AsyncData *async)
+{
+       g_object_unref (async->cancellable);
+
+       if (async->error != NULL)
+       {
+               g_error_free (async->error);
+       }
+
+       g_slice_free (AsyncData, async);
+}
+
+static void
+gtk_source_file_saver_class_init (GtkSourceFileSaverClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_file_saver_dispose;
+       object_class->set_property = gtk_source_file_saver_set_property;
+       object_class->get_property = gtk_source_file_saver_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_BUFFER,
+                                        g_param_spec_object ("buffer",
+                                                             "Buffer",
+                                                             "The associated GtkTextBuffer",
+                                                             GTK_TYPE_TEXT_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_LOCATION,
+                                        g_param_spec_object ("location",
+                                                             "Location",
+                                                             "The output location",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ENCODING,
+                                        g_param_spec_boxed ("encoding",
+                                                            "Encoding",
+                                                            "The encoding of the saved file",
+                                                            GTK_SOURCE_TYPE_ENCODING,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT_ONLY |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_NEWLINE_TYPE,
+                                        g_param_spec_enum ("newline-type",
+                                                           "Newline type",
+                                                           "The type of line ending",
+                                                           GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                                                           GTK_SOURCE_NEWLINE_TYPE_LF,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT_ONLY |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_COMPRESSION_TYPE,
+                                        g_param_spec_enum ("compression-type",
+                                                           "Compression type",
+                                                           "The compression type",
+                                                           GTK_SOURCE_TYPE_COMPRESSION_TYPE,
+                                                           GTK_SOURCE_COMPRESSION_TYPE_NONE,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT_ONLY |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_FLAGS,
+                                        g_param_spec_flags ("flags",
+                                                            "Flags",
+                                                            "The flags for the saving operation",
+                                                            GTK_SOURCE_TYPE_FILE_SAVE_FLAGS,
+                                                            0,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT_ONLY |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ENSURE_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("ensure-trailing-newline",
+                                                              "Ensure Trailing Newline",
+                                                              "Ensure the buffer ends with a trailing 
newline",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE |
+                                                              G_PARAM_CONSTRUCT_ONLY |
+                                                              G_PARAM_STATIC_STRINGS));
+
+       signals[SAVING] =
+               g_signal_new ("saving",
+                             G_OBJECT_CLASS_TYPE (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (GtkSourceFileSaverClass, saving),
+                             NULL, NULL,
+                             _gtksourceview_marshal_VOID__BOOLEAN_POINTER,
+                             G_TYPE_NONE,
+                             2,
+                             G_TYPE_BOOLEAN,
+                             G_TYPE_POINTER);
+}
+
+static void
+gtk_source_file_saver_init (GtkSourceFileSaver *saver)
+{
+       saver->priv = gtk_source_file_saver_get_instance_private (saver);
+
+       saver->priv->cancellable = g_cancellable_new ();
+}
+
+GtkSourceFileSaver *
+gtk_source_file_saver_new (GtkTextBuffer            *buffer,
+                          GFile                    *location,
+                          const GtkSourceEncoding  *encoding,
+                          GtkSourceNewlineType      newline_type,
+                          GtkSourceCompressionType  compression_type,
+                          GtkSourceFileSaveFlags    flags,
+                          gboolean                  ensure_trailing_newline)
+{
+       g_return_val_if_fail (GTK_IS_TEXT_BUFFER (buffer), NULL);
+
+       if (encoding == NULL)
+       {
+               encoding = gtk_source_encoding_get_utf8 ();
+       }
+
+       return g_object_new (GTK_SOURCE_TYPE_FILE_SAVER,
+                            "buffer", buffer,
+                            "location", location,
+                            "encoding", encoding,
+                            "newline_type", newline_type,
+                            "compression_type", compression_type,
+                            "flags", flags,
+                            "ensure-trailing-newline", ensure_trailing_newline,
+                            NULL);
+}
+
+static void
+remote_save_completed_or_failed (GtkSourceFileSaver *saver,
+                                AsyncData          *async)
+{
+       gtk_source_file_saver_saving (saver, TRUE, saver->priv->error);
+
+       if (async != NULL)
+       {
+               async_data_free (async);
+       }
+}
+
+static void
+async_failed (AsyncData *async,
+             GError    *error)
+{
+       g_propagate_error (&async->saver->priv->error, error);
+       remote_save_completed_or_failed (async->saver, async);
+}
+
+/* BEGIN NOTE:
+ *
+ * This fixes an issue in GOutputStream that applies the atomic replace save
+ * strategy. The stream moves the written file to the original file when the
+ * stream is closed. However, there is no way currently to tell the stream that
+ * the save should be aborted (there could be a conversion error). The patch
+ * explicitly closes the output stream in all these cases with a GCancellable in
+ * the cancelled state, causing the output stream to close, but not move the
+ * file. This makes use of an implementation detail in the local  file stream
+ * and should be properly fixed by adding the appropriate API in GIO. Until
+ * then, at least we prevent data corruption for now.
+ *
+ * Relevant bug reports:
+ *
+ * Bug 615110 - write file ignore encoding errors (gedit)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=615110
+ *
+ * Bug 602412 - g_file_replace does not restore original file when there is
+ *              errors while writing (glib/gio)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=602412
+ */
+static void
+cancel_output_stream_ready_cb (GOutputStream *stream,
+                               GAsyncResult  *result,
+                               AsyncData     *async)
+{
+       GError *error;
+
+       g_output_stream_close_finish (stream, result, NULL);
+
+       /* check cancelled state manually */
+       if (g_cancellable_is_cancelled (async->cancellable) || async->error == NULL)
+       {
+               async_data_free (async);
+               return;
+       }
+
+       error = async->error;
+       async->error = NULL;
+
+       async_failed (async, error);
+}
+
+static void
+cancel_output_stream (AsyncData *async)
+{
+       GCancellable *cancellable;
+
+       DEBUG ({
+              g_print ("Cancel output stream\n");
+       });
+
+       cancellable = g_cancellable_new ();
+       g_cancellable_cancel (cancellable);
+
+       g_output_stream_close_async (async->saver->priv->stream,
+                                    G_PRIORITY_HIGH,
+                                    cancellable,
+                                    (GAsyncReadyCallback)cancel_output_stream_ready_cb,
+                                    async);
+
+       g_object_unref (cancellable);
+}
+
+static void
+cancel_output_stream_and_fail (AsyncData *async,
+                               GError    *error)
+{
+       DEBUG ({
+              g_print ("Cancel output stream and fail\n");
+       });
+
+       g_propagate_error (&async->error, error);
+       cancel_output_stream (async);
+}
+
+/*
+ * END NOTE
+ */
+
+static void
+remote_get_info_cb (GFile        *source,
+                   GAsyncResult *res,
+                   AsyncData    *async)
+{
+       GtkSourceFileSaver *saver;
+       GFileInfo *info;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* check cancelled state manually */
+       if (g_cancellable_is_cancelled (async->cancellable))
+       {
+               async_data_free (async);
+               return;
+       }
+
+       saver = async->saver;
+
+       DEBUG ({
+              g_print ("Finished query info on file\n");
+       });
+
+       info = g_file_query_info_finish (source, res, &error);
+
+       if (info != NULL)
+       {
+               if (saver->priv->info != NULL)
+               {
+                       g_object_unref (saver->priv->info);
+               }
+
+               saver->priv->info = info;
+       }
+       else
+       {
+               DEBUG ({
+                      g_print ("Query info failed: %s\n", error->message);
+               });
+
+               g_propagate_error (&saver->priv->error, error);
+       }
+
+       remote_save_completed_or_failed (saver, async);
+}
+
+static void
+close_async_ready_get_info_cb (GOutputStream *stream,
+                              GAsyncResult  *res,
+                              AsyncData     *async)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* check cancelled state manually */
+       if (g_cancellable_is_cancelled (async->cancellable))
+       {
+               async_data_free (async);
+               return;
+       }
+
+       DEBUG ({
+              g_print ("Finished closing stream\n");
+       });
+
+       if (!g_output_stream_close_finish (stream, res, &error))
+       {
+               DEBUG ({
+                      g_print ("Closing stream error: %s\n", error->message);
+               });
+
+               async_failed (async, error);
+               return;
+       }
+
+       /* get the file info: note we cannot use
+        * g_file_output_stream_query_info_async since it is not able to get the
+        * content type etc, beside it is not supported by gvfs.
+        * I'm not sure this is actually necessary, can't we just use
+        * g_content_type_guess (since we have the file name and the data)
+        */
+       DEBUG ({
+              g_print ("Query info on file\n");
+       });
+
+       g_file_query_info_async (async->saver->priv->location,
+                                REMOTE_QUERY_ATTRIBUTES,
+                                G_FILE_QUERY_INFO_NONE,
+                                G_PRIORITY_HIGH,
+                                async->cancellable,
+                                (GAsyncReadyCallback) remote_get_info_cb,
+                                async);
+}
+
+static void
+write_complete (AsyncData *async)
+{
+       GError *error = NULL;
+
+       /* first we close the input stream */
+       DEBUG ({
+              g_print ("Close input stream\n");
+       });
+
+       if (!g_input_stream_close (async->saver->priv->input,
+                                  async->cancellable, &error))
+       {
+               DEBUG ({
+                      g_print ("Closing input stream error: %s\n", error->message);
+               });
+
+               cancel_output_stream_and_fail (async, error);
+               return;
+       }
+
+       /* now we close the output stream */
+       DEBUG ({
+              g_print ("Close output stream\n");
+       });
+
+       g_output_stream_close_async (async->saver->priv->stream,
+                                    G_PRIORITY_HIGH,
+                                    async->cancellable,
+                                    (GAsyncReadyCallback)close_async_ready_get_info_cb,
+                                    async);
+}
+
+static void
+async_write_cb (GOutputStream *stream,
+               GAsyncResult  *res,
+               AsyncData     *async)
+{
+       GtkSourceFileSaver *saver;
+       gssize bytes_written;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* Check cancelled state manually */
+       if (g_cancellable_is_cancelled (async->cancellable))
+       {
+               cancel_output_stream (async);
+               return;
+       }
+
+       bytes_written = g_output_stream_write_finish (stream, res, &error);
+
+       DEBUG ({
+              g_print ("Written: %" G_GSSIZE_FORMAT "\n", bytes_written);
+       });
+
+       if (bytes_written == -1)
+       {
+               DEBUG ({
+                      g_print ("Write error: %s\n", error->message);
+               });
+
+               cancel_output_stream_and_fail (async, error);
+               return;
+       }
+
+       saver = async->saver;
+       async->written += bytes_written;
+
+       /* write again */
+       if (async->written != async->read)
+       {
+               write_file_chunk (async);
+               return;
+       }
+
+       /* note that this signal blocks the write... check if it isn't
+        * a performance problem
+        */
+       gtk_source_file_saver_saving (saver,
+                                     FALSE,
+                                     NULL);
+
+       read_file_chunk (async);
+}
+
+static void
+write_file_chunk (AsyncData *async)
+{
+       GtkSourceFileSaver *saver;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       saver = async->saver;
+
+       g_output_stream_write_async (G_OUTPUT_STREAM (saver->priv->stream),
+                                    async->buffer + async->written,
+                                    async->read - async->written,
+                                    G_PRIORITY_HIGH,
+                                    async->cancellable,
+                                    (GAsyncReadyCallback) async_write_cb,
+                                    async);
+}
+
+static void
+read_file_chunk (AsyncData *async)
+{
+       GtkSourceFileSaver *saver;
+       GtkSourceBufferInputStream *dstream;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       saver = async->saver;
+       async->written = 0;
+
+       /* we use sync methods on doc stream since it is in memory. Using async
+          would be racy and we can endup with invalidated iters */
+       async->read = g_input_stream_read (saver->priv->input,
+                                          async->buffer,
+                                          WRITE_CHUNK_SIZE,
+                                          async->cancellable,
+                                          &error);
+
+       if (error != NULL)
+       {
+               cancel_output_stream_and_fail (async, error);
+               return;
+       }
+
+       /* Check if we finished reading and writing */
+       if (async->read == 0)
+       {
+               write_complete (async);
+               return;
+       }
+
+       /* Get how many chars have been read */
+       dstream = GTK_SOURCE_BUFFER_INPUT_STREAM (saver->priv->input);
+       saver->priv->bytes_written = _gtk_source_buffer_input_stream_tell (dstream);
+
+       write_file_chunk (async);
+}
+
+static void
+async_replace_ready_callback (GFile        *source,
+                             GAsyncResult *res,
+                             AsyncData    *async)
+{
+       GtkSourceFileSaver *saver;
+       GCharsetConverter *converter;
+       GFileOutputStream *file_stream;
+       GOutputStream *base_stream;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* Check cancelled state manually */
+       if (g_cancellable_is_cancelled (async->cancellable))
+       {
+               async_data_free (async);
+               return;
+       }
+
+       saver = async->saver;
+       file_stream = g_file_replace_finish (source, res, &error);
+
+       /* handle any error that might occur */
+       if (!file_stream)
+       {
+               DEBUG ({
+                      g_print ("Opening file failed: %s\n", error->message);
+               });
+
+               async_failed (async, error);
+               return;
+       }
+
+       if (saver->priv->compression_type == GTK_SOURCE_COMPRESSION_TYPE_GZIP)
+       {
+               GZlibCompressor *compressor;
+
+               DEBUG ({
+                      g_print ("Use gzip compressor\n");
+               });
+
+               compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
+
+               base_stream = g_converter_output_stream_new (G_OUTPUT_STREAM (file_stream),
+                                                            G_CONVERTER (compressor));
+
+               g_object_unref (compressor);
+               g_object_unref (file_stream);
+       }
+       else
+       {
+               base_stream = G_OUTPUT_STREAM (file_stream);
+       }
+
+       /* FIXME: manage converter error? */
+       DEBUG ({
+              g_print ("Encoding charset: %s\n",
+                       gtk_source_encoding_get_charset (saver->priv->encoding));
+       });
+
+       if (saver->priv->encoding != gtk_source_encoding_get_utf8 ())
+       {
+               converter = g_charset_converter_new (gtk_source_encoding_get_charset (saver->priv->encoding),
+                                                    "UTF-8",
+                                                    NULL);
+
+               saver->priv->stream = g_converter_output_stream_new (base_stream,
+                                                                    G_CONVERTER (converter));
+
+               g_object_unref (converter);
+               g_object_unref (base_stream);
+       }
+       else
+       {
+               saver->priv->stream = G_OUTPUT_STREAM (base_stream);
+       }
+
+       saver->priv->input = _gtk_source_buffer_input_stream_new (saver->priv->buffer,
+                                                                 saver->priv->newline_type,
+                                                                 saver->priv->ensure_trailing_newline);
+
+       saver->priv->size = _gtk_source_buffer_input_stream_get_total_size (GTK_SOURCE_BUFFER_INPUT_STREAM 
(saver->priv->input));
+
+       read_file_chunk (async);
+}
+
+static void
+begin_write (AsyncData *async)
+{
+       GtkSourceFileSaver *saver;
+       gboolean make_backup;
+
+       DEBUG ({
+              g_print ("Start replacing file contents\n");
+       });
+
+       /* For remote files we simply use g_file_replace_async. There is no
+        * backup as of yet.
+        */
+       saver = async->saver;
+
+       make_backup = (saver->priv->flags & GTK_SOURCE_FILE_SAVE_IGNORE_BACKUP) == 0 &&
+                     (saver->priv->flags & GTK_SOURCE_FILE_SAVE_PRESERVE_BACKUP) == 0;
+
+       DEBUG ({
+              g_print ("File contents size: %" G_GINT64_FORMAT "\n", saver->priv->size);
+              g_print ("Make backup: %s\n", make_backup ? "yes" : "no");
+       });
+
+       g_file_replace_async (saver->priv->location,
+                             NULL,
+                             make_backup,
+                             G_FILE_CREATE_NONE,
+                             G_PRIORITY_HIGH,
+                             async->cancellable,
+                             (GAsyncReadyCallback) async_replace_ready_callback,
+                             async);
+}
+
+static void
+mount_ready_callback (GFile        *file,
+                     GAsyncResult *res,
+                     AsyncData    *async)
+{
+       GError *error = NULL;
+       gboolean mounted;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* manual check for cancelled state */
+       if (g_cancellable_is_cancelled (async->cancellable))
+       {
+               async_data_free (async);
+               return;
+       }
+
+       mounted = g_file_mount_enclosing_volume_finish (file, res, &error);
+
+       if (!mounted)
+       {
+               async_failed (async, error);
+       }
+       else
+       {
+               /* try again to get the modified state */
+               check_modified_async (async);
+       }
+}
+
+static GMountOperation *
+create_mount_operation (GtkSourceFileSaver *saver)
+{
+       return saver->priv->mount_operation_factory != NULL ?
+               saver->priv->mount_operation_factory (saver->priv->mount_operation_userdata) :
+               g_mount_operation_new ();
+}
+
+static void
+recover_not_mounted (AsyncData *async)
+{
+       GMountOperation *mount_operation = create_mount_operation (async->saver);
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       async->tried_mount = TRUE;
+       g_file_mount_enclosing_volume (async->saver->priv->location,
+                                      G_MOUNT_MOUNT_NONE,
+                                      mount_operation,
+                                      async->cancellable,
+                                      (GAsyncReadyCallback) mount_ready_callback,
+                                      async);
+
+       g_object_unref (mount_operation);
+}
+
+static void
+check_modification_callback (GFile        *source,
+                            GAsyncResult *res,
+                            AsyncData    *async)
+{
+       GtkSourceFileSaver *saver;
+       GError *error = NULL;
+       GFileInfo *info;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* manually check cancelled state */
+       if (g_cancellable_is_cancelled (async->cancellable))
+       {
+               async_data_free (async);
+               return;
+       }
+
+       saver = async->saver;
+       info = g_file_query_info_finish (source, res, &error);
+       if (info == NULL)
+       {
+               if (error->code == G_IO_ERROR_NOT_MOUNTED && !async->tried_mount)
+               {
+                       recover_not_mounted (async);
+                       g_error_free (error);
+                       return;
+               }
+
+               /* it's perfectly fine if the file doesn't exist yet */
+               if (error->code != G_IO_ERROR_NOT_FOUND)
+               {
+                       DEBUG ({
+                              g_print ("Error getting modification: %s\n", error->message);
+                       });
+
+                       async_failed (async, error);
+                       return;
+               }
+       }
+
+       /* check if the mtime is > what we know about it (if we have it) */
+       if (info != NULL && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+       {
+               GTimeVal mtime;
+               GTimeVal old_mtime;
+
+               g_file_info_get_modification_time (info, &mtime);
+               old_mtime = saver->priv->old_mtime;
+
+               if ((old_mtime.tv_sec > 0 || old_mtime.tv_usec > 0) &&
+                   (mtime.tv_sec != old_mtime.tv_sec || mtime.tv_usec != old_mtime.tv_usec) &&
+                   (saver->priv->flags & GTK_SOURCE_FILE_SAVE_IGNORE_MTIME) == 0)
+               {
+                       DEBUG ({
+                              g_print ("File is externally modified\n");
+                       });
+
+                       g_set_error (&saver->priv->error,
+                                    GTK_SOURCE_FILE_ERROR,
+                                    GTK_SOURCE_FILE_ERROR_EXTERNALLY_MODIFIED,
+                                    "Externally modified");
+
+                       remote_save_completed_or_failed (saver, async);
+                       g_object_unref (info);
+
+                       return;
+               }
+       }
+
+       if (info != NULL)
+       {
+               g_object_unref (info);
+       }
+
+       /* modification check passed, start write */
+       begin_write (async);
+}
+
+static void
+check_modified_async (AsyncData *async)
+{
+       DEBUG ({
+              g_print ("Check externally modified\n");
+       });
+
+       g_file_query_info_async (async->saver->priv->location,
+                                G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                G_FILE_QUERY_INFO_NONE,
+                                G_PRIORITY_HIGH,
+                                async->cancellable,
+                                (GAsyncReadyCallback) check_modification_callback,
+                                async);
+}
+
+static gboolean
+save_remote_file_real (GtkSourceFileSaver *saver)
+{
+       AsyncData *async;
+
+       DEBUG ({
+              g_print ("Starting  save\n");
+       });
+
+       /* First find out if the file is modified externally. This requires
+        * a stat, but I don't think we can do this any other way
+        */
+       async = async_data_new (saver);
+
+       check_modified_async (async);
+
+       /* return false to stop timeout */
+       return FALSE;
+}
+
+void
+gtk_source_file_saver_save (GtkSourceFileSaver *saver,
+                           GTimeVal           *old_mtime)
+{
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->location != NULL);
+
+       g_return_if_fail (saver->priv->used == FALSE);
+       saver->priv->used = TRUE;
+
+       /* CHECK:
+        report async (in an idle handler) or sync (bool ret)
+        async is extra work here, sync is special casing in the caller */
+
+       saver->priv->old_mtime = *old_mtime;
+
+       /* saving start */
+       gtk_source_file_saver_saving (saver, FALSE, NULL);
+
+       g_timeout_add_full (G_PRIORITY_HIGH,
+                           0,
+                           (GSourceFunc) save_remote_file_real,
+                           saver,
+                           NULL);
+}
+
+void
+gtk_source_file_saver_saving (GtkSourceFileSaver *saver,
+                            gboolean            completed,
+                            GError             *error)
+{
+       /* the object will be unrefed in the callback of the saving
+        * signal, so we need to prevent finalization.
+        */
+       if (completed)
+       {
+               g_object_ref (saver);
+       }
+
+       g_signal_emit (saver, signals[SAVING], 0, completed, error);
+
+       if (completed)
+       {
+               if (error == NULL)
+               {
+                       DEBUG ({
+                              g_print ("save completed\n");
+                       });
+               }
+               else
+               {
+                       DEBUG ({
+                              g_print ("save failed\n");
+                       });
+               }
+
+               g_object_unref (saver);
+       }
+}
+
+GtkTextBuffer *
+gtk_source_file_saver_get_buffer (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->buffer;
+}
+
+GFile *
+gtk_source_file_saver_get_location (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return g_file_dup (saver->priv->location);
+}
+
+/* Returns 0 if file size is unknown */
+goffset
+gtk_source_file_saver_get_file_size (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), 0);
+
+       return saver->priv->size;
+}
+
+goffset
+gtk_source_file_saver_get_bytes_written (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), 0);
+
+       return saver->priv->bytes_written;
+}
+
+GFileInfo *
+gtk_source_file_saver_get_info (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->info;
+}
+
+void
+gtk_source_file_saver_set_mount_operation_factory (GtkSourceFileSaver             *saver,
+                                                  GtkSourceMountOperationFactory  callback,
+                                                  gpointer                        user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+
+       saver->priv->mount_operation_factory = callback;
+       saver->priv->mount_operation_userdata = user_data;
+}
diff --git a/gtksourceview/gtksourcefilesaver.h b/gtksourceview/gtksourcefilesaver.h
new file mode 100644
index 0000000..24c889f
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.h
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefilesaver.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005, 2007 - Paolo Maggi
+ * Copyrhing (C) 2007 - Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2013 - Sébastien Wilmet
+ *
+ * GtkSourceView 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.
+ *
+ * GtkSourceView 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 this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_FILE_SAVER_H__
+#define __GTK_SOURCE_FILE_SAVER_H__
+
+#include <gtk/gtk.h>
+#include "gtksourcetypes.h"
+#include "gtksourcetypes-private.h"
+#include "gtksourcefile.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_FILE_SAVER              (gtk_source_file_saver_get_type())
+#define GTK_SOURCE_FILE_SAVER(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaver))
+#define GTK_SOURCE_FILE_SAVER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaverClass))
+#define GTK_SOURCE_IS_FILE_SAVER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
GTK_SOURCE_TYPE_FILE_SAVER))
+#define GTK_SOURCE_IS_FILE_SAVER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_SOURCE_TYPE_FILE_SAVER))
+#define GTK_SOURCE_FILE_SAVER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaverClass))
+
+typedef struct _GtkSourceFileSaverClass   GtkSourceFileSaverClass;
+typedef struct _GtkSourceFileSaverPrivate GtkSourceFileSaverPrivate;
+
+/**
+ * GtkSourceMountOperationFactory: (skip)
+ * @userdata:
+ */
+typedef GMountOperation *(*GtkSourceMountOperationFactory)(gpointer userdata);
+
+struct _GtkSourceFileSaver
+{
+       GObject object;
+
+       GtkSourceFileSaverPrivate *priv;
+};
+
+struct _GtkSourceFileSaverClass
+{
+       GObjectClass parent_class;
+
+       /* Signals */
+       void (* saving) (GtkSourceFileSaver  *saver,
+                        gboolean             completed,
+                        const GError        *error);
+};
+
+G_GNUC_INTERNAL
+GType                   gtk_source_file_saver_get_type         (void) G_GNUC_CONST;
+
+/* If enconding == NULL, the encoding will be autodetected */
+G_GNUC_INTERNAL
+GtkSourceFileSaver     *gtk_source_file_saver_new              (GtkTextBuffer                *buffer,
+                                                                GFile                        *location,
+                                                                const GtkSourceEncoding      *encoding,
+                                                                GtkSourceNewlineType          newline_type,
+                                                                GtkSourceCompressionType      
compression_type,
+                                                                GtkSourceFileSaveFlags        flags,
+                                                                gboolean                      
ensure_trailing_newline);
+
+G_GNUC_INTERNAL
+void                    gtk_source_file_saver_saving           (GtkSourceFileSaver  *saver,
+                                                                gboolean             completed,
+                                                                GError              *error);
+
+G_GNUC_INTERNAL
+void                    gtk_source_file_saver_save             (GtkSourceFileSaver  *saver,
+                                                                GTimeVal            *old_mtime);
+
+G_GNUC_INTERNAL
+GtkTextBuffer          *gtk_source_file_saver_get_buffer       (GtkSourceFileSaver  *saver);
+
+G_GNUC_INTERNAL
+GFile                  *gtk_source_file_saver_get_location     (GtkSourceFileSaver  *saver);
+
+/* If backup_uri is NULL no backup will be made */
+G_GNUC_INTERNAL
+const gchar            *gtk_source_file_saver_get_backup_uri   (GtkSourceFileSaver  *saver);
+
+G_GNUC_INTERNAL
+void                   *gtk_source_file_saver_set_backup_uri   (GtkSourceFileSaver  *saver,
+                                                                const gchar         *backup_uri);
+
+/* Returns 0 if file size is unknown */
+G_GNUC_INTERNAL
+goffset                         gtk_source_file_saver_get_file_size    (GtkSourceFileSaver  *saver);
+
+G_GNUC_INTERNAL
+goffset                         gtk_source_file_saver_get_bytes_written (GtkSourceFileSaver *saver);
+
+G_GNUC_INTERNAL
+GFileInfo              *gtk_source_file_saver_get_info         (GtkSourceFileSaver  *saver);
+
+G_GNUC_INTERNAL
+void                    gtk_source_file_saver_set_mount_operation_factory
+                                                               (GtkSourceFileSaver             *saver,
+                                                                GtkSourceMountOperationFactory  callback,
+                                                                gpointer                        user_data);
+
+G_END_DECLS
+
+#endif  /* __GTK_SOURCE_FILE_SAVER_H__  */
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index 4489548..cc38d97 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -31,6 +31,7 @@ typedef struct _GtkSourceCompletionContainer  GtkSourceCompletionContainer;
 typedef struct _GtkSourceCompletionModel       GtkSourceCompletionModel;
 typedef struct _GtkSourceContextEngine         GtkSourceContextEngine;
 typedef struct _GtkSourceEngine                        GtkSourceEngine;
+typedef struct _GtkSourceFileSaver             GtkSourceFileSaver;
 typedef struct _GtkSourceGutterRendererLines   GtkSourceGutterRendererLines;
 typedef struct _GtkSourceGutterRendererMarks   GtkSourceGutterRendererMarks;
 typedef struct _GtkSourceMarksSequence         GtkSourceMarksSequence;
diff --git a/gtksourceview/gtksourceview-marshal.list b/gtksourceview/gtksourceview-marshal.list
index 1ef9b81..72114e9 100644
--- a/gtksourceview/gtksourceview-marshal.list
+++ b/gtksourceview/gtksourceview-marshal.list
@@ -1,6 +1,7 @@
 VOID:VOID
 VOID:BOOLEAN
 VOID:BOOLEAN,INT
+VOID:BOOLEAN,POINTER
 VOID:BOXED
 VOID:BOXED,BOXED
 VOID:BOXED,ENUM
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3cf64be..78ee138 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -125,6 +125,7 @@ gtksourceview/gtksourcecompletionmodel.c
 gtksourceview/gtksourcecontextengine.c
 gtksourceview/gtksourceencoding.c
 gtksourceview/gtksourcefile.c
+gtksourceview/gtksourcefilesaver.c
 gtksourceview/gtksourcegutter.c
 gtksourceview/gtksourcegutterrenderer.c
 gtksourceview/gtksourcegutterrendererpixbuf.c



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