Add GeditDocumentOutputStream and unit tests.

parent 22c72c33
......@@ -67,6 +67,7 @@ NOINST_H_FILES = \
gedit-dirs.h \
gedit-document-input-stream.h \
gedit-document-loader.h \
gedit-document-output-stream.h \
gedit-document-saver.h \
gedit-documents-panel.h \
gedit-gio-document-loader.h \
......@@ -144,6 +145,7 @@ libgedit_la_SOURCES = \
gedit-document.c \
gedit-document-input-stream.c \
gedit-document-loader.c \
gedit-document-output-stream.c \
gedit-gio-document-loader.c \
gedit-document-saver.c \
gedit-gio-document-saver.c \
......
/*
* gedit-document-output-stream.c
* This file is part of gedit
*
* Copyright (C) 2010 - Ignacio Casal Quinteiro
*
* gedit is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gedit 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gedit; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#include "gedit-document-output-stream.h"
#define GEDIT_DOCUMENT_OUTPUT_STREAM_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStreamPrivate))
struct _GeditDocumentOutputStreamPrivate
{
GeditDocument *doc;
GtkTextIter pos;
guint is_initialized : 1;
guint is_closed : 1;
};
enum
{
PROP_0,
PROP_DOCUMENT
};
G_DEFINE_TYPE (GeditDocumentOutputStream, gedit_document_output_stream, G_TYPE_OUTPUT_STREAM)
static gssize gedit_document_output_stream_write (GOutputStream *stream,
const void *buffer,
gsize count,
GCancellable *cancellable,
GError **error);
static gboolean gedit_document_output_stream_close (GOutputStream *stream,
GCancellable *cancellable,
GError **error);
static void
gedit_document_output_stream_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GeditDocumentOutputStream *stream = GEDIT_DOCUMENT_OUTPUT_STREAM (object);
switch (prop_id)
{
case PROP_DOCUMENT:
stream->priv->doc = GEDIT_DOCUMENT (g_value_get_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gedit_document_output_stream_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GeditDocumentOutputStream *stream = GEDIT_DOCUMENT_OUTPUT_STREAM (object);
switch (prop_id)
{
case PROP_DOCUMENT:
g_value_set_object (value, stream->priv->doc);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gedit_document_output_stream_class_init (GeditDocumentOutputStreamClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GOutputStreamClass *stream_class = G_OUTPUT_STREAM_CLASS (klass);
object_class->get_property = gedit_document_output_stream_get_property;
object_class->set_property = gedit_document_output_stream_set_property;
stream_class->write_fn = gedit_document_output_stream_write;
stream_class->close_fn = gedit_document_output_stream_close;
g_object_class_install_property (object_class,
PROP_DOCUMENT,
g_param_spec_object ("document",
"Document",
"The document which is written",
GEDIT_TYPE_DOCUMENT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_type_class_add_private (object_class, sizeof (GeditDocumentOutputStreamPrivate));
}
static void
gedit_document_output_stream_init (GeditDocumentOutputStream *stream)
{
stream->priv = GEDIT_DOCUMENT_OUTPUT_STREAM_GET_PRIVATE (stream);
stream->priv->is_initialized = FALSE;
stream->priv->is_closed = FALSE;
}
static GeditDocumentNewlineType
get_newline_type (GtkTextIter *end)
{
GeditDocumentNewlineType res;
GtkTextIter copy;
gunichar c;
copy = *end;
c = gtk_text_iter_get_char (&copy);
GtkTextIter tt = copy;
gtk_text_iter_forward_chars (&tt, 2);
if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN)
{
if (gtk_text_iter_forward_char (&copy) &&
g_unichar_break_type (gtk_text_iter_get_char (&copy)) == G_UNICODE_BREAK_LINE_FEED)
{
res = GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF;
}
else
{
res = GEDIT_DOCUMENT_NEWLINE_TYPE_CR;
}
}
else
{
res = GEDIT_DOCUMENT_NEWLINE_TYPE_LF;
}
return res;
}
GOutputStream *
gedit_document_output_stream_new (GeditDocument *doc)
{
return G_OUTPUT_STREAM (g_object_new (GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM,
"document", doc, NULL));
}
GeditDocumentNewlineType
gedit_document_output_stream_detect_newline_type (GeditDocumentOutputStream *stream)
{
GeditDocumentNewlineType type;
GtkTextIter iter;
g_return_val_if_fail (GEDIT_IS_DOCUMENT_OUTPUT_STREAM (stream),
GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT);
type = GEDIT_DOCUMENT_NEWLINE_TYPE_DEFAULT;
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (stream->priv->doc), &iter);
if (!gtk_text_iter_backward_line (&iter))
{
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (stream->priv->doc),
&iter);
}
if (gtk_text_iter_ends_line (&iter) || gtk_text_iter_forward_to_line_end (&iter))
{
type = get_newline_type (&iter);
}
return type;
}
/* If the last char is a newline, remove it from the buffer (otherwise
GtkTextView shows it as an empty line). See bug #324942. */
static void
remove_ending_newline (GeditDocumentOutputStream *stream)
{
GtkTextIter end;
GtkTextIter start;
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (stream->priv->doc), &end);
start = end;
gtk_text_iter_set_line_offset (&start, 0);
if (gtk_text_iter_ends_line (&start) &&
gtk_text_iter_backward_line (&start))
{
if (!gtk_text_iter_ends_line (&start))
{
gtk_text_iter_forward_to_line_end (&start);
}
/* Delete the empty line which is from 'start' to 'end' */
gtk_text_buffer_delete (GTK_TEXT_BUFFER (stream->priv->doc),
&start,
&end);
}
}
static void
end_append_text_to_document (GeditDocumentOutputStream *stream)
{
remove_ending_newline (stream);
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (stream->priv->doc),
FALSE);
gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (stream->priv->doc));
}
static gssize
gedit_document_output_stream_write (GOutputStream *stream,
const void *buffer,
gsize count,
GCancellable *cancellable,
GError **error)
{
GeditDocumentOutputStream *ostream = GEDIT_DOCUMENT_OUTPUT_STREAM (stream);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
return -1;
if (!ostream->priv->is_initialized)
{
/* Init the undoable action */
gtk_source_buffer_begin_not_undoable_action (GTK_SOURCE_BUFFER (ostream->priv->doc));
/* clear the buffer */
gtk_text_buffer_set_text (GTK_TEXT_BUFFER (ostream->priv->doc),
"", 0);
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (ostream->priv->doc),
&ostream->priv->pos);
ostream->priv->is_initialized = TRUE;
}
gtk_text_buffer_insert (GTK_TEXT_BUFFER (ostream->priv->doc),
&ostream->priv->pos, buffer, count);
return count;
}
static gboolean
gedit_document_output_stream_close (GOutputStream *stream,
GCancellable *cancellable,
GError **error)
{
GeditDocumentOutputStream *ostream = GEDIT_DOCUMENT_OUTPUT_STREAM (stream);
if (!ostream->priv->is_closed && ostream->priv->is_initialized)
{
end_append_text_to_document (ostream);
ostream->priv->is_closed = TRUE;
}
return TRUE;
}
/*
* gedit-document-output-stream.h
* This file is part of gedit
*
* Copyright (C) 2010 - Ignacio Casal Quinteiro
*
* gedit is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gedit 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gedit; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
#ifndef __GEDIT_DOCUMENT_OUTPUT_STREAM_H__
#define __GEDIT_DOCUMENT_OUTPUT_STREAM_H__
#include <gio/gio.h>
#include "gedit-document.h"
G_BEGIN_DECLS
#define GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM (gedit_document_output_stream_get_type ())
#define GEDIT_DOCUMENT_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStream))
#define GEDIT_DOCUMENT_OUTPUT_STREAM_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStream const))
#define GEDIT_DOCUMENT_OUTPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStreamClass))
#define GEDIT_IS_DOCUMENT_OUTPUT_STREAM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM))
#define GEDIT_IS_DOCUMENT_OUTPUT_STREAM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM))
#define GEDIT_DOCUMENT_OUTPUT_STREAM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GEDIT_TYPE_DOCUMENT_OUTPUT_STREAM, GeditDocumentOutputStreamClass))
typedef struct _GeditDocumentOutputStream GeditDocumentOutputStream;
typedef struct _GeditDocumentOutputStreamClass GeditDocumentOutputStreamClass;
typedef struct _GeditDocumentOutputStreamPrivate GeditDocumentOutputStreamPrivate;
struct _GeditDocumentOutputStream
{
GOutputStream parent;
GeditDocumentOutputStreamPrivate *priv;
};
struct _GeditDocumentOutputStreamClass
{
GOutputStreamClass parent_class;
};
GType gedit_document_output_stream_get_type (void) G_GNUC_CONST;
GOutputStream *gedit_document_output_stream_new (GeditDocument *doc);
GeditDocumentNewlineType gedit_document_output_stream_detect_newline_type (GeditDocumentOutputStream *stream);
G_END_DECLS
#endif /* __GEDIT_DOCUMENT_OUTPUT_STREAM_H__ */
......@@ -39,6 +39,7 @@
#include <gio/gio.h>
#include "gedit-gio-document-loader.h"
#include "gedit-document-output-stream.h"
#include "gedit-smart-charset-converter.h"
#include "gedit-prefs-manager.h"
#include "gedit-debug.h"
......@@ -48,6 +49,9 @@ typedef struct
{
GeditGioDocumentLoader *loader;
GCancellable *cancellable;
gssize read;
gssize written;
gboolean tried_mount;
} AsyncData;
......@@ -80,13 +84,12 @@ struct _GeditGioDocumentLoaderPrivate
/* Handle for remote files */
GCancellable *cancellable;
GInputStream *stream;
GOutputStream *output;
GeditSmartCharsetConverter *converter;
gchar buffer[READ_CHUNK_SIZE];
GError *error;
guint started_insert : 1;
};
G_DEFINE_TYPE(GeditGioDocumentLoader, gedit_gio_document_loader, GEDIT_TYPE_DOCUMENT_LOADER)
......@@ -111,6 +114,12 @@ gedit_gio_document_loader_dispose (GObject *object)
priv->stream = NULL;
}
if (priv->output != NULL)
{
g_object_unref (priv->output);
priv->output = NULL;
}
if (priv->converter != NULL)
{
g_object_unref (priv->converter);
......@@ -161,7 +170,6 @@ gedit_gio_document_loader_init (GeditGioDocumentLoader *gvloader)
gvloader->priv->converter = NULL;
gvloader->priv->error = NULL;
gvloader->priv->started_insert = FALSE;
}
static AsyncData *
......@@ -229,25 +237,11 @@ get_metadata_encoding (GeditDocumentLoader *loader)
static void
remote_load_completed_or_failed (GeditGioDocumentLoader *gvloader, AsyncData *async)
{
GeditDocumentLoader *loader;
loader = GEDIT_DOCUMENT_LOADER (gvloader);
if (async)
async_data_free (async);
if (gvloader->priv->stream)
g_input_stream_close_async (G_INPUT_STREAM (gvloader->priv->stream),
G_PRIORITY_HIGH, NULL, NULL, NULL);
gedit_document_loader_loading (GEDIT_DOCUMENT_LOADER (gvloader),
gedit_document_loader_loading (GEDIT_DOCUMENT_LOADER (async->loader),
TRUE,
gvloader->priv->error);
async->loader->priv->error);
}
/* prototype, because they call each other... isn't C lovely */
static void read_file_chunk (AsyncData *async);
static void
async_failed (AsyncData *async, GError *error)
{
......@@ -256,110 +250,145 @@ async_failed (AsyncData *async, GError *error)
}
static void
append_text_to_document (GeditDocumentLoader *loader,
const gchar *text,
gint len)
close_output_stream_ready_cb (GOutputStream *stream,
GAsyncResult *res,
AsyncData *async)
{
GeditDocument *doc = loader->document;
GtkTextIter end;
/* Insert text in the buffer */
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (doc), &end);
GError *error = NULL;
gtk_text_buffer_insert (GTK_TEXT_BUFFER (doc), &end, text, len);
}
static GeditDocumentNewlineType
get_newline_type (GtkTextIter *end)
{
GeditDocumentNewlineType res;
GtkTextIter copy;
gunichar c;
/* check cancelled state manually */
if (g_cancellable_is_cancelled (async->cancellable))
{
async_data_free (async);
return;
}
gedit_debug_message (DEBUG_SAVER, "Finished closing stream");
if (!g_output_stream_close_finish (stream, res, &error))
{
gedit_debug_message (DEBUG_SAVER, "Closing stream error: %s", error->message);
copy = *end;
c = gtk_text_iter_get_char (&copy);
async_failed (async, error);
return;
}
GtkTextIter tt = copy;
gtk_text_iter_forward_chars (&tt, 2);
remote_load_completed_or_failed (async->loader, async);
}
if (g_unichar_break_type (c) == G_UNICODE_BREAK_CARRIAGE_RETURN)
static void
close_input_stream_ready_cb (GInputStream *stream,
GAsyncResult *res,
AsyncData *async)
{
GError *error = NULL;
/* check cancelled state manually */
if (g_cancellable_is_cancelled (async->cancellable))
{
if (gtk_text_iter_forward_char (&copy) &&
g_unichar_break_type (gtk_text_iter_get_char (&copy)) == G_UNICODE_BREAK_LINE_FEED)
{
res = GEDIT_DOCUMENT_NEWLINE_TYPE_CR_LF;
}
else
{
res = GEDIT_DOCUMENT_NEWLINE_TYPE_CR;
}
async_data_free (async);
return;
}
else
gedit_debug_message (DEBUG_SAVER, "Finished closing input stream");
if (!g_input_stream_close_finish (stream, res, &error))
{
res = GEDIT_DOCUMENT_NEWLINE_TYPE_LF;
gedit_debug_message (DEBUG_SAVER, "Closing input stream error: %s", error->message);
async_failed (async, error);
return;
}
return res;
/* now we close the output stream */
gedit_debug_message (DEBUG_SAVER, "Close output stream");
g_output_stream_close_async (async->loader->priv->output,
G_PRIORITY_HIGH,
async->cancellable,
(GAsyncReadyCallback)close_output_stream_ready_cb,
async);
}
static void
detect_newline_type (GeditDocumentLoader *loader)
write_complete (AsyncData *async)
{
GtkTextIter iter;
GeditDocumentLoader *loader;
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (loader->document), &iter);
loader = GEDIT_DOCUMENT_LOADER (async->loader);
if (!gtk_text_iter_backward_line (&iter))
{
gtk_text_buffer_get_start_iter (GTK_TEXT_BUFFER (loader->document), &iter);
}
if (gtk_text_iter_ends_line (&iter) || gtk_text_iter_forward_to_line_end (&iter))
{
loader->auto_detected_newline_type = get_newline_type (&iter);
}
if (async->loader->priv->stream)
g_input_stream_close_async (G_INPUT_STREAM (async->loader->priv->stream),
G_PRIORITY_HIGH,
async->cancellable,
(GAsyncReadyCallback)close_input_stream_ready_cb,
async);
}
/* prototype, because they call each other... isn't C lovely */
static void read_file_chunk (AsyncData *async);
static void write_file_chunk (AsyncData *async);
static void
remove_ending_newline (GeditDocumentLoader *loader)
async_write_cb (GOutputStream *stream,
GAsyncResult *res,
AsyncData *async)
{
GtkTextIter end;
GtkTextIter start;
GeditGioDocumentLoader *gvloader;
gssize bytes_written;
GError *error = NULL;
/* Check cancelled state manually */
if (g_cancellable_is_cancelled (async->cancellable))
{
async_data_free (async);
return;
}
gtk_text_buffer_get_end_iter (GTK_TEXT_BUFFER (loader->document), &end);
start = end;
bytes_written = g_output_stream_write_finish (stream, res, &error);
gtk_text_iter_set_line_offset (&start, 0);
gedit_debug_message (DEBUG_SAVER, "Written: %" G_GSSIZE_FORMAT, bytes_written);
if (gtk_text_iter_ends_line (&start) &&
gtk_text_iter_backward_line (&start))
if (bytes_written == -1)
{
if (!gtk_text_iter_ends_line (&start))
{
gtk_text_iter_forward_to_line_end (&start);
}
gedit_debug_message (DEBUG_SAVER, "Write error: %s", error->message);
async_failed (async, error);
return;
}
/* Delete the empty line which is from 'start' to 'end' */
gtk_text_buffer_delete (GTK_TEXT_BUFFER (loader->document),
&start,
&end);
gvloader = async->loader;
async->written += bytes_written;
/* write again */
if (async->written != async->read)
{
write_file_chunk (async);
return;
}
/* note that this signal blocks the read... check if it isn't
* a performance problem
*/
gedit_document_loader_loading (GEDIT_DOCUMENT_LOADER (gvloader),
FALSE,
NULL);
read_file_chunk (async);
}
static void
end_append_text_to_document (GeditDocumentLoader *loader)
write_file_chunk (AsyncData *async)
{
detect_newline_type (loader);
/* If the last char is a newline, remove it from the buffer (otherwise
GtkTextView shows it as an empty line). See bug #324942. */
remove_ending_newline (loader);
gtk_text_buffer_set_modified (GTK_TEXT_BUFFER (loader->document), FALSE);
GeditGioDocumentLoader *gvloader;
gtk_source_buffer_end_not_undoable_action (GTK_SOURCE_BUFFER (loader->document));
gvloader = async->loader;
GEDIT_GIO_DOCUMENT_LOADER (loader)->priv->started_insert = FALSE;
g_output_stream_write_async (G_OUTPUT_STREAM (gvloader->priv->output),
gvloader->priv->buffer + async->written,
async->read - async->written,
G_PRIORITY_HIGH,
async->cancellable,
(GAsyncReadyCallback) async_write_cb,
async);
}
static void
......@@ -370,7 +399,6 @@ async_read_cb (GInputStream *stream,
gedit_debug (DEBUG_LOADER);
GeditGioDocumentLoader *gvloader;
GeditDocumentLoader *loader;
gssize bytes_read;
GError *error = NULL;
gvloader = async->loader;
......@@ -379,44 +407,44 @@ async_read_cb (GInputStream *stream,
/* manually check cancelled state */
if (g_cancellable_is_cancelled (async->cancellable))
{
end_append_text_to_document (GEDIT_DOCUMENT_LOADER (gvloader));
remote_load_completed_or_failed (gvloader, async);
return;
}
bytes_read = g_input_stream_read_finish (stream, res, &error);
async->read = g_input_stream_read_finish (stream, res, &error);
async->written = 0;
/* error occurred */
if (bytes_read == -1)
if (async->read == -1)
{
async_failed (async, error);
return;
}
/* Check for the extremely unlikely case where the file size overflows. */
if (gvloader->priv->bytes_read + bytes_read < gvloader->priv->bytes_read)
if (gvloader->priv->bytes_read + async->read < gvloader->priv->bytes_read)
{
g_set_error (&gvloader->priv->error,
GEDIT_DOCUMENT_ERROR,
GEDIT_DOCUMENT_ERROR_TOO_BIG,
"File too big");
end_append_text_to_document (GEDIT_DOCUMENT_LOADER (gvloader));
remote_load_completed_or_failed (gvloader, async);
async_failed (async, gvloader->priv->error);
return;
}
/* Bump the size. */
gvloader->priv->bytes_read += bytes_read;
gvloader->priv->bytes_read += async->read;
/* end of the file, we are done! */
if (bytes_read == 0)
if (async->read == 0)
{
GEDIT_DOCUMENT_LOADER (gvloader)->auto_detected_encoding =
gedit_smart_charset_converter_get_guessed (gvloader->priv->converter);
loader->auto_detected_encoding = gedit_smart_charset_converter_get_guessed (gvloader->priv->converter);
loader->auto_detected_newline_type =
gedit_document_output_stream_detect_newline_type (GEDIT_DOCUMENT_OUTPUT_STREAM (gvloader->priv->output));
/* Check if we needed some fallback char, if so, check if there was
a previous error and if not set a fallback used error */
......@@ -431,27 +459,12 @@ async_read_cb (GInputStream *stream,
"needed to use a fallback char");
}*/
end_append_text_to_document (GEDIT_DOCUMENT_LOADER (gvloader));
remote_load_completed_or_failed (gvloader, async);
write_complete (async);
return;
}
append_text_to_document (GEDIT_DOCUMENT_LOADER (gvloader),
gvloader->priv->buffer,
bytes_read);
/* otherwise emit progress and read some more */
/* note that this signal blocks the read... check if it isn't