[gedit] Merge gio document loader and saver into document loader and saver
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gedit] Merge gio document loader and saver into document loader and saver
- Date: Fri, 30 Apr 2010 07:36:18 +0000 (UTC)
commit 4bd74a1f47a3fa41385ffae3bb78aeb5afabb564
Author: Garrett Regier <alias301 gmail com>
Date: Thu Apr 29 23:17:58 2010 -0700
Merge gio document loader and saver into document loader and saver
https://bugzilla.gnome.org/show_bug.cgi?id=617215
gedit/Makefile.am | 4 -
gedit/gedit-document-loader.c | 726 +++++++++++++++++++++++++++++----
gedit/gedit-document-loader.h | 21 +-
gedit/gedit-document-saver.c | 811 +++++++++++++++++++++++++++++++++----
gedit/gedit-document-saver.h | 30 +-
gedit/gedit-gio-document-loader.c | 704 --------------------------------
gedit/gedit-gio-document-loader.h | 80 ----
gedit/gedit-gio-document-saver.c | 770 -----------------------------------
gedit/gedit-gio-document-saver.h | 77 ----
tests/document-loader.c | 2 +-
tests/document-saver.c | 2 +-
11 files changed, 1408 insertions(+), 1819 deletions(-)
---
diff --git a/gedit/Makefile.am b/gedit/Makefile.am
index aa86b0e..5a2da38 100644
--- a/gedit/Makefile.am
+++ b/gedit/Makefile.am
@@ -70,8 +70,6 @@ NOINST_H_FILES = \
gedit-document-output-stream.h \
gedit-document-saver.h \
gedit-documents-panel.h \
- gedit-gio-document-loader.h \
- gedit-gio-document-saver.h \
gedit-history-entry.h \
gedit-io-error-message-area.h \
gedit-language-manager.h \
@@ -147,9 +145,7 @@ libgedit_la_SOURCES = \
gedit-document-input-stream.c \
gedit-document-loader.c \
gedit-document-output-stream.c \
- gedit-gio-document-loader.c \
gedit-document-saver.c \
- gedit-gio-document-saver.c \
gedit-documents-panel.c \
gedit-encodings.c \
gedit-encodings-combo-box.c \
diff --git a/gedit/gedit-document-loader.c b/gedit/gedit-document-loader.c
index 36b6ac9..67116f8 100644
--- a/gedit/gedit-document-loader.c
+++ b/gedit/gedit-document-loader.c
@@ -4,6 +4,7 @@
*
* Copyright (C) 2005 - Paolo Maggi
* Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -17,12 +18,12 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330,
+ * Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
- * Modified by the gedit Team, 2005-2007. See the AUTHORS file for a
+ * Modified by the gedit Team, 2005-2008. See the AUTHORS file for a
* list of people on the gedit Team.
* See the ChangeLog files for a list of changes.
*
@@ -34,18 +35,31 @@
#endif
#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+#include <gio/gio.h>
#include "gedit-document-loader.h"
+#include "gedit-document-output-stream.h"
+#include "gedit-smart-charset-converter.h"
+#include "gedit-prefs-manager.h"
#include "gedit-debug.h"
#include "gedit-metadata-manager.h"
#include "gedit-utils.h"
#include "gedit-marshal.h"
#include "gedit-enum-types.h"
-/* Those are for the the gedit_document_loader_new() factory */
-#include "gedit-gio-document-loader.h"
+#ifndef ENABLE_GVFS_METADATA
+#include "gedit-metadata-manager.h"
+#endif
+
+typedef struct
+{
+ GeditDocumentLoader *loader;
+ GCancellable *cancellable;
-G_DEFINE_ABSTRACT_TYPE(GeditDocumentLoader, gedit_document_loader, G_TYPE_OBJECT)
+ gssize read;
+ gboolean tried_mount;
+} AsyncData;
/* Signals */
@@ -67,6 +81,47 @@ enum
PROP_NEWLINE_TYPE
};
+#define READ_CHUNK_SIZE 8192
+#define REMOTE_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+ G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE "," \
+ GEDIT_METADATA_ATTRIBUTE_ENCODING
+
+#define GEDIT_DOCUMENT_LOADER_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_DOCUMENT_LOADER, \
+ GeditDocumentLoaderPrivate))
+
+static void open_async_read (AsyncData *async);
+
+struct _GeditDocumentLoaderPrivate
+{ GeditDocument *document;
+ gboolean used;
+
+ /* Info on the current file */
+ GFileInfo *info;
+ GFile *location;
+ const GeditEncoding *encoding;
+ const GeditEncoding *auto_detected_encoding;
+ GeditDocumentNewlineType auto_detected_newline_type;
+
+ goffset bytes_read;
+
+ /* Handle for remote files */
+ GCancellable *cancellable;
+ GInputStream *stream;
+ GOutputStream *output;
+ GeditSmartCharsetConverter *converter;
+
+ gchar buffer[READ_CHUNK_SIZE];
+
+ GError *error;
+};
+
+G_DEFINE_TYPE(GeditDocumentLoader, gedit_document_loader, G_TYPE_OBJECT)
+
static void
gedit_document_loader_set_property (GObject *object,
guint prop_id,
@@ -78,19 +133,19 @@ gedit_document_loader_set_property (GObject *object,
switch (prop_id)
{
case PROP_DOCUMENT:
- g_return_if_fail (loader->document == NULL);
- loader->document = g_value_get_object (value);
+ g_return_if_fail (loader->priv->document == NULL);
+ loader->priv->document = g_value_get_object (value);
break;
case PROP_LOCATION:
- g_return_if_fail (loader->location == NULL);
- loader->location = g_value_dup_object (value);
+ g_return_if_fail (loader->priv->location == NULL);
+ loader->priv->location = g_value_dup_object (value);
break;
case PROP_ENCODING:
- g_return_if_fail (loader->encoding == NULL);
- loader->encoding = g_value_get_boxed (value);
+ g_return_if_fail (loader->priv->encoding == NULL);
+ loader->priv->encoding = g_value_get_boxed (value);
break;
case PROP_NEWLINE_TYPE:
- loader->auto_detected_newline_type = g_value_get_enum (value);
+ loader->priv->auto_detected_newline_type = g_value_get_enum (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -109,16 +164,16 @@ gedit_document_loader_get_property (GObject *object,
switch (prop_id)
{
case PROP_DOCUMENT:
- g_value_set_object (value, loader->document);
+ g_value_set_object (value, loader->priv->document);
break;
case PROP_LOCATION:
- g_value_set_object (value, loader->location);
+ g_value_set_object (value, loader->priv->location);
break;
case PROP_ENCODING:
g_value_set_boxed (value, gedit_document_loader_get_encoding (loader));
break;
case PROP_NEWLINE_TYPE:
- g_value_set_enum (value, loader->auto_detected_newline_type);
+ g_value_set_enum (value, loader->priv->auto_detected_newline_type);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -127,26 +182,53 @@ gedit_document_loader_get_property (GObject *object,
}
static void
-gedit_document_loader_finalize (GObject *object)
-{
- G_OBJECT_CLASS (gedit_document_loader_parent_class)->finalize (object);
-}
-
-static void
gedit_document_loader_dispose (GObject *object)
{
- GeditDocumentLoader *loader = GEDIT_DOCUMENT_LOADER (object);
+ GeditDocumentLoaderPrivate *priv;
+
+ priv = GEDIT_DOCUMENT_LOADER (object)->priv;
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ if (priv->stream != NULL)
+ {
+ g_object_unref (priv->stream);
+ priv->stream = NULL;
+ }
+
+ if (priv->output != NULL)
+ {
+ g_object_unref (priv->output);
+ priv->output = NULL;
+ }
+
+ if (priv->converter != NULL)
+ {
+ g_object_unref (priv->converter);
+ priv->converter = NULL;
+ }
+
+ if (priv->error != NULL)
+ {
+ g_error_free (priv->error);
+ priv->error = NULL;
+ }
- if (loader->info != NULL)
+ if (priv->info != NULL)
{
- g_object_unref (loader->info);
- loader->info = NULL;
+ g_object_unref (priv->info);
+ priv->info = NULL;
}
- if (loader->location != NULL)
+ if (priv->location != NULL)
{
- g_object_unref (loader->location);
- loader->location = NULL;
+ g_object_unref (priv->location);
+ priv->location = NULL;
}
G_OBJECT_CLASS (gedit_document_loader_parent_class)->dispose (object);
@@ -157,7 +239,6 @@ gedit_document_loader_class_init (GeditDocumentLoaderClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
- object_class->finalize = gedit_document_loader_finalize;
object_class->dispose = gedit_document_loader_dispose;
object_class->get_property = gedit_document_loader_get_property;
object_class->set_property = gedit_document_loader_set_property;
@@ -212,13 +293,513 @@ gedit_document_loader_class_init (GeditDocumentLoaderClass *klass)
2,
G_TYPE_BOOLEAN,
G_TYPE_POINTER);
+
+ g_type_class_add_private (object_class, sizeof (GeditDocumentLoaderPrivate));
}
static void
gedit_document_loader_init (GeditDocumentLoader *loader)
{
- loader->used = FALSE;
- loader->auto_detected_newline_type = GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT;
+ loader->priv = GEDIT_DOCUMENT_LOADER_GET_PRIVATE (loader);
+
+ loader->priv->used = FALSE;
+ loader->priv->auto_detected_newline_type = GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT;
+ loader->priv->converter = NULL;
+ loader->priv->error = NULL;
+}
+
+GeditDocumentLoader *
+gedit_document_loader_new (GeditDocument *doc,
+ GFile *location,
+ const GeditEncoding *encoding)
+{
+ g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
+
+ return GEDIT_DOCUMENT_LOADER (g_object_new (GEDIT_TYPE_DOCUMENT_LOADER,
+ "document", doc,
+ "location", location,
+ "encoding", encoding,
+ NULL));
+}
+
+static AsyncData *
+async_data_new (GeditDocumentLoader *loader)
+{
+ AsyncData *async;
+
+ async = g_slice_new (AsyncData);
+ async->loader = loader;
+ async->cancellable = g_object_ref (loader->priv->cancellable);
+ async->tried_mount = FALSE;
+
+ return async;
+}
+
+static void
+async_data_free (AsyncData *async)
+{
+ g_object_unref (async->cancellable);
+ g_slice_free (AsyncData, async);
+}
+
+static const GeditEncoding *
+get_metadata_encoding (GeditDocumentLoader *loader)
+{
+ const GeditEncoding *enc = NULL;
+
+#ifndef ENABLE_GVFS_METADATA
+ gchar *charset;
+ GFile *location;
+ gchar *uri;
+
+ location = gedit_document_loader_get_location (loader);
+ uri = g_file_get_uri (location);
+ g_object_unref (location);
+
+ charset = gedit_metadata_manager_get (uri, "encoding");
+ g_free (uri);
+
+ if (charset == NULL)
+ return NULL;
+
+ enc = gedit_encoding_get_from_charset (charset);
+
+ g_free (charset);
+#else
+ GFileInfo *info;
+
+ info = gedit_document_loader_get_info (loader);
+
+ /* check if the encoding was set in the metadata */
+ if (g_file_info_has_attribute (info, GEDIT_METADATA_ATTRIBUTE_ENCODING))
+ {
+ const gchar *charset;
+
+ charset = g_file_info_get_attribute_string (info,
+ GEDIT_METADATA_ATTRIBUTE_ENCODING);
+
+ if (charset == NULL)
+ return NULL;
+
+ enc = gedit_encoding_get_from_charset (charset);
+ }
+#endif
+
+ return enc;
+}
+
+static void
+remote_load_completed_or_failed (GeditDocumentLoader *loader, AsyncData *async)
+{
+ gedit_document_loader_loading (loader,
+ TRUE,
+ loader->priv->error);
+
+ if (async)
+ async_data_free (async);
+}
+
+static void
+async_failed (AsyncData *async, GError *error)
+{
+ g_propagate_error (&async->loader->priv->error, error);
+ remote_load_completed_or_failed (async->loader, async);
+}
+
+static void
+close_input_stream_ready_cb (GInputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_LOADER);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gedit_debug_message (DEBUG_SAVER, "Finished closing input stream");
+
+ if (!g_input_stream_close_finish (stream, res, &error))
+ {
+ gedit_debug_message (DEBUG_SAVER, "Closing input stream error: %s", error->message);
+
+ async_failed (async, error);
+ return;
+ }
+
+ gedit_debug_message (DEBUG_SAVER, "Close output stream");
+ if (!g_output_stream_close (async->loader->priv->output,
+ async->cancellable, &error))
+ {
+ async_failed (async, error);
+ return;
+ }
+
+ remote_load_completed_or_failed (async->loader, async);
+}
+
+static void
+write_complete (AsyncData *async)
+{
+ if (async->loader->priv->stream)
+ g_input_stream_close_async (G_INPUT_STREAM (async->loader->priv->stream),
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback)close_input_stream_ready_cb,
+ async);
+}
+
+/* prototype, because they call each other... isn't C lovely */
+static void read_file_chunk (AsyncData *async);
+
+static void
+write_file_chunk (AsyncData *async)
+{
+ GeditDocumentLoader *loader;
+ gssize bytes_written;
+ GError *error = NULL;
+
+ loader = async->loader;
+
+ /* we use sync methods on doc stream since it is in memory. Using async
+ would be racy and we can endup with invalidated iters */
+ bytes_written = g_output_stream_write (G_OUTPUT_STREAM (loader->priv->output),
+ loader->priv->buffer,
+ async->read,
+ async->cancellable,
+ &error);
+
+ gedit_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written);
+ if (bytes_written == -1)
+ {
+ gedit_debug_message (DEBUG_SAVER, "Write error: %s", error->message);
+ async_failed (async, error);
+ return;
+ }
+
+ /* note that this signal blocks the read... check if it isn't
+ * a performance problem
+ */
+ gedit_document_loader_loading (loader,
+ FALSE,
+ NULL);
+
+ read_file_chunk (async);
+}
+
+static void
+async_read_cb (GInputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ gedit_debug (DEBUG_LOADER);
+ GeditDocumentLoader *loader;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_LOADER);
+
+ /* manually check cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ loader = async->loader;
+
+ async->read = g_input_stream_read_finish (stream, res, &error);
+
+ /* error occurred */
+ if (async->read == -1)
+ {
+ async_failed (async, error);
+ return;
+ }
+
+ /* Check for the extremely unlikely case where the file size overflows. */
+ if (loader->priv->bytes_read + async->read < loader->priv->bytes_read)
+ {
+ g_set_error (&loader->priv->error,
+ GEDIT_DOCUMENT_ERROR,
+ GEDIT_DOCUMENT_ERROR_TOO_BIG,
+ "File too big");
+
+ async_failed (async, loader->priv->error);
+ return;
+ }
+
+ /* Bump the size. */
+ loader->priv->bytes_read += async->read;
+
+ /* end of the file, we are done! */
+ if (async->read == 0)
+ {
+ loader->priv->auto_detected_encoding =
+ gedit_smart_charset_converter_get_guessed (loader->priv->converter);
+
+ loader->priv->auto_detected_newline_type =
+ gedit_document_output_stream_detect_newline_type (GEDIT_DOCUMENT_OUTPUT_STREAM (loader->priv->output));
+
+ /* Check if we needed some fallback char, if so, check if there was
+ a previous error and if not set a fallback used error */
+ /* FIXME Uncomment this when we want to manage conversion fallback */
+ /*if ((gedit_smart_charset_converter_get_num_fallbacks (loader->priv->converter) != 0) &&
+ loader->priv->error == NULL)
+ {
+ g_set_error_literal (&loader->priv->error,
+ GEDIT_DOCUMENT_ERROR,
+ GEDIT_DOCUMENT_ERROR_CONVERSION_FALLBACK,
+ "There was a conversion error and it was "
+ "needed to use a fallback char");
+ }*/
+
+ write_complete (async);
+
+ return;
+ }
+
+ write_file_chunk (async);
+}
+
+static void
+read_file_chunk (AsyncData *async)
+{
+ GeditDocumentLoader *loader;
+
+ loader = async->loader;
+
+ g_input_stream_read_async (G_INPUT_STREAM (loader->priv->stream),
+ loader->priv->buffer,
+ READ_CHUNK_SIZE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) async_read_cb,
+ async);
+}
+
+static GSList *
+get_candidate_encodings (GeditDocumentLoader *loader)
+{
+ const GeditEncoding *metadata;
+ GSList *encodings = NULL;
+
+ encodings = gedit_prefs_manager_get_auto_detected_encodings ();
+
+ metadata = get_metadata_encoding (loader);
+ if (metadata != NULL)
+ {
+ encodings = g_slist_prepend (encodings, (gpointer)metadata);
+ }
+
+ return encodings;
+}
+
+static void
+finish_query_info (AsyncData *async)
+{
+ GeditDocumentLoader *loader;
+ GInputStream *conv_stream;
+ GFileInfo *info;
+ GSList *candidate_encodings;
+
+ loader = async->loader;
+ info = loader->priv->info;
+
+ /* if it's not a regular file, error out... */
+ if (g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) &&
+ g_file_info_get_file_type (info) != G_FILE_TYPE_REGULAR)
+ {
+ g_set_error (&loader->priv->error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_REGULAR_FILE,
+ "Not a regular file");
+
+ remote_load_completed_or_failed (loader, async);
+
+ return;
+ }
+
+ /* Get the candidate encodings */
+ if (loader->priv->encoding == NULL)
+ {
+ candidate_encodings = get_candidate_encodings (loader);
+ }
+ else
+ {
+ candidate_encodings = g_slist_prepend (NULL, (gpointer) loader->priv->encoding);
+ }
+
+ loader->priv->converter = gedit_smart_charset_converter_new (candidate_encodings);
+ g_slist_free (candidate_encodings);
+
+ conv_stream = g_converter_input_stream_new (loader->priv->stream,
+ G_CONVERTER (loader->priv->converter));
+ g_object_unref (loader->priv->stream);
+
+ loader->priv->stream = conv_stream;
+
+ /* Output stream */
+ loader->priv->output = gedit_document_output_stream_new (loader->priv->document);
+
+ /* start reading */
+ read_file_chunk (async);
+}
+
+static void
+query_info_cb (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_LOADER);
+
+ /* manually check the cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ /* finish the info query */
+ info = g_file_query_info_finish (async->loader->priv->location,
+ res,
+ &error);
+
+ if (info == NULL)
+ {
+ /* propagate the error and clean up */
+ async_failed (async, error);
+ return;
+ }
+
+ async->loader->priv->info = info;
+
+ finish_query_info (async);
+}
+
+static void
+mount_ready_callback (GFile *file,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+ gboolean mounted;
+
+ gedit_debug (DEBUG_LOADER);
+
+ /* 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 open the file for reading */
+ open_async_read (async);
+ }
+}
+
+static void
+recover_not_mounted (AsyncData *async)
+{
+ GeditDocument *doc;
+ GMountOperation *mount_operation;
+
+ gedit_debug (DEBUG_LOADER);
+
+ doc = gedit_document_loader_get_document (async->loader);
+ mount_operation = _gedit_document_create_mount_operation (doc);
+
+ async->tried_mount = TRUE;
+ g_file_mount_enclosing_volume (async->loader->priv->location,
+ G_MOUNT_MOUNT_NONE,
+ mount_operation,
+ async->cancellable,
+ (GAsyncReadyCallback) mount_ready_callback,
+ async);
+
+ g_object_unref (mount_operation);
+}
+
+static void
+async_read_ready_callback (GObject *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GError *error = NULL;
+ GeditDocumentLoader *loader;
+
+ gedit_debug (DEBUG_LOADER);
+
+ /* manual check for cancelled state */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ loader = async->loader;
+
+ loader->priv->stream = G_INPUT_STREAM (g_file_read_finish (loader->priv->location,
+ res, &error));
+
+ if (!loader->priv->stream)
+ {
+ if (error->code == G_IO_ERROR_NOT_MOUNTED && !async->tried_mount)
+ {
+ recover_not_mounted (async);
+ g_error_free (error);
+ return;
+ }
+
+ /* Propagate error */
+ g_propagate_error (&loader->priv->error, error);
+ gedit_document_loader_loading (loader,
+ TRUE,
+ loader->priv->error);
+
+ async_data_free (async);
+ return;
+ }
+
+ /* get the file info: note we cannot use
+ * g_file_input_stream_query_info_async since it is not able to get the
+ * content type etc, beside it is not supported by gvfs.
+ * Using the file instead of the stream is slightly racy, but for
+ * loading this is not too bad...
+ */
+ g_file_query_info_async (loader->priv->location,
+ REMOTE_QUERY_ATTRIBUTES,
+ G_FILE_QUERY_INFO_NONE,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) query_info_cb,
+ async);
+}
+
+static void
+open_async_read (AsyncData *async)
+{
+ g_file_read_async (async->loader->priv->location,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback) async_read_ready_callback,
+ async);
}
void
@@ -247,56 +828,53 @@ gedit_document_loader_loading (GeditDocumentLoader *loader,
}
}
-/* This is a factory method that returns an appopriate loader
- * for the given location.
- */
-GeditDocumentLoader *
-gedit_document_loader_new (GeditDocument *doc,
- GFile *location,
- const GeditEncoding *encoding)
-{
- GeditDocumentLoader *loader;
- GType loader_type;
-
- g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
-
- /* At the moment we just use gio loader in all cases...
- * In the future it would be great to have a PolicyKit
- * loader to get permission to save systen files etc */
- loader_type = GEDIT_TYPE_GIO_DOCUMENT_LOADER;
-
- loader = GEDIT_DOCUMENT_LOADER (g_object_new (loader_type,
- "document", doc,
- "location", location,
- "encoding", encoding,
- NULL));
-
- return loader;
-}
-
-/* If enconding == NULL, the encoding will be autodetected */
void
gedit_document_loader_load (GeditDocumentLoader *loader)
{
+ AsyncData *async;
+
gedit_debug (DEBUG_LOADER);
g_return_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader));
/* the loader can be used just once, then it must be thrown away */
- g_return_if_fail (loader->used == FALSE);
- loader->used = TRUE;
+ g_return_if_fail (loader->priv->used == FALSE);
+ loader->priv->used = TRUE;
+
+ /* make sure no load operation is currently running */
+ g_return_if_fail (loader->priv->cancellable == NULL);
- GEDIT_DOCUMENT_LOADER_GET_CLASS (loader)->load (loader);
+ /* loading start */
+ gedit_document_loader_loading (loader,
+ FALSE,
+ NULL);
+
+ loader->priv->cancellable = g_cancellable_new ();
+ async = async_data_new (loader);
+
+ open_async_read (async);
}
-gboolean
+gboolean
gedit_document_loader_cancel (GeditDocumentLoader *loader)
{
gedit_debug (DEBUG_LOADER);
-
+
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), FALSE);
- return GEDIT_DOCUMENT_LOADER_GET_CLASS (loader)->cancel (loader);
+ if (loader->priv->cancellable == NULL)
+ return FALSE;
+
+ g_cancellable_cancel (loader->priv->cancellable);
+
+ g_set_error (&loader->priv->error,
+ G_IO_ERROR,
+ G_IO_ERROR_CANCELLED,
+ "Operation cancelled");
+
+ remote_load_completed_or_failed (loader, NULL);
+
+ return TRUE;
}
GeditDocument *
@@ -304,7 +882,7 @@ gedit_document_loader_get_document (GeditDocumentLoader *loader)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL);
- return loader->document;
+ return loader->priv->document;
}
/* Returns STDIN_URI if loading from stdin */
@@ -313,7 +891,7 @@ gedit_document_loader_get_location (GeditDocumentLoader *loader)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL);
- return g_file_dup (loader->location);
+ return g_file_dup (loader->priv->location);
}
goffset
@@ -321,7 +899,7 @@ gedit_document_loader_get_bytes_read (GeditDocumentLoader *loader)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), 0);
- return GEDIT_DOCUMENT_LOADER_GET_CLASS (loader)->get_bytes_read (loader);
+ return loader->priv->bytes_read;
}
const GeditEncoding *
@@ -329,13 +907,13 @@ gedit_document_loader_get_encoding (GeditDocumentLoader *loader)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL);
- if (loader->encoding != NULL)
- return loader->encoding;
+ if (loader->priv->encoding != NULL)
+ return loader->priv->encoding;
- g_return_val_if_fail (loader->auto_detected_encoding != NULL,
+ g_return_val_if_fail (loader->priv->auto_detected_encoding != NULL,
gedit_encoding_get_current ());
- return loader->auto_detected_encoding;
+ return loader->priv->auto_detected_encoding;
}
GeditDocumentNewlineType
@@ -344,7 +922,7 @@ gedit_document_loader_get_newline_type (GeditDocumentLoader *loader)
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader),
GEDIT_DOCUMENT_NEWLINE_TYPE_LF);
- return loader->auto_detected_newline_type;
+ return loader->priv->auto_detected_newline_type;
}
GFileInfo *
@@ -352,6 +930,6 @@ gedit_document_loader_get_info (GeditDocumentLoader *loader)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_LOADER (loader), NULL);
- return loader->info;
+ return loader->priv->info;
}
/* ex:ts=8:noet: */
diff --git a/gedit/gedit-document-loader.h b/gedit/gedit-document-loader.h
index 2b5b867..644af20 100644
--- a/gedit/gedit-document-loader.h
+++ b/gedit/gedit-document-loader.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2005 - Paolo Maggi
* Copyright (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,7 +23,7 @@
*/
/*
- * Modified by the gedit Team, 2005-2007. See the AUTHORS file for a
+ * Modified by the gedit Team, 2005-2008. See the AUTHORS file for a
* list of people on the gedit Team.
* See the ChangeLog files for a list of changes.
*
@@ -54,19 +55,12 @@ typedef struct _GeditDocumentLoaderPrivate GeditDocumentLoaderPrivate;
*/
typedef struct _GeditDocumentLoader GeditDocumentLoader;
-struct _GeditDocumentLoader
+struct _GeditDocumentLoader
{
GObject object;
- GeditDocument *document;
- gboolean used;
-
- /* Info on the current file */
- GFileInfo *info;
- GFile *location;
- const GeditEncoding *encoding;
- const GeditEncoding *auto_detected_encoding;
- GeditDocumentNewlineType auto_detected_newline_type;
+ /*< private > */
+ GeditDocumentLoaderPrivate *priv;
};
/*
@@ -82,11 +76,6 @@ struct _GeditDocumentLoaderClass
void (* loading) (GeditDocumentLoader *loader,
gboolean completed,
const GError *error);
-
- /* VTable */
- void (* load) (GeditDocumentLoader *loader);
- gboolean (* cancel) (GeditDocumentLoader *loader);
- goffset (* get_bytes_read) (GeditDocumentLoader *loader);
};
/*
diff --git a/gedit/gedit-document-saver.c b/gedit/gedit-document-saver.c
index 6e9ad8d..7dce672 100644
--- a/gedit/gedit-document-saver.c
+++ b/gedit/gedit-document-saver.c
@@ -4,6 +4,7 @@
*
* Copyright (C) 2005-2006 - Paolo Borelli and Paolo Maggi
* Copyright (C) 2007 - Paolo Borelli, Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -31,21 +32,20 @@
#include <config.h>
#endif
-#include <string.h>
-#include <errno.h>
-#include <unistd.h>
-
#include <glib/gi18n.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <string.h>
#include "gedit-document-saver.h"
-#include "gedit-debug.h"
+#include "gedit-document-input-stream.h"
#include "gedit-prefs-manager.h"
+#include "gedit-debug.h"
#include "gedit-marshal.h"
#include "gedit-utils.h"
#include "gedit-enum-types.h"
-#include "gedit-gio-document-saver.h"
-G_DEFINE_ABSTRACT_TYPE(GeditDocumentSaver, gedit_document_saver, G_TYPE_OBJECT)
+#define WRITE_CHUNK_SIZE 8192
/* Signals */
@@ -67,6 +67,54 @@ enum {
PROP_FLAGS
};
+typedef struct
+{
+ GeditDocumentSaver *saver;
+ gchar buffer[WRITE_CHUNK_SIZE];
+ GCancellable *cancellable;
+ gboolean tried_mount;
+ gssize written;
+ gssize read;
+ GError *error;
+} AsyncData;
+
+#define REMOTE_QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+#define GEDIT_DOCUMENT_SAVER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), \
+ GEDIT_TYPE_DOCUMENT_SAVER, \
+ GeditDocumentSaverPrivate))
+
+static void check_modified_async (AsyncData *async);
+
+struct _GeditDocumentSaverPrivate
+{
+ GFileInfo *info;
+ GeditDocument *document;
+ gboolean used;
+
+ GFile *location;
+ const GeditEncoding *encoding;
+ GeditDocumentNewlineType newline_type;
+
+ GeditDocumentSaveFlags flags;
+
+ gboolean keep_backup;
+
+ GTimeVal old_mtime;
+
+ goffset size;
+ goffset bytes_written;
+
+ GCancellable *cancellable;
+ GOutputStream *stream;
+ GInputStream *input;
+
+ GError *error;
+};
+
+G_DEFINE_TYPE(GeditDocumentSaver, gedit_document_saver, G_TYPE_OBJECT)
+
static void
gedit_document_saver_set_property (GObject *object,
guint prop_id,
@@ -78,22 +126,22 @@ gedit_document_saver_set_property (GObject *object,
switch (prop_id)
{
case PROP_DOCUMENT:
- g_return_if_fail (saver->document == NULL);
- saver->document = g_value_get_object (value);
+ g_return_if_fail (saver->priv->document == NULL);
+ saver->priv->document = g_value_get_object (value);
break;
case PROP_LOCATION:
- g_return_if_fail (saver->location == NULL);
- saver->location = g_value_dup_object (value);
+ g_return_if_fail (saver->priv->location == NULL);
+ saver->priv->location = g_value_dup_object (value);
break;
case PROP_ENCODING:
- g_return_if_fail (saver->encoding == NULL);
- saver->encoding = g_value_get_boxed (value);
+ g_return_if_fail (saver->priv->encoding == NULL);
+ saver->priv->encoding = g_value_get_boxed (value);
break;
case PROP_NEWLINE_TYPE:
- saver->newline_type = g_value_get_enum (value);
+ saver->priv->newline_type = g_value_get_enum (value);
break;
case PROP_FLAGS:
- saver->flags = g_value_get_flags (value);
+ saver->priv->flags = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -112,19 +160,19 @@ gedit_document_saver_get_property (GObject *object,
switch (prop_id)
{
case PROP_DOCUMENT:
- g_value_set_object (value, saver->document);
+ g_value_set_object (value, saver->priv->document);
break;
case PROP_LOCATION:
- g_value_set_object (value, saver->location);
+ g_value_set_object (value, saver->priv->location);
break;
case PROP_ENCODING:
- g_value_set_boxed (value, saver->encoding);
+ g_value_set_boxed (value, saver->priv->encoding);
break;
case PROP_NEWLINE_TYPE:
- g_value_set_enum (value, saver->newline_type);
+ g_value_set_enum (value, saver->priv->newline_type);
break;
case PROP_FLAGS:
- g_value_set_flags (value, saver->flags);
+ g_value_set_flags (value, saver->priv->flags);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -135,23 +183,79 @@ gedit_document_saver_get_property (GObject *object,
static void
gedit_document_saver_dispose (GObject *object)
{
- GeditDocumentSaver *saver = GEDIT_DOCUMENT_SAVER (object);
+ GeditDocumentSaverPrivate *priv = GEDIT_DOCUMENT_SAVER (object)->priv;
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+ }
+
+ if (priv->error != NULL)
+ {
+ g_error_free (priv->error);
+ priv->error = NULL;
+ }
+
+ if (priv->stream != NULL)
+ {
+ g_object_unref (priv->stream);
+ priv->stream = NULL;
+ }
- if (saver->info != NULL)
+ if (priv->input != NULL)
{
- g_object_unref (saver->info);
- saver->info = NULL;
+ g_object_unref (priv->input);
+ priv->input = NULL;
+ }
+
+ if (priv->info != NULL)
+ {
+ g_object_unref (priv->info);
+ priv->info = NULL;
}
- if (saver->location != NULL)
+ if (priv->location != NULL)
{
- g_object_unref (saver->location);
- saver->location = NULL;
+ g_object_unref (priv->location);
+ priv->location = NULL;
}
G_OBJECT_CLASS (gedit_document_saver_parent_class)->dispose (object);
}
+static AsyncData *
+async_data_new (GeditDocumentSaver *saver)
+{
+ AsyncData *async;
+
+ async = g_slice_new (AsyncData);
+ async->saver = saver;
+ async->cancellable = g_object_ref (saver->priv->cancellable);
+
+ async->tried_mount = FALSE;
+ async->written = 0;
+ async->read = 0;
+
+ async->error = NULL;
+
+ return async;
+}
+
+static void
+async_data_free (AsyncData *async)
+{
+ g_object_unref (async->cancellable);
+
+ if (async->error)
+ {
+ g_error_free (async->error);
+ }
+
+ g_slice_free (AsyncData, async);
+}
+
static void
gedit_document_saver_class_init (GeditDocumentSaverClass *klass)
{
@@ -224,12 +328,18 @@ gedit_document_saver_class_init (GeditDocumentSaverClass *klass)
2,
G_TYPE_BOOLEAN,
G_TYPE_POINTER);
+
+ g_type_class_add_private (object_class, sizeof (GeditDocumentSaverPrivate));
}
static void
gedit_document_saver_init (GeditDocumentSaver *saver)
{
- saver->used = FALSE;
+ saver->priv = GEDIT_DOCUMENT_SAVER_GET_PRIVATE (saver);
+
+ saver->priv->cancellable = g_cancellable_new ();
+ saver->priv->error = NULL;
+ saver->priv->used = FALSE;
}
GeditDocumentSaver *
@@ -239,76 +349,637 @@ gedit_document_saver_new (GeditDocument *doc,
GeditDocumentNewlineType newline_type,
GeditDocumentSaveFlags flags)
{
- GeditDocumentSaver *saver;
- GType saver_type;
-
g_return_val_if_fail (GEDIT_IS_DOCUMENT (doc), NULL);
- saver_type = GEDIT_TYPE_GIO_DOCUMENT_SAVER;
-
if (encoding == NULL)
encoding = gedit_encoding_get_utf8 ();
- saver = GEDIT_DOCUMENT_SAVER (g_object_new (saver_type,
- "document", doc,
- "location", location,
- "encoding", encoding,
- "newline_type", newline_type,
- "flags", flags,
- NULL));
+ return GEDIT_DOCUMENT_SAVER (g_object_new (GEDIT_TYPE_DOCUMENT_SAVER,
+ "document", doc,
+ "location", location,
+ "encoding", encoding,
+ "newline_type", newline_type,
+ "flags", flags,
+ NULL));
+}
+
+static void
+remote_save_completed_or_failed (GeditDocumentSaver *saver,
+ AsyncData *async)
+{
+ gedit_document_saver_saving (saver,
+ TRUE,
+ saver->priv->error);
- return saver;
+ if (async)
+ async_data_free (async);
}
-void
-gedit_document_saver_saving (GeditDocumentSaver *saver,
- gboolean completed,
- GError *error)
+static void
+async_failed (AsyncData *async,
+ GError *error)
{
- /* the object will be unrefed in the callback of the saving
- * signal, so we need to prevent finalization.
+ 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 . 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/)
+ * 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;
+
+ gedit_debug_message (DEBUG_SAVER, "Cancel output stream");
+
+ 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)
+{
+
+ gedit_debug_message (DEBUG_SAVER, "Cancel output stream and fail");
+
+ g_propagate_error (&async->error, error);
+ cancel_output_stream (async);
+}
+
+/*
+ * END NOTE
+ */
+
+static void
+remote_get_info_cb (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GeditDocumentSaver *saver;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_SAVER);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ saver = async->saver;
+
+ gedit_debug_message (DEBUG_SAVER, "Finished query info on file");
+ 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
+ {
+ gedit_debug_message (DEBUG_SAVER, "Query info failed: %s", 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;
+
+ gedit_debug (DEBUG_SAVER);
+
+ /* check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
+ {
+ async_data_free (async);
+ return;
+ }
+
+ gedit_debug_message (DEBUG_SAVER, "Finished closing stream");
+
+ if (!g_output_stream_close_finish (stream, res, &error))
+ {
+ gedit_debug_message (DEBUG_SAVER, "Closing stream error: %s", 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)
*/
- if (completed)
+ gedit_debug_message (DEBUG_SAVER, "Query info on file");
+ 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 */
+ gedit_debug_message (DEBUG_SAVER, "Close input stream");
+ if (!g_input_stream_close (async->saver->priv->input,
+ async->cancellable, &error))
{
- g_object_ref (saver);
+ gedit_debug_message (DEBUG_SAVER, "Closing input stream error: %s", error->message);
+ cancel_output_stream_and_fail (async, error);
+ return;
}
- g_signal_emit (saver, signals[SAVING], 0, completed, error);
+ /* now we close the output stream */
+ gedit_debug_message (DEBUG_SAVER, "Close output stream");
+ g_output_stream_close_async (async->saver->priv->stream,
+ G_PRIORITY_HIGH,
+ async->cancellable,
+ (GAsyncReadyCallback)close_async_ready_get_info_cb,
+ async);
+}
- if (completed)
+/* 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
+async_write_cb (GOutputStream *stream,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GeditDocumentSaver *saver;
+ gssize bytes_written;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_SAVER);
+
+ /* Check cancelled state manually */
+ if (g_cancellable_is_cancelled (async->cancellable))
{
- if (error == NULL)
- gedit_debug_message (DEBUG_SAVER, "save completed");
- else
- gedit_debug_message (DEBUG_SAVER, "save failed");
+ cancel_output_stream (async);
+ return;
+ }
- g_object_unref (saver);
+ bytes_written = g_output_stream_write_finish (stream, res, &error);
+
+ gedit_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written);
+
+ if (bytes_written == -1)
+ {
+ gedit_debug_message (DEBUG_SAVER, "Write error: %s", 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
+ */
+ gedit_document_saver_saving (saver,
+ FALSE,
+ NULL);
+
+ read_file_chunk (async);
+}
+
+static void
+write_file_chunk (AsyncData *async)
+{
+ GeditDocumentSaver *saver;
+
+ gedit_debug (DEBUG_SAVER);
+
+ 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)
+{
+ GeditDocumentSaver *saver;
+ GeditDocumentInputStream *dstream;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_SAVER);
+
+ 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 = GEDIT_DOCUMENT_INPUT_STREAM (saver->priv->input);
+ saver->priv->bytes_written = gedit_document_input_stream_tell (dstream);
+
+ write_file_chunk (async);
+}
+
+static void
+async_replace_ready_callback (GFile *source,
+ GAsyncResult *res,
+ AsyncData *async)
+{
+ GeditDocumentSaver *saver;
+ GCharsetConverter *converter;
+ GFileOutputStream *file_stream;
+ GError *error = NULL;
+
+ gedit_debug (DEBUG_SAVER);
+
+ /* 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)
+ {
+ gedit_debug_message (DEBUG_SAVER, "Opening file failed: %s", error->message);
+ async_failed (async, error);
+ return;
+ }
+
+ /* FIXME: manage converter error? */
+ gedit_debug_message (DEBUG_SAVER, "Encoding charset: %s",
+ gedit_encoding_get_charset (saver->priv->encoding));
+
+ if (saver->priv->encoding != gedit_encoding_get_utf8 ())
+ {
+ converter = g_charset_converter_new (gedit_encoding_get_charset (saver->priv->encoding),
+ "UTF-8",
+ NULL);
+ saver->priv->stream = g_converter_output_stream_new (G_OUTPUT_STREAM (file_stream),
+ G_CONVERTER (converter));
+
+ g_object_unref (file_stream);
+ g_object_unref (converter);
+ }
+ else
+ {
+ saver->priv->stream = G_OUTPUT_STREAM (file_stream);
+ }
+
+ saver->priv->input = gedit_document_input_stream_new (GTK_TEXT_BUFFER (saver->priv->document),
+ saver->priv->newline_type);
+
+ saver->priv->size = gedit_document_input_stream_get_total_size (GEDIT_DOCUMENT_INPUT_STREAM (saver->priv->input));
+
+ read_file_chunk (async);
+}
+
+static void
+begin_write (AsyncData *async)
+{
+ GeditDocumentSaver *saver;
+ gboolean backup;
+
+ gedit_debug_message (DEBUG_SAVER, "Start replacing file contents");
+
+ /* For remote files we simply use g_file_replace_async. There is no
+ * backup as of yet
+ */
+ saver = async->saver;
+
+ /* Do not make backups for remote files so they do not clutter remote systems */
+ backup = (saver->priv->keep_backup && gedit_document_is_local (saver->priv->document));
+
+ gedit_debug_message (DEBUG_SAVER, "File contents size: %" G_GINT64_FORMAT, saver->priv->size);
+ gedit_debug_message (DEBUG_SAVER, "Calling replace_async");
+ gedit_debug_message (DEBUG_SAVER, backup ? "Keep backup" : "Discard backup");
+
+ g_file_replace_async (saver->priv->location,
+ NULL,
+ 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;
+
+ gedit_debug (DEBUG_SAVER);
+
+ /* 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 void
+recover_not_mounted (AsyncData *async)
+{
+ GeditDocument *doc;
+ GMountOperation *mount_operation;
+
+ gedit_debug (DEBUG_LOADER);
+
+ doc = gedit_document_saver_get_document (async->saver);
+ mount_operation = _gedit_document_create_mount_operation (doc);
+
+ 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)
+{
+ GeditDocumentSaver *saver;
+ GError *error = NULL;
+ GFileInfo *info;
+
+ gedit_debug (DEBUG_SAVER);
+
+ /* 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)
+ {
+ gedit_debug_message (DEBUG_SAVER, "Error getting modification: %s", 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 & GEDIT_DOCUMENT_SAVE_IGNORE_MTIME) == 0)
+ {
+ gedit_debug_message (DEBUG_SAVER, "File is externally modified");
+ g_set_error (&saver->priv->error,
+ GEDIT_DOCUMENT_ERROR,
+ GEDIT_DOCUMENT_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)
+{
+ gedit_debug_message (DEBUG_SAVER, "Check externally modified");
+
+ 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 (GeditDocumentSaver *saver)
+{
+ AsyncData *async;
+
+ gedit_debug_message (DEBUG_SAVER, "Starting save");
+
+ /* 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
-gedit_document_saver_save (GeditDocumentSaver *saver,
- GTimeVal *old_mtime)
+gedit_document_saver_save (GeditDocumentSaver *saver,
+ GTimeVal *old_mtime)
{
gedit_debug (DEBUG_SAVER);
g_return_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver));
- g_return_if_fail (saver->location != NULL);
+ g_return_if_fail (saver->priv->location != NULL);
- g_return_if_fail (saver->used == FALSE);
- saver->used = TRUE;
+ 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 */
/* never keep backup of autosaves */
- if ((saver->flags & GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP) != 0)
- saver->keep_backup = FALSE;
+ if ((saver->priv->flags & GEDIT_DOCUMENT_SAVE_PRESERVE_BACKUP) != 0)
+ saver->priv->keep_backup = FALSE;
else
- saver->keep_backup = gedit_prefs_manager_get_create_backup_copy ();
+ saver->priv->keep_backup = gedit_prefs_manager_get_create_backup_copy ();
+
+ saver->priv->old_mtime = *old_mtime;
+
+ /* saving start */
+ gedit_document_saver_saving (saver, FALSE, NULL);
+
+ g_timeout_add_full (G_PRIORITY_HIGH,
+ 0,
+ (GSourceFunc) save_remote_file_real,
+ saver,
+ NULL);
+}
- GEDIT_DOCUMENT_SAVER_GET_CLASS (saver)->save (saver, old_mtime);
+void
+gedit_document_saver_saving (GeditDocumentSaver *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)
+ gedit_debug_message (DEBUG_SAVER, "save completed");
+ else
+ gedit_debug_message (DEBUG_SAVER, "save failed");
+
+ g_object_unref (saver);
+ }
}
GeditDocument *
@@ -316,7 +987,7 @@ gedit_document_saver_get_document (GeditDocumentSaver *saver)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), NULL);
- return saver->document;
+ return saver->priv->document;
}
GFile *
@@ -324,7 +995,7 @@ gedit_document_saver_get_location (GeditDocumentSaver *saver)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), NULL);
- return g_file_dup (saver->location);
+ return g_file_dup (saver->priv->location);
}
/* Returns 0 if file size is unknown */
@@ -333,7 +1004,7 @@ gedit_document_saver_get_file_size (GeditDocumentSaver *saver)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), 0);
- return GEDIT_DOCUMENT_SAVER_GET_CLASS (saver)->get_file_size (saver);
+ return saver->priv->size;
}
goffset
@@ -341,7 +1012,7 @@ gedit_document_saver_get_bytes_written (GeditDocumentSaver *saver)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), 0);
- return GEDIT_DOCUMENT_SAVER_GET_CLASS (saver)->get_bytes_written (saver);
+ return saver->priv->bytes_written;
}
GFileInfo *
@@ -349,6 +1020,6 @@ gedit_document_saver_get_info (GeditDocumentSaver *saver)
{
g_return_val_if_fail (GEDIT_IS_DOCUMENT_SAVER (saver), NULL);
- return saver->info;
+ return saver->priv->info;
}
/* ex:ts=8:noet: */
diff --git a/gedit/gedit-document-saver.h b/gedit/gedit-document-saver.h
index 17a8ea4..9b10241 100644
--- a/gedit/gedit-document-saver.h
+++ b/gedit/gedit-document-saver.h
@@ -4,6 +4,7 @@
*
* Copyright (C) 2005 - Paolo Maggi
* Copyrhing (C) 2007 - Paolo Maggi, Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -20,13 +21,11 @@
* Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
-
+
/*
- * Modified by the gedit Team, 2005. See the AUTHORS file for a
+ * Modified by the gedit Team, 2005-2007. See the AUTHORS file for a
* list of people on the gedit Team.
* See the ChangeLog files for a list of changes.
- *
- * $Id$
*/
#ifndef __GEDIT_DOCUMENT_SAVER_H__
@@ -46,6 +45,9 @@ G_BEGIN_DECLS
#define GEDIT_IS_DOCUMENT_SAVER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT_SAVER))
#define GEDIT_DOCUMENT_SAVER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GEDIT_TYPE_DOCUMENT_SAVER, GeditDocumentSaverClass))
+/* Private structure type */
+typedef struct _GeditDocumentSaverPrivate GeditDocumentSaverPrivate;
+
/*
* Main object structure
*/
@@ -56,17 +58,7 @@ struct _GeditDocumentSaver
GObject object;
/*< private >*/
- GFileInfo *info;
- GeditDocument *document;
- gboolean used;
-
- GFile *location;
- const GeditEncoding *encoding;
- GeditDocumentNewlineType newline_type;
-
- GeditDocumentSaveFlags flags;
-
- gboolean keep_backup;
+ GeditDocumentSaverPrivate *priv;
};
/*
@@ -79,15 +71,9 @@ struct _GeditDocumentSaverClass
GObjectClass parent_class;
/* Signals */
- void (* saving) (GeditDocumentSaver *saver,
+ void (* saving) (GeditDocumentSaver *saver,
gboolean completed,
const GError *error);
-
- /* VTable */
- void (* save) (GeditDocumentSaver *saver,
- GTimeVal *old_mtime);
- goffset (* get_file_size) (GeditDocumentSaver *saver);
- goffset (* get_bytes_written) (GeditDocumentSaver *saver);
};
/*
diff --git a/tests/document-loader.c b/tests/document-loader.c
index 9190660..e669e27 100644
--- a/tests/document-loader.c
+++ b/tests/document-loader.c
@@ -20,7 +20,7 @@
* Boston, MA 02110-1301 USA
*/
-#include "gedit-gio-document-loader.h"
+#include "gedit-document-loader.h"
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <glib.h>
diff --git a/tests/document-saver.c b/tests/document-saver.c
index 540e389..083a496 100644
--- a/tests/document-saver.c
+++ b/tests/document-saver.c
@@ -20,7 +20,7 @@
* Boston, MA 02110-1301 USA
*/
-#include "gedit-gio-document-loader.h"
+#include "gedit-document-loader.h"
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <glib.h>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]