[gtksourceview/wip/loader-saver: 11/11] File saving



commit ce8f5947f55a61d44aaf340fddbfb2815c535eb4
Author: Sébastien Wilmet <swilmet gnome org>
Date:   Sun Dec 29 14:15:38 2013 +0100

    File saving
    
    Add the public class GtkSourceFile.
    Add the private class GtkSourceFileSaver, taken from gedit
    (GeditDocumentSaver) and adapted to fit the GtkSourceFile API (GIO
    conventions for async operations).
    
    See this mail thread:
    https://mail.gnome.org/archives/gedit-list/2013-December/msg00006.html
    
    And the bug report:
    https://bugzilla.gnome.org/show_bug.cgi?id=721016

 docs/reference/Makefile.am                    |    2 +
 docs/reference/gtksourceview-3.0-sections.txt |   58 ++
 docs/reference/gtksourceview-docs.xml         |    6 +
 gtksourceview/Makefile.am                     |    4 +
 gtksourceview/gtksource.h                     |    1 +
 gtksourceview/gtksourcebuffer.h               |    7 -
 gtksourceview/gtksourcebufferinputstream.h    |    2 +-
 gtksourceview/gtksourcefile.c                 |  645 +++++++++++++++++
 gtksourceview/gtksourcefile.h                 |  195 ++++++
 gtksourceview/gtksourcefilesaver.c            |  911 +++++++++++++++++++++++++
 gtksourceview/gtksourcefilesaver.h            |   88 +++
 gtksourceview/gtksourcetypes-private.h        |    1 +
 gtksourceview/gtksourcetypes.h                |    1 +
 po/POTFILES.in                                |    2 +
 tests/Makefile.am                             |    7 +
 tests/test-file-saver.c                       |  773 +++++++++++++++++++++
 16 files changed, 2695 insertions(+), 8 deletions(-)
