[gedit] Merge gio document loader and saver into document loader and saver



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]