[gtksourceview/wip/loader-saver] GtkSourceFileSaver private class, just compilable
- From: Sébastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/loader-saver] GtkSourceFileSaver private class, just compilable
- Date: Thu, 12 Dec 2013 18:21:37 +0000 (UTC)
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]