---
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 1987d7f..eaaee88 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -22,6 +22,7 @@ CFILE_GLOB = $(top_srcdir)/gtksourceview/*.c
 IGNORE_HFILES =                                        \
        config.h                                \
        gtksourcebuffer-private.h               \
+       gtksourcebufferinputstream.h            \
        gtksourcecompletioncontainer.h          \
        gtksourcecompletionmodel.h              \
        gtksourcecompletion-private.h           \
@@ -31,6 +32,7 @@ IGNORE_HFILES =                                       \
        gtksourcecompletionwordsutils.h         \
        gtksourcecontextengine.h                \
        gtksourceengine.h                       \
+       gtksourcefilesaver.h                    \
        gtksourcegutter-private.h               \
        gtksourcegutterrendererlines.h          \
        gtksourcegutterrenderermarks.h          \
diff --git a/docs/reference/gtksourceview-3.0-sections.txt b/docs/reference/gtksourceview-3.0-sections.txt
index 0618e46..2418c41 100644
--- a/docs/reference/gtksourceview-3.0-sections.txt
+++ b/docs/reference/gtksourceview-3.0-sections.txt
@@ -207,6 +207,64 @@ gtk_source_completion_words_get_type
 </SECTION>
 
 <SECTION>
+<FILE>encoding</FILE>
+<TITLE>GtkSourceEncoding</TITLE>
+GtkSourceEncoding
+<SUBSECTION>
+gtk_source_encoding_get_from_charset
+gtk_source_encoding_get_from_index
+gtk_source_encoding_to_string
+gtk_source_encoding_get_name
+gtk_source_encoding_get_charset
+gtk_source_encoding_get_utf8
+gtk_source_encoding_get_current
+gtk_source_encoding_copy
+gtk_source_encoding_free
+<SUBSECTION Standard>
+GTK_SOURCE_TYPE_ENCODING
+gtk_source_encoding_get_type
+</SECTION>
+
+<SECTION>
+<FILE>file</FILE>
+<TITLE>GtkSourceFile</TITLE>
+GtkSourceFile
+GtkSourceNewlineType
+GtkSourceCompressionType
+GtkSourceFileSaveFlags
+GtkSourceMountOperationFactory
+GTK_SOURCE_FILE_ERROR
+<SUBSECTION>
+gtk_source_file_new
+gtk_source_file_get_location
+gtk_source_file_get_buffer
+gtk_source_file_get_encoding
+gtk_source_file_set_encoding
+gtk_source_file_get_newline_type
+gtk_source_file_set_newline_type
+gtk_source_file_get_compression_type
+gtk_source_file_set_compression_type
+gtk_source_file_get_ensure_trailing_newline
+gtk_source_file_set_ensure_trailing_newline
+gtk_source_file_set_mount_operation_factory
+gtk_source_file_load_async
+gtk_source_file_load_finish
+gtk_source_file_save_async
+gtk_source_file_save_finish
+<SUBSECTION Standard>
+GTK_SOURCE_FILE
+GTK_SOURCE_FILE_CLASS
+GTK_SOURCE_FILE_GET_CLASS
+GTK_SOURCE_IS_FILE
+GTK_SOURCE_IS_FILE_CLASS
+GTK_SOURCE_TYPE_FILE
+GtkSourceFileClass
+GtkSourceFilePrivate
+gtk_source_file_error_quark
+gtk_source_file_get_type
+</SECTION>
+
+<SECTION>
 <FILE>gutter</FILE>
 <TITLE>GtkSourceGutter</TITLE>
 GtkSourceGutter
diff --git a/docs/reference/gtksourceview-docs.xml b/docs/reference/gtksourceview-docs.xml
index 658ada7..6dc1d31 100644
--- a/docs/reference/gtksourceview-docs.xml
+++ b/docs/reference/gtksourceview-docs.xml
@@ -24,6 +24,8 @@
     <xi:include href="xml/completionproposal.xml"/>
     <xi:include href="xml/completionprovider.xml"/>
     <xi:include href="xml/completionwords.xml"/>
+    <xi:include href="xml/encoding.xml"/>
+    <xi:include href="xml/file.xml"/>
     <xi:include href="xml/gutter.xml"/>
     <xi:include href="xml/gutterrenderer.xml"/>
     <xi:include href="xml/gutterrendererpixbuf.xml"/>
@@ -80,6 +82,10 @@
     <title>Index of new symbols in 3.12</title>
     <xi:include href="xml/api-index-3.12.xml"><xi:fallback /></xi:include>
   </index>
+  <index id="api-index-3-14" role="3.14">
+    <title>Index of new symbols in 3.14</title>
+    <xi:include href="xml/api-index-3.14.xml"><xi:fallback /></xi:include>
+  </index>
 
   <xi:include href="xml/annotation-glossary.xml"><xi:fallback /></xi:include>
 
diff --git a/gtksourceview/Makefile.am b/gtksourceview/Makefile.am
index a3c6c1a..dd3631e 100644
--- a/gtksourceview/Makefile.am
+++ b/gtksourceview/Makefile.am
@@ -31,6 +31,7 @@ libgtksourceview_headers =                    \
        gtksourcecompletionproposal.h           \
        gtksourcecompletionprovider.h           \
        gtksourceencoding.h                     \
+       gtksourcefile.h                         \
        gtksourcegutter.h                       \
        gtksourcegutterrenderer.h               \
        gtksourcegutterrendererpixbuf.h         \
@@ -58,6 +59,7 @@ libgtksourceview_private_headers = \
        gtksourcecompletion-private.h           \
        gtksourcecontextengine.h                \
        gtksourceengine.h                       \
+       gtksourcefilesaver.h                    \
        gtksourcegutter-private.h               \
        gtksourcegutterrendererlines.h          \
        gtksourcegutterrenderermarks.h          \
@@ -79,6 +81,7 @@ libgtksourceview_private_c_files = \
        gtksourcecompletionmodel.c      \
        gtksourcecontextengine.c        \
        gtksourceengine.c               \
+       gtksourcefilesaver.c            \
        gtksourcegutterrendererlines.c  \
        gtksourcegutterrenderermarks.c  \
        gtksourcelanguage-parser-1.c    \
@@ -101,6 +104,7 @@ libgtksourceview_c_files = \
        gtksourcecompletionproposal.c   \
        gtksourcecompletionprovider.c   \
        gtksourceencoding.c             \
+       gtksourcefile.c                 \
        gtksourcegutter.c               \
        gtksourcegutterrenderer.c       \
        gtksourcegutterrendererpixbuf.c \
diff --git a/gtksourceview/gtksource.h b/gtksourceview/gtksource.h
index b2b3add..049cd12 100644
--- a/gtksourceview/gtksource.h
+++ b/gtksourceview/gtksource.h
@@ -29,6 +29,7 @@
 #include <gtksourceview/gtksourcecompletionproposal.h>
 #include <gtksourceview/gtksourcecompletionprovider.h>
 #include <gtksourceview/gtksourceencoding.h>
+#include <gtksourceview/gtksourcefile.h>
 #include <gtksourceview/gtksourcegutter.h>
 #include <gtksourceview/gtksourcegutterrenderer.h>
 #include <gtksourceview/gtksourcegutterrenderertext.h>
diff --git a/gtksourceview/gtksourcebuffer.h b/gtksourceview/gtksourcebuffer.h
index 17c7a63..f04facb 100644
--- a/gtksourceview/gtksourcebuffer.h
+++ b/gtksourceview/gtksourcebuffer.h
@@ -73,13 +73,6 @@ typedef enum
        GTK_SOURCE_CHANGE_CASE_TITLE
 } GtkSourceChangeCaseType;
 
-typedef enum
-{
-       GTK_SOURCE_NEWLINE_TYPE_LF,
-       GTK_SOURCE_NEWLINE_TYPE_CR,
-       GTK_SOURCE_NEWLINE_TYPE_CR_LF
-} GtkSourceNewlineType;
-
 struct _GtkSourceBuffer
 {
        GtkTextBuffer parent_instance;
diff --git a/gtksourceview/gtksourcebufferinputstream.h b/gtksourceview/gtksourcebufferinputstream.h
index 1134f10..f67e831 100644
--- a/gtksourceview/gtksourcebufferinputstream.h
+++ b/gtksourceview/gtksourcebufferinputstream.h
@@ -26,7 +26,7 @@
 #include <gio/gio.h>
 #include <gtk/gtk.h>
 #include "gtksourcetypes-private.h"
-#include "gtksourcebuffer.h"
+#include "gtksourcefile.h"
 
 G_BEGIN_DECLS
 
diff --git a/gtksourceview/gtksourcefile.c b/gtksourceview/gtksourcefile.c
new file mode 100644
index 0000000..1470b3a
--- /dev/null
+++ b/gtksourceview/gtksourcefile.c
@@ -0,0 +1,645 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefile.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "gtksourcefile.h"
+#include "gtksourcefilesaver.h"
+#include "gtksourcebuffer.h"
+#include "gtksourceencoding.h"
+#include "gtksourceview-typebuiltins.h"
+
+enum
+{
+       PROP_0,
+       PROP_LOCATION,
+       PROP_BUFFER,
+       PROP_ENCODING,
+       PROP_NEWLINE_TYPE,
+       PROP_COMPRESSION_TYPE,
+       PROP_ENSURE_TRAILING_NEWLINE
+};
+
+struct _GtkSourceFilePrivate
+{
+       GFile *location;
+       GtkSourceBuffer *buffer;
+       const GtkSourceEncoding *encoding;
+       GtkSourceNewlineType newline_type;
+       GtkSourceCompressionType compression_type;
+
+       GtkSourceMountOperationFactory mount_operation_factory;
+       gpointer mount_operation_userdata;
+
+       GtkSourceFileSaver *saver;
+
+       /* The time when the file was last modified, from our point of view. The
+        * file may be externally modified.
+        */
+       GTimeVal mtime;
+
+       guint mtime_set : 1;
+       guint ensure_trailing_newline : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFile, gtk_source_file, G_TYPE_OBJECT)
+
+static void
+gtk_source_file_get_property (GObject    *object,
+                             guint       prop_id,
+                             GValue     *value,
+                             GParamSpec *pspec)
+{
+       GtkSourceFile *file;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE (object));
+
+       file = GTK_SOURCE_FILE (object);
+
+       switch (prop_id)
+       {
+               case PROP_LOCATION:
+                       g_value_set_object (value, file->priv->location);
+                       break;
+
+               case PROP_BUFFER:
+                       g_value_set_object (value, file->priv->buffer);
+                       break;
+
+               case PROP_ENCODING:
+                       g_value_set_boxed (value, file->priv->encoding);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       g_value_set_enum (value, file->priv->newline_type);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       g_value_set_enum (value, file->priv->compression_type);
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, file->priv->ensure_trailing_newline);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_set_property (GObject      *object,
+                             guint         prop_id,
+                             const GValue *value,
+                             GParamSpec   *pspec)
+{
+       GtkSourceFile *file;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE (object));
+
+       file = GTK_SOURCE_FILE (object);
+
+       switch (prop_id)
+       {
+               case PROP_LOCATION:
+                       g_assert (file->priv->location == NULL);
+                       file->priv->location = g_value_dup_object (value);
+                       break;
+
+               case PROP_BUFFER:
+                       g_assert (file->priv->buffer == NULL);
+                       file->priv->buffer = g_value_dup_object (value);
+                       break;
+
+               case PROP_ENCODING:
+                       gtk_source_file_set_encoding (file, g_value_get_boxed (value));
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       gtk_source_file_set_newline_type (file, g_value_get_enum (value));
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       gtk_source_file_set_compression_type (file, g_value_get_enum (value));
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       gtk_source_file_set_ensure_trailing_newline (file, g_value_get_boolean (value));
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_dispose (GObject *object)
+{
+       GtkSourceFile *file = GTK_SOURCE_FILE (object);
+
+       g_clear_object (&file->priv->location);
+       g_clear_object (&file->priv->buffer);
+
+       G_OBJECT_CLASS (gtk_source_file_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_file_finalize (GObject *object)
+{
+
+       G_OBJECT_CLASS (gtk_source_file_parent_class)->finalize (object);
+}
+
+static void
+gtk_source_file_class_init (GtkSourceFileClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->get_property = gtk_source_file_get_property;
+       object_class->set_property = gtk_source_file_set_property;
+       object_class->dispose = gtk_source_file_dispose;
+       object_class->finalize = gtk_source_file_finalize;
+
+       g_object_class_install_property (object_class, PROP_LOCATION,
+                                        g_param_spec_object ("location",
+                                                             "Location",
+                                                             "The file's location",
+                                                             G_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class, PROP_BUFFER,
+                                        g_param_spec_object ("buffer",
+                                                             "Buffer",
+                                                             "The associated GtkSourceBuffer",
+                                                             GTK_SOURCE_TYPE_BUFFER,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class, PROP_ENCODING,
+                                        g_param_spec_boxed ("encoding",
+                                                            "Encoding",
+                                                            "The GtkSourceEncoding used",
+                                                            GTK_SOURCE_TYPE_ENCODING,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class, PROP_NEWLINE_TYPE,
+                                        g_param_spec_enum ("newline-type",
+                                                           "Newline type",
+                                                           "The type of line ending",
+                                                           GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                                                           GTK_SOURCE_NEWLINE_TYPE_LF,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class, PROP_COMPRESSION_TYPE,
+                                        g_param_spec_enum ("compression-type",
+                                                           "Compression type",
+                                                           "The compression type",
+                                                           GTK_SOURCE_TYPE_COMPRESSION_TYPE,
+                                                           GTK_SOURCE_COMPRESSION_TYPE_NONE,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class, PROP_ENSURE_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("ensure-trailing-newline",
+                                                              "Ensure trailing newline",
+                                                              "Ensure the file ends with a trailing newline",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE |
+                                                              G_PARAM_CONSTRUCT |
+                                                              G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtk_source_file_init (GtkSourceFile *self)
+{
+       self->priv = gtk_source_file_get_instance_private (self);
+}
+
+GQuark
+gtk_source_file_error_quark (void)
+{
+       static GQuark quark = 0;
+
+       if (G_UNLIKELY (quark == 0))
+       {
+               quark = g_quark_from_static_string ("gtk-source-file-error");
+       }
+
+       return quark;
+}
+
+/**
+ * gtk_source_file_new:
+ * @location: a #GFile.
+ * @buffer: a #GtkSourceBuffer.
+ *
+ * Returns: a new #GtkSourceFile object.
+ * Since: 3.14
+ */
+GtkSourceFile *
+gtk_source_file_new (GFile           *location,
+                    GtkSourceBuffer *buffer)
+{
+       g_return_val_if_fail (G_IS_FILE (location), NULL);
+       g_return_val_if_fail (GTK_SOURCE_IS_BUFFER (buffer), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_FILE,
+                            "location", location,
+                            "buffer", buffer,
+                            NULL);
+}
+
+/**
+ * gtk_source_file_get_location:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: (transfer full): the location. Call g_object_unref() on the return
+ * value when no longer needed.
+ * Since: 3.14
+ */
+GFile *
+gtk_source_file_get_location (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return g_object_ref (file->priv->location);
+}
+
+/**
+ * gtk_source_file_get_buffer:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: (transfer none): the buffer.
+ * Since: 3.14
+ */
+GtkSourceBuffer *
+gtk_source_file_get_buffer (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return file->priv->buffer;
+}
+
+/**
+ * gtk_source_file_get_encoding:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: the encoding.
+ * Since: 3.14
+ */
+const GtkSourceEncoding *
+gtk_source_file_get_encoding (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return file->priv->encoding;
+}
+
+/**
+ * gtk_source_file_set_encoding:
+ * @file: a #GtkSourceFile.
+ * @encoding: the new encoding.
+ *
+ * Changes the encoding used for file loading and saving. Note that the
+ * #GtkSourceBuffer has always a UTF-8 encoding.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_encoding (GtkSourceFile           *file,
+                             const GtkSourceEncoding *encoding)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->encoding != encoding)
+       {
+               file->priv->encoding = encoding;
+               g_object_notify (G_OBJECT (file), "encoding");
+       }
+}
+
+/**
+ * gtk_source_file_get_newline_type:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: the newline type.
+ * Since: 3.14
+ */
+GtkSourceNewlineType
+gtk_source_file_get_newline_type (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), GTK_SOURCE_NEWLINE_TYPE_LF);
+
+       return file->priv->newline_type;
+}
+
+/**
+ * gtk_source_file_set_newline_type:
+ * @file: a #GtkSourceFile.
+ * @newline_type: the new newline type.
+ *
+ * Changes the newline type used for the file loading and saving. It doesn't
+ * change the newline type of the #GtkSourceBuffer.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_newline_type (GtkSourceFile        *file,
+                                 GtkSourceNewlineType  newline_type)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->newline_type != newline_type)
+       {
+               file->priv->newline_type = newline_type;
+               g_object_notify (G_OBJECT (file), "newline-type");
+       }
+}
+
+/**
+ * gtk_source_file_get_compression_type:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: the compression type.
+ * Since: 3.14
+ */
+GtkSourceCompressionType
+gtk_source_file_get_compression_type (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), GTK_SOURCE_COMPRESSION_TYPE_NONE);
+
+       return file->priv->compression_type;
+}
+
+/**
+ * gtk_source_file_set_compression_type:
+ * @file: a #GtkSourceFile.
+ * @compression_type: the new compression type.
+ *
+ * Changes the compression type used for the file loading and saving. It doesn't
+ * affect the #GtkSourceBuffer.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_compression_type (GtkSourceFile            *file,
+                                     GtkSourceCompressionType  compression_type)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->compression_type != compression_type)
+       {
+               file->priv->compression_type = compression_type;
+               g_object_notify (G_OBJECT (file), "compression-type");
+       }
+}
+
+/**
+ * gtk_source_file_get_ensure_trailing_newline:
+ * @file: a #GtkSourceFile.
+ *
+ * Returns: whether the file should end with a trailing newline.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_file_get_ensure_trailing_newline (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), TRUE);
+
+       return file->priv->ensure_trailing_newline;
+}
+
+/**
+ * gtk_source_file_set_ensure_trailing_newline:
+ * @file: a #GtkSourceFile.
+ * @ensure_trailing_newline: whether the file should end with a trailing
+ * newline.
+ *
+ * If a #GtkTextBuffer contains a trailing newline, there will be one more
+ * visible line in a #GtkTextView. This is generally not what the user expects.
+ *
+ * When @ensure_trailing_newline is %TRUE, the trailing newline (if present) is
+ * removed when the file is loaded into the #GtkTextBuffer. When the buffer is
+ * saved into the file, a trailing newline is always added.
+ *
+ * On the other hand, when @ensure_trailing_newline is %FALSE, the file's
+ * contents is not modified when loaded into the buffer, and the buffer's
+ * contents is not modified when saved into the file.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_ensure_trailing_newline (GtkSourceFile *file,
+                                            gboolean       ensure_trailing_newline)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       ensure_trailing_newline = ensure_trailing_newline != FALSE;
+
+       if (file->priv->ensure_trailing_newline != ensure_trailing_newline)
+       {
+               file->priv->ensure_trailing_newline = ensure_trailing_newline;
+               g_object_notify (G_OBJECT (file), "ensure-trailing-newline");
+       }
+}
+
+/**
+ * gtk_source_file_set_mount_operation_factory:
+ * @file: a #GtkSourceFile.
+ * @callback: a #GtkSourceMountOperationFactory to call when a #GMountOperation
+ * is needed.
+ * @user_data: the data to pass to the @callback function.
+ *
+ * Sets a #GtkSourceMountOperationFactory function that will be called when a
+ * #GMountOperation must be created. This is useful for creating a
+ * #GtkMountOperation with the parent #GtkWindow.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_set_mount_operation_factory (GtkSourceFile                  *file,
+                                            GtkSourceMountOperationFactory  callback,
+                                            gpointer                        user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       file->priv->mount_operation_factory = callback;
+       file->priv->mount_operation_userdata = user_data;
+}
+
+GMountOperation *
+_gtk_source_file_create_mount_operation (GtkSourceFile *file)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return file->priv->mount_operation_factory != NULL ?
+               file->priv->mount_operation_factory (file->priv->mount_operation_userdata) :
+               g_mount_operation_new ();
+}
+
+/* FIXME add load flags parameter for future expension? */
+void
+gtk_source_file_load_async (GtkSourceFile         *file,
+                           gint                   io_priority,
+                           GCancellable          *cancellable,
+                           GFileProgressCallback  progress_callback,
+                           gpointer               progress_callback_data,
+                           GAsyncReadyCallback    callback,
+                           gpointer               user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+}
+
+gboolean
+gtk_source_file_load_finish (GtkSourceFile  *file,
+                            GAsyncResult   *result,
+                            GError        **error)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       return TRUE;
+}
+
+/**
+ * gtk_source_file_save_async:
+ * @file: a #GtkSourceFile.
+ * @flags: a set of #GtkSourceFileSaveFlags.
+ * @io_priority: the I/O priority of the request. E.g. %G_PRIORITY_LOW,
+ *   %G_PRIORITY_DEFAULT or %G_PRIORITY_HIGH.
+ * @cancellable: (allow-none): optional #GCancellable object, %NULL to ignore.
+ * @progress_callback: (allow-none): function to call back with progress
+ *   information, or %NULL if progress information is not needed.
+ * @progress_callback_data: user data to pass to @progress_callback.
+ * @callback: (scope async): a #GAsyncReadyCallback to call when the request is
+ *   satisfied.
+ * @user_data: user data to pass to @callback.
+ *
+ * Saves asynchronously the #GtkSourceFile:buffer into the
+ * #GtkSourceFile:location. See the #GAsyncResult documentation to know how to
+ * use this function.
+ *
+ * Since: 3.14
+ */
+void
+gtk_source_file_save_async (GtkSourceFile          *file,
+                           GtkSourceFileSaveFlags  flags,
+                           gint                    io_priority,
+                           GCancellable           *cancellable,
+                           GFileProgressCallback   progress_callback,
+                           gpointer                progress_callback_data,
+                           GAsyncReadyCallback     callback,
+                           gpointer                user_data)
+{
+       GTimeVal *mtime;
+
+       g_return_if_fail (GTK_SOURCE_IS_FILE (file));
+
+       if (file->priv->saver != NULL)
+       {
+               g_task_report_new_error (file,
+                                        callback,
+                                        user_data,
+                                        file,
+                                        G_IO_ERROR,
+                                        G_IO_ERROR_PENDING,
+                                        "Another save operation is already running on the file \"%s\".",
+                                        g_file_get_parse_name (file->priv->location));
+
+               return;
+       }
+
+       if (file->priv->mtime_set)
+       {
+               mtime = &file->priv->mtime;
+       }
+       else
+       {
+               mtime = NULL;
+               flags |= GTK_SOURCE_FILE_SAVE_IGNORE_MTIME;
+       }
+
+       file->priv->saver = gtk_source_file_saver_new (file,
+                                                      file->priv->encoding,
+                                                      file->priv->newline_type,
+                                                      file->priv->compression_type,
+                                                      file->priv->ensure_trailing_newline,
+                                                      flags);
+
+       gtk_source_file_saver_save_async (file->priv->saver,
+                                         mtime,
+                                         io_priority,
+                                         cancellable,
+                                         progress_callback,
+                                         progress_callback_data,
+                                         callback,
+                                         user_data);
+}
+
+/**
+ * gtk_source_file_save_finish:
+ * @file: a #GtkSourceFile.
+ * @result: a #GAsyncResult.
+ * @error: a #GError, or %NULL.
+ *
+ * Finishes a file saving started with gtk_source_file_save_async().
+ *
+ * Returns: whether the file was saved successfully.
+ * Since: 3.14
+ */
+gboolean
+gtk_source_file_save_finish (GtkSourceFile  *file,
+                            GAsyncResult   *result,
+                            GError        **error)
+{
+       gboolean ok;
+
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+       if (g_async_result_is_tagged (result, file))
+       {
+               return g_task_propagate_boolean (G_TASK (result), error);
+       }
+
+       g_return_val_if_fail (file->priv->saver != NULL, FALSE);
+
+       ok = gtk_source_file_saver_save_finish (file->priv->saver, result, error);
+
+       if (ok)
+       {
+               GFileInfo *info = gtk_source_file_saver_get_info (file->priv->saver);
+
+               g_file_info_get_modification_time (info, &file->priv->mtime);
+               file->priv->mtime_set = TRUE;
+       }
+
+       g_clear_object (&file->priv->saver);
+
+       return ok;
+}
diff --git a/gtksourceview/gtksourcefile.h b/gtksourceview/gtksourcefile.h
new file mode 100644
index 0000000..7ebddd0
--- /dev/null
+++ b/gtksourceview/gtksourcefile.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefile.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2013 - Sébastien Wilmet <swilmet gnome org>
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_FILE_H__
+#define __GTK_SOURCE_FILE_H__
+
+#include <gio/gio.h>
+#include <gtksourceview/gtksourcetypes.h>
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_FILE             (gtk_source_file_get_type ())
+#define GTK_SOURCE_FILE(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_SOURCE_TYPE_FILE, 
GtkSourceFile))
+#define GTK_SOURCE_FILE_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_SOURCE_TYPE_FILE, 
GtkSourceFileClass))
+#define GTK_SOURCE_IS_FILE(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_SOURCE_TYPE_FILE))
+#define GTK_SOURCE_IS_FILE_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_SOURCE_TYPE_FILE))
+#define GTK_SOURCE_FILE_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_SOURCE_TYPE_FILE, 
GtkSourceFileClass))
+
+typedef struct _GtkSourceFileClass    GtkSourceFileClass;
+typedef struct _GtkSourceFilePrivate  GtkSourceFilePrivate;
+
+/**
+ * GtkSourceNewlineType:
+ * @GTK_SOURCE_NEWLINE_TYPE_LF: line feed, used on UNIX.
+ * @GTK_SOURCE_NEWLINE_TYPE_CR: carriage return, used on Mac.
+ * @GTK_SOURCE_NEWLINE_TYPE_CR_LF: carriage return followed by a line feed, used
+ *   on Windows.
+ *
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_NEWLINE_TYPE_LF,
+       GTK_SOURCE_NEWLINE_TYPE_CR,
+       GTK_SOURCE_NEWLINE_TYPE_CR_LF
+} GtkSourceNewlineType;
+
+/* NOTE: when adding a new compression type, make sure to update:
+ *   1) The buffer loader to support it
+ *   2) gedit_document_compression_type_for_display
+ * TODO update this comment when finished
+ */
+
+/**
+ * GtkSourceCompressionType:
+ * @GTK_SOURCE_COMPRESSION_TYPE_NONE: save file in plain text.
+ * @GTK_SOURCE_COMPRESSION_TYPE_GZIP: save file using gzip compression.
+ *
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_COMPRESSION_TYPE_NONE,
+       GTK_SOURCE_COMPRESSION_TYPE_GZIP
+} GtkSourceCompressionType;
+
+/**
+ * GtkSourceFileSaveFlags:
+ * @GTK_SOURCE_FILE_SAVE_IGNORE_MTIME: save file despite external modifications.
+ * @GTK_SOURCE_FILE_SAVE_CREATE_BACKUP: create a backup before saving the file.
+ * @GTK_SOURCE_FILE_SAVE_IGNORE_INVALID_CHARS: do not save invalid characters.
+ *
+ * FIXME ignore invalid chars is not yet used in gsv
+ *
+ * Since: 3.14
+ */
+typedef enum
+{
+       GTK_SOURCE_FILE_SAVE_IGNORE_MTIME               = 1 << 0,
+       GTK_SOURCE_FILE_SAVE_CREATE_BACKUP              = 1 << 1,
+       GTK_SOURCE_FILE_SAVE_IGNORE_INVALID_CHARS       = 1 << 2
+} GtkSourceFileSaveFlags;
+
+#define GTK_SOURCE_FILE_ERROR gtk_source_file_error_quark ()
+
+/* TODO check if these errors are used, and document them. */
+enum
+{
+       GTK_SOURCE_FILE_ERROR_EXTERNALLY_MODIFIED,
+       GTK_SOURCE_FILE_ERROR_CANT_CREATE_BACKUP,
+       GTK_SOURCE_FILE_ERROR_TOO_BIG,
+       GTK_SOURCE_FILE_ERROR_ENCODING_AUTO_DETECTION_FAILED,
+       GTK_SOURCE_FILE_ERROR_CONVERSION_FALLBACK
+};
+
+/**
+ * GtkSourceMountOperationFactory:
+ * @userdata: user data
+ *
+ * Type definition for a function that will be called to create a
+ * #GMountOperation. This is useful for creating a #GtkMountOperation.
+ *
+ * Since: 3.14
+ */
+typedef GMountOperation *(*GtkSourceMountOperationFactory)(gpointer userdata);
+
+struct _GtkSourceFile
+{
+       GObject parent;
+
+       GtkSourceFilePrivate *priv;
+};
+
+struct _GtkSourceFileClass
+{
+       GObjectClass parent_class;
+};
+
+GType                   gtk_source_file_get_type               (void) G_GNUC_CONST;
+
+GQuark                  gtk_source_file_error_quark            (void);
+
+GtkSourceFile          *gtk_source_file_new                    (GFile                    *location,
+                                                                GtkSourceBuffer          *buffer);
+
+GFile                  *gtk_source_file_get_location           (GtkSourceFile            *file);
+
+GtkSourceBuffer                *gtk_source_file_get_buffer             (GtkSourceFile            *file);
+
+const GtkSourceEncoding        *gtk_source_file_get_encoding           (GtkSourceFile            *file);
+
+void                    gtk_source_file_set_encoding           (GtkSourceFile            *file,
+                                                                const GtkSourceEncoding  *encoding);
+
+GtkSourceNewlineType    gtk_source_file_get_newline_type       (GtkSourceFile            *file);
+
+void                    gtk_source_file_set_newline_type       (GtkSourceFile            *file,
+                                                                GtkSourceNewlineType      newline_type);
+
+GtkSourceCompressionType gtk_source_file_get_compression_type  (GtkSourceFile            *file);
+
+void                    gtk_source_file_set_compression_type   (GtkSourceFile            *file,
+                                                                GtkSourceCompressionType  compression_type);
+
+gboolean                gtk_source_file_get_ensure_trailing_newline
+                                                               (GtkSourceFile            *file);
+
+void                    gtk_source_file_set_ensure_trailing_newline
+                                                               (GtkSourceFile            *file,
+                                                                gboolean                  
ensure_trailing_newline);
+
+void                    gtk_source_file_set_mount_operation_factory
+                                                               (GtkSourceFile                  *file,
+                                                                GtkSourceMountOperationFactory  callback,
+                                                                gpointer                        user_data);
+
+void                    gtk_source_file_load_async             (GtkSourceFile            *file,
+                                                                gint                      io_priority,
+                                                                GCancellable             *cancellable,
+                                                                GFileProgressCallback     progress_callback,
+                                                                gpointer                  
progress_callback_data,
+                                                                GAsyncReadyCallback       callback,
+                                                                gpointer                  user_data);
+
+gboolean                gtk_source_file_load_finish            (GtkSourceFile            *file,
+                                                                GAsyncResult             *result,
+                                                                GError                  **error);
+
+void                    gtk_source_file_save_async             (GtkSourceFile            *file,
+                                                                GtkSourceFileSaveFlags    flags,
+                                                                gint                      io_priority,
+                                                                GCancellable             *cancellable,
+                                                                GFileProgressCallback     progress_callback,
+                                                                gpointer                  
progress_callback_data,
+                                                                GAsyncReadyCallback       callback,
+                                                                gpointer                  user_data);
+
+gboolean                gtk_source_file_save_finish            (GtkSourceFile            *file,
+                                                                GAsyncResult             *result,
+                                                                GError                  **error);
+
+G_GNUC_INTERNAL
+GMountOperation                *_gtk_source_file_create_mount_operation (GtkSourceFile           *file);
+
+G_END_DECLS
+
+#endif /* __GTK_SOURCE_FILE_H__ */
diff --git a/gtksourceview/gtksourcefilesaver.c b/gtksourceview/gtksourcefilesaver.c
new file mode 100644
index 0000000..4d72260
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.c
@@ -0,0 +1,911 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefilesaver.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005-2007 - Paolo Borelli and Paolo Maggi
+ * Copyright (C) 2007 - Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2013 - Sébastien Wilmet
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+/* The code has been written initially in gedit (GeditDocumentSaver).
+ * It uses a GtkSourceBufferInputStream as input, create converter(s) if needed
+ * for the encoding and the compression, and write the contents to a
+ * GOutputStream (the file).
+ * The FileSaver has properties for all the settings relevant for saving a file.
+ * It can not rely on a GtkSourceFile, because its properties can change during
+ * the saving.
+ */
+
+#include "gtksourcefilesaver.h"
+#include "gtksourcebufferinputstream.h"
+#include "gtksourceencoding.h"
+#include "gtksourceview-typebuiltins.h"
+
+#if 0
+#define DEBUG(x) (x)
+#else
+#define DEBUG(x)
+#endif
+
+#define WRITE_CHUNK_SIZE 8192
+
+#define QUERY_ATTRIBUTES G_FILE_ATTRIBUTE_TIME_MODIFIED
+
+enum
+{
+       PROP_0,
+       PROP_FILE,
+       PROP_ENCODING,
+       PROP_NEWLINE_TYPE,
+       PROP_COMPRESSION_TYPE,
+       PROP_ENSURE_TRAILING_NEWLINE,
+       PROP_FLAGS
+};
+
+struct _GtkSourceFileSaverPrivate
+{
+       GtkSourceFile *file;
+       const GtkSourceEncoding *encoding;
+       GtkSourceNewlineType newline_type;
+       GtkSourceCompressionType compression_type;
+       GtkSourceFileSaveFlags flags;
+
+       GTimeVal old_mtime;
+
+       GTask *task;
+
+       /* This field is used when cancelling the output stream: an error occurs
+        * and is stored in this field, the output stream is cancelled
+        * asynchronously, and then the error is reported to the task.
+        */
+       GError *error;
+
+       goffset total_size;
+       GFileProgressCallback progress_cb;
+       gpointer progress_cb_data;
+
+       gchar chunk_buffer[WRITE_CHUNK_SIZE];
+       gssize chunk_bytes_read;
+       gssize chunk_bytes_written;
+
+       /* The output_stream contains the required converter(s) for the encoding
+        * and the compression type. The input_stream is the
+        * GtkSourceBufferInputStream (thus in UTF-8, without compression).
+        */
+       GOutputStream *output_stream;
+       GInputStream *input_stream;
+
+       GFileInfo *info;
+
+       guint ensure_trailing_newline : 1;
+       guint tried_mount : 1;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (GtkSourceFileSaver, gtk_source_file_saver, G_TYPE_OBJECT)
+
+static void read_file_chunk (GtkSourceFileSaver *saver);
+static void write_file_chunk (GtkSourceFileSaver *saver);
+static void check_externally_modified (GtkSourceFileSaver *saver);
+static void recover_not_mounted (GtkSourceFileSaver *saver);
+
+static void
+gtk_source_file_saver_set_property (GObject      *object,
+                                   guint         prop_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       switch (prop_id)
+       {
+               case PROP_FILE:
+                       g_assert (saver->priv->file == NULL);
+                       saver->priv->file = g_value_get_object (value);
+                       break;
+
+               case PROP_ENCODING:
+                       g_assert (saver->priv->encoding == NULL);
+                       saver->priv->encoding = g_value_get_boxed (value);
+                       if (saver->priv->encoding == NULL)
+                       {
+                               saver->priv->encoding = gtk_source_encoding_get_utf8 ();
+                       }
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       saver->priv->newline_type = g_value_get_enum (value);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       saver->priv->compression_type = g_value_get_enum (value);
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       saver->priv->ensure_trailing_newline = g_value_get_boolean (value);
+                       break;
+
+               case PROP_FLAGS:
+                       saver->priv->flags = g_value_get_flags (value);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_saver_get_property (GObject    *object,
+                                  guint       prop_id,
+                                  GValue     *value,
+                                  GParamSpec *pspec)
+{
+       GtkSourceFileSaver *saver = GTK_SOURCE_FILE_SAVER (object);
+
+       switch (prop_id)
+       {
+               case PROP_FILE:
+                       g_value_set_object (value, saver->priv->file);
+                       break;
+
+               case PROP_ENCODING:
+                       g_value_set_boxed (value, saver->priv->encoding);
+                       break;
+
+               case PROP_NEWLINE_TYPE:
+                       g_value_set_enum (value, saver->priv->newline_type);
+                       break;
+
+               case PROP_COMPRESSION_TYPE:
+                       g_value_set_enum (value, saver->priv->compression_type);
+                       break;
+
+               case PROP_ENSURE_TRAILING_NEWLINE:
+                       g_value_set_boolean (value, saver->priv->ensure_trailing_newline);
+                       break;
+
+               case PROP_FLAGS:
+                       g_value_set_flags (value, saver->priv->flags);
+                       break;
+
+               default:
+                       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                       break;
+       }
+}
+
+static void
+gtk_source_file_saver_dispose (GObject *object)
+{
+       GtkSourceFileSaverPrivate *priv = GTK_SOURCE_FILE_SAVER (object)->priv;
+
+       g_clear_object (&priv->output_stream);
+       g_clear_object (&priv->input_stream);
+       g_clear_object (&priv->task);
+       g_clear_object (&priv->info);
+       g_clear_error (&priv->error);
+
+       priv->file = NULL;
+
+       G_OBJECT_CLASS (gtk_source_file_saver_parent_class)->dispose (object);
+}
+
+static void
+gtk_source_file_saver_class_init (GtkSourceFileSaverClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->dispose = gtk_source_file_saver_dispose;
+       object_class->set_property = gtk_source_file_saver_set_property;
+       object_class->get_property = gtk_source_file_saver_get_property;
+
+       g_object_class_install_property (object_class,
+                                        PROP_FILE,
+                                        g_param_spec_object ("file",
+                                                             "File",
+                                                             "The associated GtkSourceFile",
+                                                             GTK_SOURCE_TYPE_FILE,
+                                                             G_PARAM_READWRITE |
+                                                             G_PARAM_CONSTRUCT_ONLY |
+                                                             G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ENCODING,
+                                        g_param_spec_boxed ("encoding",
+                                                            "Encoding",
+                                                            "The encoding of the saved file",
+                                                            GTK_SOURCE_TYPE_ENCODING,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT_ONLY |
+                                                            G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_NEWLINE_TYPE,
+                                        g_param_spec_enum ("newline-type",
+                                                           "Newline type",
+                                                           "The type of line ending",
+                                                           GTK_SOURCE_TYPE_NEWLINE_TYPE,
+                                                           GTK_SOURCE_NEWLINE_TYPE_LF,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT_ONLY |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_COMPRESSION_TYPE,
+                                        g_param_spec_enum ("compression-type",
+                                                           "Compression type",
+                                                           "The compression type",
+                                                           GTK_SOURCE_TYPE_COMPRESSION_TYPE,
+                                                           GTK_SOURCE_COMPRESSION_TYPE_NONE,
+                                                           G_PARAM_READWRITE |
+                                                           G_PARAM_CONSTRUCT_ONLY |
+                                                           G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_ENSURE_TRAILING_NEWLINE,
+                                        g_param_spec_boolean ("ensure-trailing-newline",
+                                                              "Ensure Trailing Newline",
+                                                              "Ensure the buffer ends with a trailing 
newline",
+                                                              TRUE,
+                                                              G_PARAM_READWRITE |
+                                                              G_PARAM_CONSTRUCT_ONLY |
+                                                              G_PARAM_STATIC_STRINGS));
+
+       g_object_class_install_property (object_class,
+                                        PROP_FLAGS,
+                                        g_param_spec_flags ("flags",
+                                                            "Flags",
+                                                            "The flags for the saving operation",
+                                                            GTK_SOURCE_TYPE_FILE_SAVE_FLAGS,
+                                                            0,
+                                                            G_PARAM_READWRITE |
+                                                            G_PARAM_CONSTRUCT_ONLY |
+                                                            G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gtk_source_file_saver_init (GtkSourceFileSaver *saver)
+{
+       saver->priv = gtk_source_file_saver_get_instance_private (saver);
+}
+
+/* BEGIN NOTE:
+ *
+ * This fixes an issue in GOutputStream that applies the atomic replace save
+ * strategy. The stream moves the written file to the original file when the
+ * stream is closed. However, there is no way currently to tell the stream that
+ * the save should be aborted (there could be a conversion error). The patch
+ * explicitly closes the output stream in all these cases with a GCancellable in
+ * the cancelled state, causing the output stream to close, but not move the
+ * file. This makes use of an implementation detail in the local file stream
+ * and should be properly fixed by adding the appropriate API in GIO. Until
+ * then, at least we prevent data corruption for now.
+ *
+ * Relevant bug reports:
+ *
+ * Bug 615110 - write file ignore encoding errors (gedit)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=615110
+ *
+ * Bug 602412 - g_file_replace does not restore original file when there is
+ *              errors while writing (glib/gio)
+ * https://bugzilla.gnome.org/show_bug.cgi?id=602412
+ */
+static void
+cancel_output_stream_ready_cb (GOutputStream      *output_stream,
+                              GAsyncResult       *result,
+                              GtkSourceFileSaver *saver)
+{
+       g_output_stream_close_finish (output_stream, result, NULL);
+
+       if (saver->priv->error != NULL)
+       {
+               GError *error = saver->priv->error;
+               saver->priv->error = NULL;
+               g_task_return_error (saver->priv->task, error);
+       }
+       else
+       {
+               g_task_return_boolean (saver->priv->task, FALSE);
+       }
+}
+
+static void
+cancel_output_stream (GtkSourceFileSaver *saver)
+{
+       GCancellable *cancellable;
+
+       DEBUG ({
+              g_print ("Cancel output stream\n");
+       });
+
+       cancellable = g_cancellable_new ();
+       g_cancellable_cancel (cancellable);
+
+       g_output_stream_close_async (saver->priv->output_stream,
+                                    g_task_get_priority (saver->priv->task),
+                                    cancellable,
+                                    (GAsyncReadyCallback) cancel_output_stream_ready_cb,
+                                    saver);
+
+       g_object_unref (cancellable);
+}
+
+/*
+ * END NOTE
+ */
+
+static void
+query_info_cb (GFile              *location,
+              GAsyncResult       *result,
+              GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("Finished query info on file\n");
+       });
+
+       g_clear_object (&saver->priv->info);
+       saver->priv->info = g_file_query_info_finish (location, result, &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Query info failed: %s\n", error->message);
+               });
+
+               g_task_return_error (saver->priv->task, error);
+               return;
+       }
+
+       g_task_return_boolean (saver->priv->task, TRUE);
+}
+
+static void
+close_output_stream_cb (GOutputStream      *output_stream,
+                       GAsyncResult       *result,
+                       GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+       GFile *location;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_output_stream_close_finish (output_stream, result, &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Closing stream error: %s\n", error->message);
+               });
+
+               g_task_return_error (saver->priv->task, 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 modification time.
+        */
+       DEBUG ({
+              g_print ("Query info on file\n");
+       });
+
+       location = gtk_source_file_get_location (saver->priv->file);
+
+       g_file_query_info_async (location,
+                                QUERY_ATTRIBUTES,
+                                G_FILE_QUERY_INFO_NONE,
+                                g_task_get_priority (saver->priv->task),
+                                g_task_get_cancellable (saver->priv->task),
+                                (GAsyncReadyCallback) query_info_cb,
+                                saver);
+}
+
+static void
+write_complete (GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("Close input stream\n");
+       });
+
+       g_input_stream_close (saver->priv->input_stream,
+                             g_task_get_cancellable (saver->priv->task),
+                             &error);
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Closing input stream error: %s\n", error->message);
+               });
+
+               saver->priv->error = error;
+               cancel_output_stream (saver);
+               return;
+       }
+
+       DEBUG ({
+              g_print ("Close output stream\n");
+       });
+
+       g_output_stream_close_async (saver->priv->output_stream,
+                                    g_task_get_priority (saver->priv->task),
+                                    g_task_get_cancellable (saver->priv->task),
+                                    (GAsyncReadyCallback) close_output_stream_cb,
+                                    saver);
+}
+
+static void
+write_file_chunk_cb (GOutputStream      *output_stream,
+                    GAsyncResult       *result,
+                    GtkSourceFileSaver *saver)
+{
+       gssize bytes_written;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       bytes_written = g_output_stream_write_finish (output_stream, result, &error);
+
+       DEBUG ({
+              g_print ("Written: %" G_GSSIZE_FORMAT "\n", bytes_written);
+       });
+
+       if (error != NULL)
+       {
+               DEBUG ({
+                      g_print ("Write error: %s\n", error->message);
+               });
+
+               saver->priv->error = error;
+               cancel_output_stream (saver);
+               return;
+       }
+
+       saver->priv->chunk_bytes_written += bytes_written;
+
+       /* Write again */
+       if (saver->priv->chunk_bytes_read != saver->priv->chunk_bytes_written)
+       {
+               write_file_chunk (saver);
+               return;
+       }
+
+       if (saver->priv->progress_cb != NULL)
+       {
+               GtkSourceBufferInputStream *buffer_stream;
+               gsize total_chars_written;
+
+               buffer_stream = GTK_SOURCE_BUFFER_INPUT_STREAM (saver->priv->input_stream);
+               total_chars_written = _gtk_source_buffer_input_stream_tell (buffer_stream);
+
+               saver->priv->progress_cb (total_chars_written,
+                                         saver->priv->total_size,
+                                         saver->priv->progress_cb_data);
+       }
+
+       read_file_chunk (saver);
+}
+
+static void
+write_file_chunk (GtkSourceFileSaver *saver)
+{
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       /* FIXME check if a thread is created each time this function is called.
+        * If so, this is a performance problem and should be fixed.
+        */
+       g_output_stream_write_async (saver->priv->output_stream,
+                                    saver->priv->chunk_buffer + saver->priv->chunk_bytes_written,
+                                    saver->priv->chunk_bytes_read - saver->priv->chunk_bytes_written,
+                                    g_task_get_priority (saver->priv->task),
+                                    g_task_get_cancellable (saver->priv->task),
+                                    (GAsyncReadyCallback) write_file_chunk_cb,
+                                    saver);
+}
+
+static void
+read_file_chunk (GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       saver->priv->chunk_bytes_written = 0;
+
+       /* We use sync methods on doc stream since it is in memory. Using async
+        * would be racy and we can end up with invalid iters.
+        */
+       saver->priv->chunk_bytes_read = g_input_stream_read (saver->priv->input_stream,
+                                                            saver->priv->chunk_buffer,
+                                                            WRITE_CHUNK_SIZE,
+                                                            g_task_get_cancellable (saver->priv->task),
+                                                            &error);
+
+       if (error != NULL)
+       {
+               saver->priv->error = error;
+               cancel_output_stream (saver);
+               return;
+       }
+
+       /* Check if we finished reading and writing. */
+       if (saver->priv->chunk_bytes_read == 0)
+       {
+               write_complete (saver);
+               return;
+       }
+
+       write_file_chunk (saver);
+}
+
+static void
+replace_file_cb (GFile              *location,
+                GAsyncResult       *result,
+                GtkSourceFileSaver *saver)
+{
+       GFileOutputStream *file_output_stream;
+       GOutputStream *output_stream;
+       GtkSourceBufferInputStream *buffer_stream;
+       GtkTextBuffer *buffer;
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       file_output_stream = g_file_replace_finish (location, result, &error);
+
+       if (error != NULL)
+       {
+               if (error->domain == G_IO_ERROR &&
+                   error->code == G_IO_ERROR_NOT_MOUNTED &&
+                   !saver->priv->tried_mount)
+               {
+                       recover_not_mounted (saver);
+                       g_error_free (error);
+                       return;
+               }
+
+               DEBUG ({
+                      g_print ("Opening file failed: %s\n", error->message);
+               });
+
+               g_task_return_error (saver->priv->task, error);
+               return;
+       }
+
+       if (saver->priv->compression_type == GTK_SOURCE_COMPRESSION_TYPE_GZIP)
+       {
+               GZlibCompressor *compressor;
+
+               DEBUG ({
+                      g_print ("Use gzip compressor\n");
+               });
+
+               compressor = g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1);
+
+               output_stream = g_converter_output_stream_new (G_OUTPUT_STREAM (file_output_stream),
+                                                              G_CONVERTER (compressor));
+
+               g_object_unref (compressor);
+               g_object_unref (file_output_stream);
+       }
+       else
+       {
+               output_stream = G_OUTPUT_STREAM (file_output_stream);
+       }
+
+       /* FIXME: manage converter error? */
+
+       DEBUG ({
+              g_print ("Encoding charset: %s\n",
+                       gtk_source_encoding_get_charset (saver->priv->encoding));
+       });
+
+       if (saver->priv->encoding != gtk_source_encoding_get_utf8 ())
+       {
+               GCharsetConverter *converter;
+
+               converter = g_charset_converter_new (gtk_source_encoding_get_charset (saver->priv->encoding),
+                                                    "UTF-8",
+                                                    NULL);
+
+               saver->priv->output_stream = g_converter_output_stream_new (output_stream,
+                                                                           G_CONVERTER (converter));
+
+               g_object_unref (converter);
+               g_object_unref (output_stream);
+       }
+       else
+       {
+               saver->priv->output_stream = G_OUTPUT_STREAM (output_stream);
+       }
+
+       buffer = GTK_TEXT_BUFFER (gtk_source_file_get_buffer (saver->priv->file));
+
+       saver->priv->input_stream = _gtk_source_buffer_input_stream_new (buffer,
+                                                                        saver->priv->newline_type,
+                                                                        
saver->priv->ensure_trailing_newline);
+
+       buffer_stream = GTK_SOURCE_BUFFER_INPUT_STREAM (saver->priv->input_stream);
+       saver->priv->total_size = _gtk_source_buffer_input_stream_get_total_size (buffer_stream);
+
+       DEBUG ({
+              g_print ("Total number of characters: %" G_GINT64_FORMAT "\n", saver->priv->total_size);
+       });
+
+       read_file_chunk (saver);
+}
+
+static void
+begin_write (GtkSourceFileSaver *saver)
+{
+       GFile *location;
+       gboolean create_backup = (saver->priv->flags & GTK_SOURCE_FILE_SAVE_CREATE_BACKUP) != 0;
+
+       DEBUG ({
+              g_print ("Start replacing file contents\n");
+              g_print ("Make backup: %s\n", make_backup ? "yes" : "no");
+       });
+
+       location = gtk_source_file_get_location (saver->priv->file);
+
+       g_file_replace_async (location,
+                             NULL,
+                             create_backup,
+                             G_FILE_CREATE_NONE,
+                             g_task_get_priority (saver->priv->task),
+                             g_task_get_cancellable (saver->priv->task),
+                             (GAsyncReadyCallback) replace_file_cb,
+                             saver);
+}
+
+static void
+mount_cb (GFile              *location,
+         GAsyncResult       *result,
+         GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       g_file_mount_enclosing_volume_finish (location, result, &error);
+
+       if (error != NULL)
+       {
+               g_task_return_error (saver->priv->task, error);
+       }
+       else if (saver->priv->flags & GTK_SOURCE_FILE_SAVE_IGNORE_MTIME)
+       {
+               begin_write (saver);
+       }
+       else
+       {
+               check_externally_modified (saver);
+       }
+}
+
+static void
+recover_not_mounted (GtkSourceFileSaver *saver)
+{
+       GFile *location;
+       GMountOperation *mount_operation = _gtk_source_file_create_mount_operation (saver->priv->file);
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       saver->priv->tried_mount = TRUE;
+
+       location = gtk_source_file_get_location (saver->priv->file);
+
+       g_file_mount_enclosing_volume (location,
+                                      G_MOUNT_MOUNT_NONE,
+                                      mount_operation,
+                                      g_task_get_cancellable (saver->priv->task),
+                                      (GAsyncReadyCallback) mount_cb,
+                                      saver);
+
+       g_object_unref (mount_operation);
+}
+
+static void
+check_externally_modified_cb (GFile              *location,
+                             GAsyncResult       *result,
+                             GtkSourceFileSaver *saver)
+{
+       GError *error = NULL;
+       GFileInfo *info;
+
+       DEBUG ({
+              g_print ("%s\n", G_STRFUNC);
+       });
+
+       if (saver->priv->flags & GTK_SOURCE_FILE_SAVE_IGNORE_MTIME)
+       {
+               g_warning ("Useless check for externally modified file.");
+       }
+
+       info = g_file_query_info_finish (location, result, &error);
+
+       if (error != NULL)
+       {
+               if (error->domain == G_IO_ERROR &&
+                   error->code == G_IO_ERROR_NOT_MOUNTED &&
+                   !saver->priv->tried_mount)
+               {
+                       recover_not_mounted (saver);
+                       g_error_free (error);
+                       return;
+               }
+
+               /* It's perfectly fine if the file doesn't exist yet. */
+               if (error->code != G_IO_ERROR_NOT_FOUND)
+               {
+                       DEBUG ({
+                              g_print ("Error getting modification: %s\n", error->message);
+                       });
+
+                       g_task_return_error (saver->priv->task, error);
+                       return;
+               }
+       }
+
+       /* Check if the mtime is greater from what we know about it (if we have it). */
+       if (info != NULL && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_TIME_MODIFIED))
+       {
+               GTimeVal mtime;
+               GTimeVal old_mtime;
+
+               g_file_info_get_modification_time (info, &mtime);
+               old_mtime = saver->priv->old_mtime;
+
+               if ((old_mtime.tv_sec > 0 || old_mtime.tv_usec > 0) &&
+                   (mtime.tv_sec != old_mtime.tv_sec || mtime.tv_usec != old_mtime.tv_usec) &&
+                   (saver->priv->flags & GTK_SOURCE_FILE_SAVE_IGNORE_MTIME) == 0)
+               {
+                       DEBUG ({
+                              g_print ("File is externally modified\n");
+                       });
+
+                       g_object_unref (info);
+
+                       g_task_return_new_error (saver->priv->task,
+                                                GTK_SOURCE_FILE_ERROR,
+                                                GTK_SOURCE_FILE_ERROR_EXTERNALLY_MODIFIED,
+                                                "Externally modified");
+                       return;
+               }
+       }
+
+       if (info != NULL)
+       {
+               g_object_unref (info);
+       }
+
+       /* Externally modified check passed, start write. */
+       begin_write (saver);
+}
+
+static void
+check_externally_modified (GtkSourceFileSaver *saver)
+{
+       GFile *location;
+
+       DEBUG ({
+              g_print ("Check externally modified\n");
+       });
+
+       location = gtk_source_file_get_location (saver->priv->file);
+
+       g_file_query_info_async (location,
+                                G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                G_FILE_QUERY_INFO_NONE,
+                                g_task_get_priority (saver->priv->task),
+                                g_task_get_cancellable (saver->priv->task),
+                                (GAsyncReadyCallback)check_externally_modified_cb,
+                                saver);
+}
+
+GtkSourceFileSaver *
+gtk_source_file_saver_new (GtkSourceFile            *file,
+                          const GtkSourceEncoding  *encoding,
+                          GtkSourceNewlineType      newline_type,
+                          GtkSourceCompressionType  compression_type,
+                          gboolean                  ensure_trailing_newline,
+                          GtkSourceFileSaveFlags    flags)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE (file), NULL);
+
+       return g_object_new (GTK_SOURCE_TYPE_FILE_SAVER,
+                            "file", file,
+                            "encoding", encoding,
+                            "newline_type", newline_type,
+                            "compression_type", compression_type,
+                            "ensure-trailing-newline", ensure_trailing_newline,
+                            "flags", flags,
+                            NULL);
+}
+
+void
+gtk_source_file_saver_save_async (GtkSourceFileSaver     *saver,
+                                 GTimeVal               *old_mtime,
+                                 gint                    io_priority,
+                                 GCancellable           *cancellable,
+                                 GFileProgressCallback   progress_callback,
+                                 gpointer                progress_callback_data,
+                                 GAsyncReadyCallback     callback,
+                                 gpointer                user_data)
+{
+       g_return_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver));
+       g_return_if_fail (saver->priv->task == NULL);
+
+       saver->priv->task = g_task_new (saver->priv->file, cancellable, callback, user_data);
+       g_task_set_priority (saver->priv->task, io_priority);
+
+       saver->priv->progress_cb = progress_callback;
+       saver->priv->progress_cb_data = progress_callback_data;
+
+       DEBUG ({
+              g_print ("Starting  save\n");
+       });
+
+       if (saver->priv->flags & GTK_SOURCE_FILE_SAVE_IGNORE_MTIME)
+       {
+               begin_write (saver);
+       }
+       else
+       {
+               g_return_if_fail (old_mtime != NULL);
+               saver->priv->old_mtime = *old_mtime;
+               check_externally_modified (saver);
+       }
+}
+
+gboolean
+gtk_source_file_saver_save_finish (GtkSourceFileSaver  *saver,
+                                  GAsyncResult        *result,
+                                  GError             **error)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), FALSE);
+       g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+       g_return_val_if_fail (g_task_is_valid (result, saver->priv->file), FALSE);
+
+       return g_task_propagate_boolean (G_TASK (result), error);
+}
+
+GFileInfo *
+gtk_source_file_saver_get_info (GtkSourceFileSaver *saver)
+{
+       g_return_val_if_fail (GTK_SOURCE_IS_FILE_SAVER (saver), NULL);
+
+       return saver->priv->info;
+}
diff --git a/gtksourceview/gtksourcefilesaver.h b/gtksourceview/gtksourcefilesaver.h
new file mode 100644
index 0000000..c50ee73
--- /dev/null
+++ b/gtksourceview/gtksourcefilesaver.h
@@ -0,0 +1,88 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* gtksourcefilesaver.h
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2005, 2007 - Paolo Maggi
+ * Copyrhing (C) 2007 - Steve Frécinaux
+ * Copyright (C) 2008 - Jesse van den Kieboom
+ * Copyright (C) 2013 - Sébastien Wilmet
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __GTK_SOURCE_FILE_SAVER_H__
+#define __GTK_SOURCE_FILE_SAVER_H__
+
+#include <gtk/gtk.h>
+#include "gtksourcetypes.h"
+#include "gtksourcetypes-private.h"
+#include "gtksourcefile.h"
+
+G_BEGIN_DECLS
+
+#define GTK_SOURCE_TYPE_FILE_SAVER              (gtk_source_file_saver_get_type())
+#define GTK_SOURCE_FILE_SAVER(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaver))
+#define GTK_SOURCE_FILE_SAVER_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaverClass))
+#define GTK_SOURCE_IS_FILE_SAVER(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), 
GTK_SOURCE_TYPE_FILE_SAVER))
+#define GTK_SOURCE_IS_FILE_SAVER_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GTK_SOURCE_TYPE_FILE_SAVER))
+#define GTK_SOURCE_FILE_SAVER_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), 
GTK_SOURCE_TYPE_FILE_SAVER, GtkSourceFileSaverClass))
+
+typedef struct _GtkSourceFileSaverClass   GtkSourceFileSaverClass;
+typedef struct _GtkSourceFileSaverPrivate GtkSourceFileSaverPrivate;
+
+struct _GtkSourceFileSaver
+{
+       GObject object;
+
+       GtkSourceFileSaverPrivate *priv;
+};
+
+struct _GtkSourceFileSaverClass
+{
+       GObjectClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType                   gtk_source_file_saver_get_type         (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkSourceFileSaver     *gtk_source_file_saver_new              (GtkSourceFile            *file,
+                                                                const GtkSourceEncoding  *encoding,
+                                                                GtkSourceNewlineType      newline_type,
+                                                                GtkSourceCompressionType  compression_type,
+                                                                gboolean                  
ensure_trailing_newline,
+                                                                GtkSourceFileSaveFlags    flags);
+
+G_GNUC_INTERNAL
+void                    gtk_source_file_saver_save_async       (GtkSourceFileSaver       *saver,
+                                                                GTimeVal                 *old_mtime,
+                                                                gint                      io_priority,
+                                                                GCancellable             *cancellable,
+                                                                GFileProgressCallback     progress_callback,
+                                                                gpointer                  
progress_callback_data,
+                                                                GAsyncReadyCallback       callback,
+                                                                gpointer                  user_data);
+
+G_GNUC_INTERNAL
+gboolean                gtk_source_file_saver_save_finish      (GtkSourceFileSaver       *saver,
+                                                                GAsyncResult             *result,
+                                                                GError                  **error);
+
+G_GNUC_INTERNAL
+GFileInfo              *gtk_source_file_saver_get_info         (GtkSourceFileSaver       *saver);
+
+G_END_DECLS
+
+#endif  /* __GTK_SOURCE_FILE_SAVER_H__  */
diff --git a/gtksourceview/gtksourcetypes-private.h b/gtksourceview/gtksourcetypes-private.h
index 4489548..cc38d97 100644
--- a/gtksourceview/gtksourcetypes-private.h
+++ b/gtksourceview/gtksourcetypes-private.h
@@ -31,6 +31,7 @@ typedef struct _GtkSourceCompletionContainer  GtkSourceCompletionContainer;
 typedef struct _GtkSourceCompletionModel       GtkSourceCompletionModel;
 typedef struct _GtkSourceContextEngine         GtkSourceContextEngine;
 typedef struct _GtkSourceEngine                        GtkSourceEngine;
+typedef struct _GtkSourceFileSaver             GtkSourceFileSaver;
 typedef struct _GtkSourceGutterRendererLines   GtkSourceGutterRendererLines;
 typedef struct _GtkSourceGutterRendererMarks   GtkSourceGutterRendererMarks;
 typedef struct _GtkSourceMarksSequence         GtkSourceMarksSequence;
diff --git a/gtksourceview/gtksourcetypes.h b/gtksourceview/gtksourcetypes.h
index 43d0003..6929733 100644
--- a/gtksourceview/gtksourcetypes.h
+++ b/gtksourceview/gtksourcetypes.h
@@ -34,6 +34,7 @@ typedef struct _GtkSourceCompletionItem               GtkSourceCompletionItem;
 typedef struct _GtkSourceCompletionProposal    GtkSourceCompletionProposal;
 typedef struct _GtkSourceCompletionProvider    GtkSourceCompletionProvider;
 typedef struct _GtkSourceEncoding              GtkSourceEncoding;
+typedef struct _GtkSourceFile                  GtkSourceFile;
 typedef struct _GtkSourceGutter                        GtkSourceGutter;
 typedef struct _GtkSourceGutterRenderer                GtkSourceGutterRenderer;
 typedef struct _GtkSourceGutterRendererPixbuf  GtkSourceGutterRendererPixbuf;
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 2ac3319..78ee138 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -124,6 +124,8 @@ gtksourceview/gtksourcecompletionmodel.c
 [type: gettext/glade]gtksourceview/gtksourcecompletion.ui
 gtksourceview/gtksourcecontextengine.c
 gtksourceview/gtksourceencoding.c
+gtksourceview/gtksourcefile.c
+gtksourceview/gtksourcefilesaver.c
 gtksourceview/gtksourcegutter.c
 gtksourceview/gtksourcegutterrenderer.c
 gtksourceview/gtksourcegutterrendererpixbuf.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2fe0700..8412d49 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -86,6 +86,13 @@ test_completion_words_LDADD =                                        \
        $(DEP_LIBS)                                             \
        $(TESTS_LIBS)
 
+UNIT_TEST_PROGS += test-file-saver
+test_file_saver_SOURCES = test-file-saver.c
+test_file_saver_LDADD =                                                \
+       $(top_builddir)/gtksourceview/libgtksourceview-3.0.la   \
+       $(DEP_LIBS)                                             \
+       $(TESTS_LIBS)
+
 UNIT_TEST_PROGS += test-language
 test_language_SOURCES =                \
        test-language.c
diff --git a/tests/test-file-saver.c b/tests/test-file-saver.c
new file mode 100644
index 0000000..ef4e13a
--- /dev/null
+++ b/tests/test-file-saver.c
@@ -0,0 +1,773 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* test-file-saver.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ * Copyright (C) 2013 - Sébastien Wilmet
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <glib/gprintf.h>
+#include <gtksourceview/gtksource.h>
+
+/* linux/bsd has it. others such as Solaris, do not */
+#ifndef ACCESSPERMS
+#define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO)
+#endif
+
+#define DEFAULT_LOCAL_URI "/tmp/gtksourceview-file-saver-test.txt"
+#define DEFAULT_REMOTE_URI "sftp://localhost/tmp/gtksourceview-file-saver-test.txt";
+#define DEFAULT_CONTENT "hello world!"
+#define DEFAULT_CONTENT_RESULT "hello world!\n"
+
+#define UNOWNED_LOCAL_DIRECTORY "/tmp/gtksourceview-file-saver-unowned"
+#define UNOWNED_LOCAL_URI "/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt"
+
+#define UNOWNED_REMOTE_DIRECTORY "sftp://localhost/tmp/gtksourceview-file-saver-unowned";
+#define UNOWNED_REMOTE_URI 
"sftp://localhost/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt";
+
+#define UNOWNED_GROUP_LOCAL_URI "/tmp/gtksourceview-file-saver-unowned-group.txt"
+#define UNOWNED_GROUP_REMOTE_URI "sftp://localhost/tmp/gtksourceview-file-saver-unowned-group.txt";
+
+typedef struct _SaverTestData SaverTestData;
+typedef void (*SavedCallback) (SaverTestData *data);
+
+struct _SaverTestData
+{
+       GtkSourceFile *file;
+       GFile *location;
+       const gchar *expected_file_contents;
+       GtkSourceFileSaveFlags save_flags;
+       SavedCallback saved_callback;
+       gpointer userdata;
+
+       guint file_existed : 1;
+};
+
+static const gchar *
+read_file (GFile *location)
+{
+       /* TODO use g_file_load_contents() */
+       GError *error = NULL;
+       static gchar buffer[4096];
+       gsize read;
+
+       GInputStream *stream = G_INPUT_STREAM (g_file_read (location, NULL, &error));
+
+       g_assert_no_error (error);
+
+       g_input_stream_read_all (stream, buffer, sizeof (buffer) - 1, &read, NULL, &error);
+       g_assert_no_error (error);
+
+       buffer[read] = '\0';
+
+       g_input_stream_close (stream, NULL, NULL);
+
+       g_object_unref (stream);
+
+       return buffer;
+}
+
+static void
+save_file_cb (GtkSourceFile *file,
+             GAsyncResult  *result,
+             SaverTestData *data)
+{
+       GError *error = NULL;
+
+       gtk_source_file_save_finish (file, result, &error);
+
+       g_assert_no_error (error);
+
+       g_assert_cmpstr (data->expected_file_contents, ==, read_file (data->location));
+
+       if (data->saved_callback != NULL)
+       {
+               data->saved_callback (data);
+       }
+
+       if (!data->file_existed)
+       {
+               g_file_delete (data->location, NULL, NULL);
+       }
+
+       /* finished */
+       gtk_main_quit ();
+}
+
+static void
+save_file (SaverTestData *data)
+{
+       data->file_existed = g_file_query_exists (data->location, NULL);
+
+       gtk_source_file_save_async (data->file,
+                                   data->save_flags,
+                                   G_PRIORITY_DEFAULT,
+                                   NULL,
+                                   NULL,
+                                   NULL,
+                                   (GAsyncReadyCallback) save_file_cb,
+                                   data);
+}
+
+static void
+mount_cb (GFile         *location,
+         GAsyncResult  *result,
+         SaverTestData *data)
+{
+       GError *error = NULL;
+
+       g_file_mount_enclosing_volume_finish (location, result, &error);
+
+       if (error != NULL && error->code == G_IO_ERROR_ALREADY_MOUNTED)
+       {
+               g_error_free (error);
+       }
+       else if (error != NULL && error->code == G_IO_ERROR_NOT_SUPPORTED)
+       {
+               g_error_free (error);
+
+               /* The unit test can not be run */
+               gtk_main_quit ();
+               return;
+       }
+       else
+       {
+               g_assert_no_error (error);
+       }
+
+       save_file (data);
+}
+
+static void
+check_mounted (SaverTestData *data)
+{
+       GMountOperation *mount_operation;
+
+       if (g_file_is_native (data->location))
+       {
+               save_file (data);
+               return;
+       }
+
+       mount_operation = gtk_mount_operation_new (NULL);
+
+       g_file_mount_enclosing_volume (data->location,
+                                      G_MOUNT_MOUNT_NONE,
+                                      mount_operation,
+                                      NULL,
+                                      (GAsyncReadyCallback) mount_cb,
+                                      data);
+
+       g_object_unref (mount_operation);
+}
+
+static void
+test_saver (const gchar            *filename_or_uri,
+           const gchar            *buffer_contents,
+           const gchar            *expected_file_contents,
+           GtkSourceNewlineType    newline_type,
+           GtkSourceFileSaveFlags  save_flags,
+           SavedCallback           saved_callback,
+           gpointer                userdata)
+{
+       GFile *location;
+       GtkSourceBuffer *buffer;
+       GtkSourceFile *file;
+       SaverTestData *data;
+
+       location = g_file_new_for_commandline_arg (filename_or_uri);
+
+       buffer = gtk_source_buffer_new (NULL);
+       gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), buffer_contents, -1);
+
+       file = gtk_source_file_new (location, buffer);
+       g_object_unref (buffer);
+
+       gtk_source_file_set_newline_type (file, newline_type);
+       gtk_source_file_set_encoding (file, gtk_source_encoding_get_utf8 ());
+
+       data = g_slice_new (SaverTestData);
+       data->file = file;
+       data->location = location;
+       data->expected_file_contents = expected_file_contents;
+       data->save_flags = save_flags;
+       data->saved_callback = saved_callback;
+       data->userdata = userdata;
+
+       check_mounted (data);
+       gtk_main ();
+
+       g_object_unref (data->file);
+       g_object_unref (data->location);
+       g_slice_free (SaverTestData, data);
+}
+
+typedef struct
+{
+       GtkSourceNewlineType type;
+       const gchar *text;
+       const gchar *result;
+} NewLineTestData;
+
+static NewLineTestData newline_test_data[] = {
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld\n", "\nhello\nworld\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld\n\n", "\nhello\nworld\n\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\r\nhello\r\nworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\r\nhello\r\nworld\r\n", "\nhello\nworld\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\rhello\rworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\rhello\rworld\r", "\nhello\nworld\n\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\r\nworld", "\nhello\nworld\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\r\nworld\r", "\nhello\nworld\n\n"},
+
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld\n", "\r\nhello\r\nworld\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld\n\n", "\r\nhello\r\nworld\r\n\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\r\nhello\r\nworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\r\nhello\r\nworld\r\n", "\r\nhello\r\nworld\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\rhello\rworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\rhello\rworld\r", "\r\nhello\r\nworld\r\n\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\r\nworld", "\r\nhello\r\nworld\r\n"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\r\nworld\r", "\r\nhello\r\nworld\r\n\r\n"},
+
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld\n", "\rhello\rworld\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld\n\n", "\rhello\rworld\r\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\r\nhello\r\nworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\r\nhello\r\nworld\r\n", "\rhello\rworld\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\rhello\rworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\rhello\rworld\r", "\rhello\rworld\r\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\r\nworld", "\rhello\rworld\r"},
+       {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\r\nworld\r", "\rhello\rworld\r\r"}
+};
+
+static void
+test_new_line (const gchar            *filename,
+              GtkSourceFileSaveFlags  save_flags)
+{
+       gint i;
+       gint num = sizeof (newline_test_data) / sizeof (NewLineTestData);
+
+       for (i = 0; i < num; ++i)
+       {
+               NewLineTestData *nt = &(newline_test_data[i]);
+
+               test_saver (filename,
+                           nt->text,
+                           nt->result,
+                           nt->type,
+                           save_flags,
+                           NULL,
+                           NULL);
+       }
+}
+
+static void
+test_local_newline (void)
+{
+       test_new_line (DEFAULT_LOCAL_URI, 0);
+}
+
+static void
+test_local (void)
+{
+       test_saver (DEFAULT_LOCAL_URI,
+                   "hello world",
+                   "hello world\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_LOCAL_URI,
+                   "hello world\r\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_LOCAL_URI,
+                   "hello world\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+}
+
+static void
+test_remote_newline (void)
+{
+       test_new_line (DEFAULT_REMOTE_URI, 0);
+}
+
+static void
+test_remote (void)
+{
+       test_saver (DEFAULT_REMOTE_URI,
+                   "hello world",
+                   "hello world\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_REMOTE_URI,
+                   "hello world\r\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+
+       test_saver (DEFAULT_REMOTE_URI,
+                   "hello world\n",
+                   "hello world\n\n",
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+}
+
+#ifndef G_OS_WIN32
+static void
+check_permissions (GFile *location,
+                   guint  permissions)
+{
+       GError *error = NULL;
+       GFileInfo *info;
+
+       info = g_file_query_info (location,
+                                 G_FILE_ATTRIBUTE_UNIX_MODE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       g_assert_no_error (error);
+
+       g_assert_cmpint (permissions,
+                        ==,
+                        g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & ACCESSPERMS);
+
+       g_object_unref (info);
+}
+
+static void
+check_permissions_saved (SaverTestData *data)
+{
+       guint permissions = (guint)GPOINTER_TO_INT (data->userdata);
+
+       check_permissions (data->location, permissions);
+}
+
+static void
+test_permissions (const gchar *uri,
+                  guint        permissions)
+{
+       GError *error = NULL;
+       GFile *file = g_file_new_for_commandline_arg (uri);
+       GFileOutputStream *stream;
+       GFileInfo *info;
+       guint mode;
+
+       g_file_delete (file, NULL, NULL);
+       stream = g_file_create (file, 0, NULL, &error);
+
+       if (error && error->code == G_IO_ERROR_NOT_SUPPORTED)
+       {
+               g_error_free (error);
+               return;
+       }
+
+       g_assert_no_error (error);
+
+       g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
+       g_object_unref (stream);
+
+       info = g_file_query_info (file,
+                                 G_FILE_ATTRIBUTE_UNIX_MODE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       g_assert_no_error (error);
+
+       mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+       g_object_unref (info);
+
+       g_file_set_attribute_uint32 (file,
+                                    G_FILE_ATTRIBUTE_UNIX_MODE,
+                                    (mode & ~ACCESSPERMS) | permissions,
+                                    G_FILE_QUERY_INFO_NONE,
+                                    NULL,
+                                    &error);
+       g_assert_no_error (error);
+
+       check_permissions (file, permissions);
+
+       test_saver (uri,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   check_permissions_saved,
+                   GINT_TO_POINTER ((gint)permissions));
+
+       g_file_delete (file, NULL, NULL);
+       g_object_unref (file);
+}
+
+static void
+test_local_permissions (void)
+{
+       test_permissions (DEFAULT_LOCAL_URI, 0600);
+       test_permissions (DEFAULT_LOCAL_URI, 0660);
+       test_permissions (DEFAULT_LOCAL_URI, 0666);
+       test_permissions (DEFAULT_LOCAL_URI, 0760);
+}
+#endif
+
+static void
+test_local_unowned_directory (void)
+{
+       test_saver (UNOWNED_LOCAL_URI,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+}
+
+static void
+test_remote_unowned_directory (void)
+{
+       test_saver (UNOWNED_REMOTE_URI,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   NULL,
+                   NULL);
+}
+
+#ifndef G_OS_WIN32
+static void
+test_remote_permissions (void)
+{
+       test_permissions (DEFAULT_REMOTE_URI, 0600);
+       test_permissions (DEFAULT_REMOTE_URI, 0660);
+       test_permissions (DEFAULT_REMOTE_URI, 0666);
+       test_permissions (DEFAULT_REMOTE_URI, 0760);
+}
+
+static void
+test_unowned_group_permissions (SaverTestData *data)
+{
+       GError *error = NULL;
+       const gchar *group;
+       guint32 mode;
+
+       GFileInfo *info = g_file_query_info (data->location,
+                                            G_FILE_ATTRIBUTE_OWNER_GROUP ","
+                                            G_FILE_ATTRIBUTE_UNIX_MODE,
+                                            G_FILE_QUERY_INFO_NONE,
+                                            NULL,
+                                            &error);
+
+       g_assert_no_error (error);
+
+       group = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP);
+       g_assert_cmpstr (group, ==, "root");
+
+       mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+       g_assert_cmpint (mode & ACCESSPERMS, ==, 0660);
+
+       g_object_unref (info);
+}
+
+static void
+test_unowned_group (const gchar *uri)
+{
+       test_saver (uri,
+                   DEFAULT_CONTENT,
+                   DEFAULT_CONTENT_RESULT,
+                   GTK_SOURCE_NEWLINE_TYPE_LF,
+                   0,
+                   test_unowned_group_permissions,
+                   NULL);
+}
+
+static void
+test_local_unowned_group (void)
+{
+       test_unowned_group (UNOWNED_GROUP_LOCAL_URI);
+}
+
+#if 0
+static void
+test_remote_unowned_group (void)
+{
+       test_unowned_group (UNOWNED_GROUP_REMOTE_URI);
+}
+#endif
+
+#endif
+
+static gboolean
+check_unowned_directory (void)
+{
+       GFile *unowned = g_file_new_for_path (UNOWNED_LOCAL_DIRECTORY);
+       GFile *unowned_file;
+       GFileInfo *info;
+       GError *error = NULL;
+
+       g_printf ("*** Checking for unowned directory test... ");
+
+       info = g_file_query_info (unowned,
+                                 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error)
+       {
+               g_object_unref (unowned);
+               g_printf ("NO: directory does not exist\n");
+
+               g_error_free (error);
+               return FALSE;
+       }
+
+       if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: directory is writable\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+
+       g_object_unref (info);
+       g_object_unref (unowned);
+
+       unowned_file = g_file_new_for_commandline_arg (UNOWNED_LOCAL_URI);
+
+       info = g_file_query_info (unowned_file,
+                                 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error)
+       {
+               g_object_unref (unowned_file);
+               g_error_free (error);
+
+               g_printf ("NO: file does not exist\n");
+               return FALSE;
+       }
+
+       if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+       {
+               g_object_unref (unowned_file);
+
+               g_printf ("NO: file is not writable\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+
+       g_object_unref (info);
+       g_object_unref (unowned_file);
+
+       g_printf ("YES\n");
+       return TRUE;
+}
+
+static gboolean
+check_unowned_group (void)
+{
+       GFile *unowned = g_file_new_for_path (UNOWNED_GROUP_LOCAL_URI);
+       GFileInfo *info;
+       GError *error = NULL;
+
+       g_printf ("*** Checking for unowned group test... ");
+
+       info = g_file_query_info (unowned,
+                                 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE ","
+                                 G_FILE_ATTRIBUTE_OWNER_GROUP ","
+                                 G_FILE_ATTRIBUTE_UNIX_MODE,
+                                 G_FILE_QUERY_INFO_NONE,
+                                 NULL,
+                                 &error);
+
+       if (error)
+       {
+               g_object_unref (unowned);
+               g_printf ("NO: file does not exist\n");
+
+               g_error_free (error);
+               return FALSE;
+       }
+
+       if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: file is not writable\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+
+       if (g_strcmp0 (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP),
+                      "root") != 0)
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: group is not root (%s)\n", g_file_info_get_attribute_string (info, 
G_FILE_ATTRIBUTE_OWNER_GROUP));
+               g_object_unref (info);
+               return FALSE;
+       }
+
+#ifndef G_OS_WIN32
+       if ((g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & ACCESSPERMS) != 0660)
+       {
+               g_object_unref (unowned);
+
+               g_printf ("NO: file has wrong permissions\n");
+               g_object_unref (info);
+               return FALSE;
+       }
+#endif
+
+       g_object_unref (info);
+       g_object_unref (unowned);
+
+       g_printf ("YES\n");
+       return TRUE;
+}
+
+static void
+all_tests (void)
+{
+       gboolean have_unowned;
+       gboolean have_unowned_group;
+
+       g_printf ("\n***\n");
+       have_unowned = check_unowned_directory ();
+       have_unowned_group = check_unowned_group ();
+       g_printf ("***\n\n");
+
+       g_test_trap_subprocess ("/file-saver/subprocess/local",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       g_test_trap_subprocess ("/file-saver/subprocess/local-new-line",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       if (have_unowned)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/local-unowned-directory",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+
+       g_test_trap_subprocess ("/file-saver/subprocess/remote",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       g_test_trap_subprocess ("/file-saver/subprocess/remote-new-line",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       if (have_unowned)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/remote-unowned-directory",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+
+#if 0
+       if (have_unowned_group)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/remote-unowned-group",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+#endif
+
+#ifndef G_OS_WIN32
+       g_test_trap_subprocess ("/file-saver/subprocess/local-permissions",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+
+       if (have_unowned_group)
+       {
+               g_test_trap_subprocess ("/file-saver/subprocess/local-unowned-group",
+                                       0,
+                                       G_TEST_SUBPROCESS_INHERIT_STDERR);
+               g_test_trap_assert_passed ();
+       }
+
+       g_test_trap_subprocess ("/file-saver/subprocess/remote-permissions",
+                               0,
+                               G_TEST_SUBPROCESS_INHERIT_STDERR);
+       g_test_trap_assert_passed ();
+#endif
+}
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+       gtk_test_init (&argc, &argv);
+
+       g_test_add_func ("/file-saver", all_tests);
+
+       g_test_add_func ("/file-saver/subprocess/local", test_local);
+       g_test_add_func ("/file-saver/subprocess/local-new-line", test_local_newline);
+       g_test_add_func ("/file-saver/subprocess/local-unowned-directory", test_local_unowned_directory);
+       g_test_add_func ("/file-saver/subprocess/remote", test_remote);
+       g_test_add_func ("/file-saver/subprocess/remote-new-line", test_remote_newline);
+       g_test_add_func ("/file-saver/subprocess/remote-unowned-directory", test_remote_unowned_directory);
+
+       /* FIXME: there is a bug in gvfs sftp which doesn't pass this test */
+       /* g_test_add_func ("/file-saver/subprocess/remote-unowned-group", test_remote_unowned_group); */
+
+#ifndef G_OS_WIN32
+       g_test_add_func ("/file-saver/subprocess/local-permissions", test_local_permissions);
+       g_test_add_func ("/file-saver/subprocess/local-unowned-group", test_local_unowned_group);
+       g_test_add_func ("/file-saver/subprocess/remote-permissions", test_remote_permissions);
+#endif
+
+       return g_test_run ();
+}


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