[gtksourceview/wip/loader-saver] File saving
- From: Sébastien Wilmet <swilmet src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtksourceview/wip/loader-saver] File saving
- Date: Sun, 29 Dec 2013 14:41:46 +0000 (UTC)
commit 0fe024415a1685ba03ac1c31471e82525d704925
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 2ac3319..78ee138 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -124,6 +124,8 @@ gtksourceview/gtksourcecompletionmodel.c
[type: gettext/glade]gtksourceview/gtksourcecompletion.ui
gtksourceview/gtksourcecontextengine.c
gtksourceview/gtksourceencoding.c
+gtksourceview/gtksourcefile.c
+gtksourceview/gtksourcefilesaver.c
gtksourceview/gtksourcegutter.c
gtksourceview/gtksourcegutterrenderer.c
gtksourceview/gtksourcegutterrendererpixbuf.c
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 2fe0700..8412d49 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -86,6 +86,13 @@ test_completion_words_LDADD = \
$(DEP_LIBS) \
$(TESTS_LIBS)
+UNIT_TEST_PROGS += test-file-saver
+test_file_saver_SOURCES = test-file-saver.c
+test_file_saver_LDADD = \
+ $(top_builddir)/gtksourceview/libgtksourceview-3.0.la \
+ $(DEP_LIBS) \
+ $(TESTS_LIBS)
+
UNIT_TEST_PROGS += test-language
test_language_SOURCES = \
test-language.c
diff --git a/tests/test-file-saver.c b/tests/test-file-saver.c
new file mode 100644
index 0000000..ef4e13a
--- /dev/null
+++ b/tests/test-file-saver.c
@@ -0,0 +1,773 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*- */
+/* test-file-saver.c
+ * This file is part of GtkSourceView
+ *
+ * Copyright (C) 2010 - Jesse van den Kieboom
+ * Copyright (C) 2013 - Sébastien Wilmet
+ *
+ * GtkSourceView is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GtkSourceView is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <glib/gprintf.h>
+#include <gtksourceview/gtksource.h>
+
+/* linux/bsd has it. others such as Solaris, do not */
+#ifndef ACCESSPERMS
+#define ACCESSPERMS (S_IRWXU|S_IRWXG|S_IRWXO)
+#endif
+
+#define DEFAULT_LOCAL_URI "/tmp/gtksourceview-file-saver-test.txt"
+#define DEFAULT_REMOTE_URI "sftp://localhost/tmp/gtksourceview-file-saver-test.txt"
+#define DEFAULT_CONTENT "hello world!"
+#define DEFAULT_CONTENT_RESULT "hello world!\n"
+
+#define UNOWNED_LOCAL_DIRECTORY "/tmp/gtksourceview-file-saver-unowned"
+#define UNOWNED_LOCAL_URI "/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt"
+
+#define UNOWNED_REMOTE_DIRECTORY "sftp://localhost/tmp/gtksourceview-file-saver-unowned"
+#define UNOWNED_REMOTE_URI
"sftp://localhost/tmp/gtksourceview-file-saver-unowned/gtksourceview-file-saver-test.txt"
+
+#define UNOWNED_GROUP_LOCAL_URI "/tmp/gtksourceview-file-saver-unowned-group.txt"
+#define UNOWNED_GROUP_REMOTE_URI "sftp://localhost/tmp/gtksourceview-file-saver-unowned-group.txt"
+
+typedef struct _SaverTestData SaverTestData;
+typedef void (*SavedCallback) (SaverTestData *data);
+
+struct _SaverTestData
+{
+ GtkSourceFile *file;
+ GFile *location;
+ const gchar *expected_file_contents;
+ GtkSourceFileSaveFlags save_flags;
+ SavedCallback saved_callback;
+ gpointer userdata;
+
+ guint file_existed : 1;
+};
+
+static const gchar *
+read_file (GFile *location)
+{
+ /* TODO use g_file_load_contents() */
+ GError *error = NULL;
+ static gchar buffer[4096];
+ gsize read;
+
+ GInputStream *stream = G_INPUT_STREAM (g_file_read (location, NULL, &error));
+
+ g_assert_no_error (error);
+
+ g_input_stream_read_all (stream, buffer, sizeof (buffer) - 1, &read, NULL, &error);
+ g_assert_no_error (error);
+
+ buffer[read] = '\0';
+
+ g_input_stream_close (stream, NULL, NULL);
+
+ g_object_unref (stream);
+
+ return buffer;
+}
+
+static void
+save_file_cb (GtkSourceFile *file,
+ GAsyncResult *result,
+ SaverTestData *data)
+{
+ GError *error = NULL;
+
+ gtk_source_file_save_finish (file, result, &error);
+
+ g_assert_no_error (error);
+
+ g_assert_cmpstr (data->expected_file_contents, ==, read_file (data->location));
+
+ if (data->saved_callback != NULL)
+ {
+ data->saved_callback (data);
+ }
+
+ if (!data->file_existed)
+ {
+ g_file_delete (data->location, NULL, NULL);
+ }
+
+ /* finished */
+ gtk_main_quit ();
+}
+
+static void
+save_file (SaverTestData *data)
+{
+ data->file_existed = g_file_query_exists (data->location, NULL);
+
+ gtk_source_file_save_async (data->file,
+ data->save_flags,
+ G_PRIORITY_DEFAULT,
+ NULL,
+ NULL,
+ NULL,
+ (GAsyncReadyCallback) save_file_cb,
+ data);
+}
+
+static void
+mount_cb (GFile *location,
+ GAsyncResult *result,
+ SaverTestData *data)
+{
+ GError *error = NULL;
+
+ g_file_mount_enclosing_volume_finish (location, result, &error);
+
+ if (error != NULL && error->code == G_IO_ERROR_ALREADY_MOUNTED)
+ {
+ g_error_free (error);
+ }
+ else if (error != NULL && error->code == G_IO_ERROR_NOT_SUPPORTED)
+ {
+ g_error_free (error);
+
+ /* The unit test can not be run */
+ gtk_main_quit ();
+ return;
+ }
+ else
+ {
+ g_assert_no_error (error);
+ }
+
+ save_file (data);
+}
+
+static void
+check_mounted (SaverTestData *data)
+{
+ GMountOperation *mount_operation;
+
+ if (g_file_is_native (data->location))
+ {
+ save_file (data);
+ return;
+ }
+
+ mount_operation = gtk_mount_operation_new (NULL);
+
+ g_file_mount_enclosing_volume (data->location,
+ G_MOUNT_MOUNT_NONE,
+ mount_operation,
+ NULL,
+ (GAsyncReadyCallback) mount_cb,
+ data);
+
+ g_object_unref (mount_operation);
+}
+
+static void
+test_saver (const gchar *filename_or_uri,
+ const gchar *buffer_contents,
+ const gchar *expected_file_contents,
+ GtkSourceNewlineType newline_type,
+ GtkSourceFileSaveFlags save_flags,
+ SavedCallback saved_callback,
+ gpointer userdata)
+{
+ GFile *location;
+ GtkSourceBuffer *buffer;
+ GtkSourceFile *file;
+ SaverTestData *data;
+
+ location = g_file_new_for_commandline_arg (filename_or_uri);
+
+ buffer = gtk_source_buffer_new (NULL);
+ gtk_text_buffer_set_text (GTK_TEXT_BUFFER (buffer), buffer_contents, -1);
+
+ file = gtk_source_file_new (location, buffer);
+ g_object_unref (buffer);
+
+ gtk_source_file_set_newline_type (file, newline_type);
+ gtk_source_file_set_encoding (file, gtk_source_encoding_get_utf8 ());
+
+ data = g_slice_new (SaverTestData);
+ data->file = file;
+ data->location = location;
+ data->expected_file_contents = expected_file_contents;
+ data->save_flags = save_flags;
+ data->saved_callback = saved_callback;
+ data->userdata = userdata;
+
+ check_mounted (data);
+ gtk_main ();
+
+ g_object_unref (data->file);
+ g_object_unref (data->location);
+ g_slice_free (SaverTestData, data);
+}
+
+typedef struct
+{
+ GtkSourceNewlineType type;
+ const gchar *text;
+ const gchar *result;
+} NewLineTestData;
+
+static NewLineTestData newline_test_data[] = {
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld", "\nhello\nworld\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld\n", "\nhello\nworld\n\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\nworld\n\n", "\nhello\nworld\n\n\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\r\nhello\r\nworld", "\nhello\nworld\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\r\nhello\r\nworld\r\n", "\nhello\nworld\n\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\rhello\rworld", "\nhello\nworld\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\rhello\rworld\r", "\nhello\nworld\n\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\r\nworld", "\nhello\nworld\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_LF, "\nhello\r\nworld\r", "\nhello\nworld\n\n"},
+
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld", "\r\nhello\r\nworld\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld\n", "\r\nhello\r\nworld\r\n\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\nworld\n\n", "\r\nhello\r\nworld\r\n\r\n\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\r\nhello\r\nworld", "\r\nhello\r\nworld\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\r\nhello\r\nworld\r\n", "\r\nhello\r\nworld\r\n\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\rhello\rworld", "\r\nhello\r\nworld\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\rhello\rworld\r", "\r\nhello\r\nworld\r\n\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\r\nworld", "\r\nhello\r\nworld\r\n"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR_LF, "\nhello\r\nworld\r", "\r\nhello\r\nworld\r\n\r\n"},
+
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld", "\rhello\rworld\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld\n", "\rhello\rworld\r\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\nworld\n\n", "\rhello\rworld\r\r\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\r\nhello\r\nworld", "\rhello\rworld\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\r\nhello\r\nworld\r\n", "\rhello\rworld\r\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\rhello\rworld", "\rhello\rworld\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\rhello\rworld\r", "\rhello\rworld\r\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\r\nworld", "\rhello\rworld\r"},
+ {GTK_SOURCE_NEWLINE_TYPE_CR, "\nhello\r\nworld\r", "\rhello\rworld\r\r"}
+};
+
+static void
+test_new_line (const gchar *filename,
+ GtkSourceFileSaveFlags save_flags)
+{
+ gint i;
+ gint num = sizeof (newline_test_data) / sizeof (NewLineTestData);
+
+ for (i = 0; i < num; ++i)
+ {
+ NewLineTestData *nt = &(newline_test_data[i]);
+
+ test_saver (filename,
+ nt->text,
+ nt->result,
+ nt->type,
+ save_flags,
+ NULL,
+ NULL);
+ }
+}
+
+static void
+test_local_newline (void)
+{
+ test_new_line (DEFAULT_LOCAL_URI, 0);
+}
+
+static void
+test_local (void)
+{
+ test_saver (DEFAULT_LOCAL_URI,
+ "hello world",
+ "hello world\n",
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+
+ test_saver (DEFAULT_LOCAL_URI,
+ "hello world\r\n",
+ "hello world\n\n",
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+
+ test_saver (DEFAULT_LOCAL_URI,
+ "hello world\n",
+ "hello world\n\n",
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+}
+
+static void
+test_remote_newline (void)
+{
+ test_new_line (DEFAULT_REMOTE_URI, 0);
+}
+
+static void
+test_remote (void)
+{
+ test_saver (DEFAULT_REMOTE_URI,
+ "hello world",
+ "hello world\n",
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+
+ test_saver (DEFAULT_REMOTE_URI,
+ "hello world\r\n",
+ "hello world\n\n",
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+
+ test_saver (DEFAULT_REMOTE_URI,
+ "hello world\n",
+ "hello world\n\n",
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+}
+
+#ifndef G_OS_WIN32
+static void
+check_permissions (GFile *location,
+ guint permissions)
+{
+ GError *error = NULL;
+ GFileInfo *info;
+
+ info = g_file_query_info (location,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ g_assert_no_error (error);
+
+ g_assert_cmpint (permissions,
+ ==,
+ g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & ACCESSPERMS);
+
+ g_object_unref (info);
+}
+
+static void
+check_permissions_saved (SaverTestData *data)
+{
+ guint permissions = (guint)GPOINTER_TO_INT (data->userdata);
+
+ check_permissions (data->location, permissions);
+}
+
+static void
+test_permissions (const gchar *uri,
+ guint permissions)
+{
+ GError *error = NULL;
+ GFile *file = g_file_new_for_commandline_arg (uri);
+ GFileOutputStream *stream;
+ GFileInfo *info;
+ guint mode;
+
+ g_file_delete (file, NULL, NULL);
+ stream = g_file_create (file, 0, NULL, &error);
+
+ if (error && error->code == G_IO_ERROR_NOT_SUPPORTED)
+ {
+ g_error_free (error);
+ return;
+ }
+
+ g_assert_no_error (error);
+
+ g_output_stream_close (G_OUTPUT_STREAM (stream), NULL, NULL);
+ g_object_unref (stream);
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ g_assert_no_error (error);
+
+ mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+ g_object_unref (info);
+
+ g_file_set_attribute_uint32 (file,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ (mode & ~ACCESSPERMS) | permissions,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+
+ check_permissions (file, permissions);
+
+ test_saver (uri,
+ DEFAULT_CONTENT,
+ DEFAULT_CONTENT_RESULT,
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ check_permissions_saved,
+ GINT_TO_POINTER ((gint)permissions));
+
+ g_file_delete (file, NULL, NULL);
+ g_object_unref (file);
+}
+
+static void
+test_local_permissions (void)
+{
+ test_permissions (DEFAULT_LOCAL_URI, 0600);
+ test_permissions (DEFAULT_LOCAL_URI, 0660);
+ test_permissions (DEFAULT_LOCAL_URI, 0666);
+ test_permissions (DEFAULT_LOCAL_URI, 0760);
+}
+#endif
+
+static void
+test_local_unowned_directory (void)
+{
+ test_saver (UNOWNED_LOCAL_URI,
+ DEFAULT_CONTENT,
+ DEFAULT_CONTENT_RESULT,
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+}
+
+static void
+test_remote_unowned_directory (void)
+{
+ test_saver (UNOWNED_REMOTE_URI,
+ DEFAULT_CONTENT,
+ DEFAULT_CONTENT_RESULT,
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ NULL,
+ NULL);
+}
+
+#ifndef G_OS_WIN32
+static void
+test_remote_permissions (void)
+{
+ test_permissions (DEFAULT_REMOTE_URI, 0600);
+ test_permissions (DEFAULT_REMOTE_URI, 0660);
+ test_permissions (DEFAULT_REMOTE_URI, 0666);
+ test_permissions (DEFAULT_REMOTE_URI, 0760);
+}
+
+static void
+test_unowned_group_permissions (SaverTestData *data)
+{
+ GError *error = NULL;
+ const gchar *group;
+ guint32 mode;
+
+ GFileInfo *info = g_file_query_info (data->location,
+ G_FILE_ATTRIBUTE_OWNER_GROUP ","
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ g_assert_no_error (error);
+
+ group = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP);
+ g_assert_cmpstr (group, ==, "root");
+
+ mode = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+ g_assert_cmpint (mode & ACCESSPERMS, ==, 0660);
+
+ g_object_unref (info);
+}
+
+static void
+test_unowned_group (const gchar *uri)
+{
+ test_saver (uri,
+ DEFAULT_CONTENT,
+ DEFAULT_CONTENT_RESULT,
+ GTK_SOURCE_NEWLINE_TYPE_LF,
+ 0,
+ test_unowned_group_permissions,
+ NULL);
+}
+
+static void
+test_local_unowned_group (void)
+{
+ test_unowned_group (UNOWNED_GROUP_LOCAL_URI);
+}
+
+#if 0
+static void
+test_remote_unowned_group (void)
+{
+ test_unowned_group (UNOWNED_GROUP_REMOTE_URI);
+}
+#endif
+
+#endif
+
+static gboolean
+check_unowned_directory (void)
+{
+ GFile *unowned = g_file_new_for_path (UNOWNED_LOCAL_DIRECTORY);
+ GFile *unowned_file;
+ GFileInfo *info;
+ GError *error = NULL;
+
+ g_printf ("*** Checking for unowned directory test... ");
+
+ info = g_file_query_info (unowned,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (error)
+ {
+ g_object_unref (unowned);
+ g_printf ("NO: directory does not exist\n");
+
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ g_object_unref (unowned);
+
+ g_printf ("NO: directory is writable\n");
+ g_object_unref (info);
+ return FALSE;
+ }
+
+ g_object_unref (info);
+ g_object_unref (unowned);
+
+ unowned_file = g_file_new_for_commandline_arg (UNOWNED_LOCAL_URI);
+
+ info = g_file_query_info (unowned_file,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (error)
+ {
+ g_object_unref (unowned_file);
+ g_error_free (error);
+
+ g_printf ("NO: file does not exist\n");
+ return FALSE;
+ }
+
+ if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ g_object_unref (unowned_file);
+
+ g_printf ("NO: file is not writable\n");
+ g_object_unref (info);
+ return FALSE;
+ }
+
+ g_object_unref (info);
+ g_object_unref (unowned_file);
+
+ g_printf ("YES\n");
+ return TRUE;
+}
+
+static gboolean
+check_unowned_group (void)
+{
+ GFile *unowned = g_file_new_for_path (UNOWNED_GROUP_LOCAL_URI);
+ GFileInfo *info;
+ GError *error = NULL;
+
+ g_printf ("*** Checking for unowned group test... ");
+
+ info = g_file_query_info (unowned,
+ G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE ","
+ G_FILE_ATTRIBUTE_OWNER_GROUP ","
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL,
+ &error);
+
+ if (error)
+ {
+ g_object_unref (unowned);
+ g_printf ("NO: file does not exist\n");
+
+ g_error_free (error);
+ return FALSE;
+ }
+
+ if (!g_file_info_get_attribute_boolean (info, G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE))
+ {
+ g_object_unref (unowned);
+
+ g_printf ("NO: file is not writable\n");
+ g_object_unref (info);
+ return FALSE;
+ }
+
+ if (g_strcmp0 (g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_OWNER_GROUP),
+ "root") != 0)
+ {
+ g_object_unref (unowned);
+
+ g_printf ("NO: group is not root (%s)\n", g_file_info_get_attribute_string (info,
G_FILE_ATTRIBUTE_OWNER_GROUP));
+ g_object_unref (info);
+ return FALSE;
+ }
+
+#ifndef G_OS_WIN32
+ if ((g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE) & ACCESSPERMS) != 0660)
+ {
+ g_object_unref (unowned);
+
+ g_printf ("NO: file has wrong permissions\n");
+ g_object_unref (info);
+ return FALSE;
+ }
+#endif
+
+ g_object_unref (info);
+ g_object_unref (unowned);
+
+ g_printf ("YES\n");
+ return TRUE;
+}
+
+static void
+all_tests (void)
+{
+ gboolean have_unowned;
+ gboolean have_unowned_group;
+
+ g_printf ("\n***\n");
+ have_unowned = check_unowned_directory ();
+ have_unowned_group = check_unowned_group ();
+ g_printf ("***\n\n");
+
+ g_test_trap_subprocess ("/file-saver/subprocess/local",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+
+ g_test_trap_subprocess ("/file-saver/subprocess/local-new-line",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+
+ if (have_unowned)
+ {
+ g_test_trap_subprocess ("/file-saver/subprocess/local-unowned-directory",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+ }
+
+ g_test_trap_subprocess ("/file-saver/subprocess/remote",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+
+ g_test_trap_subprocess ("/file-saver/subprocess/remote-new-line",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+
+ if (have_unowned)
+ {
+ g_test_trap_subprocess ("/file-saver/subprocess/remote-unowned-directory",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+ }
+
+#if 0
+ if (have_unowned_group)
+ {
+ g_test_trap_subprocess ("/file-saver/subprocess/remote-unowned-group",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+ }
+#endif
+
+#ifndef G_OS_WIN32
+ g_test_trap_subprocess ("/file-saver/subprocess/local-permissions",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+
+ if (have_unowned_group)
+ {
+ g_test_trap_subprocess ("/file-saver/subprocess/local-unowned-group",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+ }
+
+ g_test_trap_subprocess ("/file-saver/subprocess/remote-permissions",
+ 0,
+ G_TEST_SUBPROCESS_INHERIT_STDERR);
+ g_test_trap_assert_passed ();
+#endif
+}
+
+gint
+main (gint argc,
+ gchar *argv[])
+{
+ gtk_test_init (&argc, &argv);
+
+ g_test_add_func ("/file-saver", all_tests);
+
+ g_test_add_func ("/file-saver/subprocess/local", test_local);
+ g_test_add_func ("/file-saver/subprocess/local-new-line", test_local_newline);
+ g_test_add_func ("/file-saver/subprocess/local-unowned-directory", test_local_unowned_directory);
+ g_test_add_func ("/file-saver/subprocess/remote", test_remote);
+ g_test_add_func ("/file-saver/subprocess/remote-new-line", test_remote_newline);
+ g_test_add_func ("/file-saver/subprocess/remote-unowned-directory", test_remote_unowned_directory);
+
+ /* FIXME: there is a bug in gvfs sftp which doesn't pass this test */
+ /* g_test_add_func ("/file-saver/subprocess/remote-unowned-group", test_remote_unowned_group); */
+
+#ifndef G_OS_WIN32
+ g_test_add_func ("/file-saver/subprocess/local-permissions", test_local_permissions);
+ g_test_add_func ("/file-saver/subprocess/local-unowned-group", test_local_unowned_group);
+ g_test_add_func ("/file-saver/subprocess/remote-permissions", test_remote_permissions);
+#endif
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]