[gtksourceview/wip/loader-saver: 68/73] File saving



commit 477f2abba7d814c090bcc081791ef86ba275979b
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.
    
    GtkSourceFile binds a GtkSourceBuffer with a GFile. There are properties
    for common things between the saving and loading. Specific saving
    settings are passed as flags in a function parameter.
    
    GtkSourceFile stores the modification time when a file is saved (and it
    will also get the modification time when the file is loaded). See the
    GTK_SOURCE_FILE_SAVE_IGNORE_MTIME flag, and the
    GTK_SOURCE_FILE_ERROR_EXTERNALLY_MODIFIED error. Currently, when saving
    the file for the first time, the mtime is not known, so the IGNORE_MTIME
    flag is forced.
    
    Differences from gedit:
    - gedit uses three signals: save, saving (to report progress) and saved.
      gsv uses the GIO conventions for async operations (also to report
      progress).
      The FileSaver uses a GTask internally.
    - for the ensure-trailing-newline setting, gedit uses GSettings, and gsv
      has properties.
    - for creating backup before saving, gedit has a GSettings, gsv has the
      CREATE_BACKUP flag in GtkSourceFileSaveFlags. gedit has also two flags
      for backups in the GeditDocumentSaveFlags enum: IGNORE_BACKUP and
      PRESERVE_BACKUP. There is a comment for the latter saying that it is
      needed for autosaving, but CREATE_BACKUP seems sufficient.
    - gedit has the _gedit_document_set_mount_operation_factory() private
      function, it is used internally by gedit when creating a
      GeditDocument. gsv must make this function public. And it is a pity to
      have this function only because it is better to set the parent GtkWindow
      to a GtkMountOperation...
    
    See also 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 5d0530b..f5739d1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -130,6 +130,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]