aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilip Withnall <bugzilla@tecnocode.co.uk>2009-08-07 10:32:55 +0800
committerMatthew Barnes <mbarnes@redhat.com>2009-08-07 11:11:47 +0800
commit22686195e6e1ca774b8fc9e0c34aab241b40d218 (patch)
tree306d7596da073a70bbedc39dda94692fe0b67436
parente679d6b55cb60208dbf3804072f31827ff070233 (diff)
downloadgsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.tar
gsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.tar.gz
gsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.tar.bz2
gsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.tar.lz
gsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.tar.xz
gsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.tar.zst
gsoc2013-evolution-22686195e6e1ca774b8fc9e0c34aab241b40d218.zip
Bug 590747 – Composer autosave can easily lose data
-rw-r--r--composer/e-composer-autosave.c326
-rw-r--r--composer/e-composer-autosave.h12
-rw-r--r--composer/e-msg-composer.c22
3 files changed, 241 insertions, 119 deletions
diff --git a/composer/e-composer-autosave.c b/composer/e-composer-autosave.c
index 5da5842d8c..2e288652e0 100644
--- a/composer/e-composer-autosave.c
+++ b/composer/e-composer-autosave.c
@@ -25,18 +25,18 @@
#include <e-util/e-error.h>
#include <e-util/e-util.h>
#include <camel/camel-stream-fs.h>
+#include <camel/camel-stream-mem.h>
#define AUTOSAVE_PREFIX ".evolution-composer.autosave"
#define AUTOSAVE_SEED AUTOSAVE_PREFIX "-XXXXXX"
-#define AUTOSAVE_INTERVAL 60 /* 60 seconds */
+#define AUTOSAVE_INTERVAL 60 /* seconds */
typedef struct _AutosaveState AutosaveState;
struct _AutosaveState {
- gchar *filename;
+ GFile *file;
gboolean enabled;
gboolean saved;
- gint fd;
};
static GList *autosave_registry;
@@ -51,13 +51,18 @@ composer_autosave_registry_lookup (const gchar *basename)
for (iter = autosave_registry; iter != NULL; iter = iter->next) {
EMsgComposer *composer = iter->data;
AutosaveState *state;
+ gchar *_basename;
state = g_object_get_data (G_OBJECT (composer), "autosave");
- if (state == NULL || state->filename == NULL)
+ if (state == NULL || state->file == NULL)
continue;
- if (g_str_has_suffix (state->filename, basename))
+ _basename = g_file_get_basename (state->file);
+ if (strcmp (_basename, basename) == 0) {
+ g_free (_basename);
return composer;
+ }
+ g_free (_basename);
}
return NULL;
@@ -69,9 +74,8 @@ composer_autosave_state_new (void)
AutosaveState *state;
state = g_slice_new (AutosaveState);
- state->filename = NULL;
+ state->file = NULL;
state->enabled = TRUE;
- state->fd = -1;
return state;
}
@@ -79,36 +83,34 @@ composer_autosave_state_new (void)
static void
composer_autosave_state_free (AutosaveState *state)
{
- if (state->fd >= 0)
- close (state->fd);
-
- g_free (state->filename);
+ g_object_unref (state->file);
g_slice_free (AutosaveState, state);
}
static gboolean
-composer_autosave_state_open (AutosaveState *state,
- GError **error)
+composer_autosave_state_open (AutosaveState *state)
{
- if (state->filename != NULL)
- return TRUE;
+ gchar *path;
- state->filename = g_build_filename (
- e_get_user_data_dir (), AUTOSAVE_SEED, NULL);
-
- errno = 0;
- if ((state->fd = g_mkstemp (state->filename)) >= 0)
+ if (state->file != NULL)
return TRUE;
- g_set_error (
- error, G_FILE_ERROR,
- g_file_error_from_errno (errno),
- "%s: %s", state->filename, g_strerror (errno));
+ path = g_build_filename (e_get_user_data_dir (), AUTOSAVE_SEED);
- g_free (state->filename);
- state->filename = NULL;
+ /* Since GIO doesn't have support for creating temporary files
+ * from a template (and in a given directory), we have to use
+ * mktemp(), which brings a small risk of overwriting another
+ * autosave file. The risk is, however, miniscule. */
+ if (mktemp (path) == NULL) {
+ g_free (path);
+ return FALSE;
+ }
+
+ /* Create the GFile */
+ state->file = g_file_new_for_path (path);
+ g_free (path);
- return FALSE;
+ return TRUE;
}
static void
@@ -118,7 +120,7 @@ composer_autosave_foreach (EMsgComposer *composer)
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
if (e_composer_autosave_get_enabled (composer))
- e_composer_autosave_snapshot (composer);
+ e_composer_autosave_snapshot_async (composer, NULL, NULL);
}
static gint
@@ -236,125 +238,223 @@ e_composer_autosave_unregister (EMsgComposer *composer,
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
state = g_object_get_data (G_OBJECT (composer), "autosave");
- if (state == NULL || state->filename == NULL)
+ if (state == NULL || state->file == NULL)
return;
- close (state->fd);
-
if (delete_file)
- g_unlink (state->filename);
+ g_file_delete (state->file, NULL, NULL);
g_object_set_data (G_OBJECT (composer), "autosave", NULL);
}
-gboolean
-e_composer_autosave_snapshot (EMsgComposer *composer)
-{
- GtkhtmlEditor *editor;
- CamelMimeMessage *message;
+typedef struct {
+ GSimpleAsyncResult *result;
+ EMsgComposer *composer;
AutosaveState *state;
- CamelStream *stream;
- gint camelfd;
- const gchar *errmsg;
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+ /* Transient data */
+ GInputStream *input_stream;
+} AutosaveData;
- editor = GTKHTML_EDITOR (composer);
+static void
+autosave_data_free (AutosaveData *data)
+{
+ g_object_unref (data->composer);
+ g_slice_free (AutosaveData, data);
+}
- /* If the contents are unchanged, exit early. */
- if (!gtkhtml_editor_get_changed (editor))
- return TRUE;
+static gboolean
+autosave_snapshot_check_for_error (AutosaveData *data, GError *error)
+{
+ if (error == NULL)
+ return FALSE;
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, FALSE);
+ g_simple_async_result_set_from_error (data->result, error);
+ g_simple_async_result_set_op_res_gboolean (data->result, FALSE);
+ g_simple_async_result_complete (data->result);
+ g_error_free (error);
- /* Open the autosave file on-demand. */
- if (!composer_autosave_state_open (state, NULL)) {
- errmsg = _("Could not open autosave file");
- goto fail;
- }
+ autosave_data_free (data);
- /* Extract a MIME message from the composer. */
- message = e_msg_composer_get_message_draft (composer);
- if (message == NULL) {
- errmsg = _("Unable to retrieve message from editor");
- goto fail;
- }
+ return TRUE;
+}
- /* Move to the beginning of the autosave file. */
- if (lseek (state->fd, (off_t) 0, SEEK_SET) < 0) {
- camel_object_unref (message);
- errmsg = g_strerror (errno);
- goto fail;
- }
+static void
+autosave_snapshot_splice_cb (GOutputStream *autosave_stream,
+ GSimpleAsyncResult *result,
+ AutosaveData *data)
+{
+ gssize length;
+ GError *error = NULL;
- /* Destroy the contents of the autosave file. */
- if (ftruncate (state->fd, (off_t) 0) < 0) {
- camel_object_unref (message);
- errmsg = g_strerror (errno);
- goto fail;
- }
+ length = g_output_stream_splice_finish (
+ autosave_stream, G_ASYNC_RESULT (result), &error);
+ g_object_unref (data->input_stream);
+ if (autosave_snapshot_check_for_error (data, error))
+ return;
- /* Duplicate the file descriptor for Camel. */
- if ((camelfd = dup (state->fd)) < 0) {
- camel_object_unref (message);
- errmsg = g_strerror (errno);
- goto fail;
- }
+ /* Snapshot was successful; set various flags. */
+ /* do not touch "changed" flag, this is only autosave,
+ * which doesn't mean it's saved permanently */
+ e_composer_autosave_set_saved (data->composer, TRUE);
- /* Open a CamelStream to the autosave file. */
- stream = camel_stream_fs_new_with_fd (camelfd);
+ /* Tidy up */
+ autosave_data_free (data);
+}
- /* Write the message to the CamelStream. */
- if (camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), stream) < 0) {
- camel_object_unref (message);
- camel_object_unref (stream);
- errmsg = g_strerror (errno);
- goto fail;
- }
+static void
+autosave_snapshot_cb (GFile *autosave_file,
+ GSimpleAsyncResult *result,
+ AutosaveData *data)
+{
+ CamelMimeMessage *message;
+ GFileOutputStream *output_stream;
+ GInputStream *input_stream;
+ CamelStream *camel_stream;
+ GByteArray *buffer;
+ GError *error = NULL;
+
+ output_stream = g_file_replace_finish (
+ autosave_file, G_ASYNC_RESULT (result), &error);
+ if (autosave_snapshot_check_for_error (data, error))
+ return;
- /* Close the CamelStream. */
- if (camel_stream_close (CAMEL_STREAM (stream)) < 0) {
- camel_object_unref (message);
- camel_object_unref (stream);
- errmsg = g_strerror (errno);
- goto fail;
+ /* Extract a MIME message from the composer. */
+ message = e_msg_composer_get_message_draft (data->composer);
+ if (message == NULL) {
+ /* If we don't set an error, but return FALSE, the error message
+ * in this odd case (we don't have an error domain or code)
+ * will be set in the _finish() function. */
+ g_simple_async_result_set_op_res_gboolean (result, FALSE);
+ g_object_unref (output_stream);
+ autosave_data_free (data);
+ return;
}
- /* Snapshot was successful; set various flags. */
- /* do not touch "changed" flag, this is only autosave,
- which doesn't mean it's saved permanently */
+ /* Decode the MIME part to an in-memory buffer. We have to do
+ * this because CamelStream is synchronous-only, and using threads
+ * (as we do with foo()) is dangerous because CamelDataWrapper
+ * is not reentrant. */
+ buffer = g_byte_array_new ();
+ camel_stream = camel_stream_mem_new ();
+ camel_stream_mem_set_byte_array (
+ CAMEL_STREAM_MEM (camel_stream), buffer);
+ camel_data_wrapper_decode_to_stream (
+ CAMEL_DATA_WRAPPER (message), camel_stream);
+ camel_object_unref (message);
+ camel_object_unref (camel_stream);
+
+ /* Load the buffer into a GMemoryInputStream.
+ * But watch out for zero length MIME parts. */
+ input_stream = g_memory_input_stream_new ();
+ if (buffer->len > 0)
+ g_memory_input_stream_add_data (
+ G_MEMORY_INPUT_STREAM (input_stream),
+ buffer->data, (gssize) buffer->len,
+ (GDestroyNotify) g_free);
+ g_byte_array_free (buffer, FALSE);
+ data->input_stream = input_stream;
+
+ /* Splice the input and output streams */
+ g_output_stream_splice_async (
+ G_OUTPUT_STREAM (output_stream), input_stream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)
+ autosave_snapshot_splice_cb, data);
+ g_object_unref (output_stream);
+}
- e_composer_autosave_set_saved (composer, TRUE);
+void
+e_composer_autosave_snapshot_async (EMsgComposer *composer,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ AutosaveData *data;
+ AutosaveState *state;
+ GSimpleAsyncResult *result;
- camel_object_unref (message);
- camel_object_unref (stream);
+ g_return_if_fail (E_IS_MSG_COMPOSER (composer));
- return TRUE;
+ result = g_simple_async_result_new (
+ G_OBJECT (composer), callback, user_data,
+ e_composer_autosave_snapshot_async);
-fail:
- e_error_run (
- GTK_WINDOW (composer), "mail-composer:no-autosave",
- (state->filename != NULL) ? state->filename : "",
- errmsg, NULL);
+ /* If the contents are unchanged, exit early. */
+ if (!gtkhtml_editor_get_changed (GTKHTML_EDITOR (composer))) {
+ g_simple_async_result_set_op_res_gboolean (result, TRUE);
+ g_simple_async_result_complete (result);
+ return;
+ }
- return FALSE;
+ state = g_object_get_data (G_OBJECT (composer), "autosave");
+ g_return_if_fail (state != NULL);
+
+ /* Open the autosave file on-demand. */
+ errno = 0;
+ if (!composer_autosave_state_open (state)) {
+ g_simple_async_result_set_error (
+ result, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ g_simple_async_result_set_op_res_gboolean (result, FALSE);
+ g_simple_async_result_complete (result);
+ return;
+ }
+
+ /* Overwrite the file */
+ data = g_slice_new (AutosaveData);
+ data->composer = composer;
+ data->result = result;
+ data->state = state;
+
+ g_file_replace_async (
+ state->file, NULL, FALSE, G_FILE_CREATE_PRIVATE,
+ G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)
+ autosave_snapshot_cb, data);
}
-gint
-e_composer_autosave_get_fd (EMsgComposer *composer)
+gboolean
+e_composer_autosave_snapshot_finish (EMsgComposer *composer,
+ GAsyncResult *async_result,
+ GError **error)
{
- AutosaveState *state;
+ GSimpleAsyncResult *result;
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), -1);
+ g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (async_result), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, -1);
+ result = G_SIMPLE_ASYNC_RESULT (async_result);
+ g_warn_if_fail (g_simple_async_result_get_source_tag (result) ==
+ e_composer_autosave_snapshot_async);
- return state->fd;
+ if (g_simple_async_result_propagate_error (result, error) ||
+ !g_simple_async_result_get_op_res_gboolean (result)) {
+ const gchar *errmsg;
+ gchar *filename;
+
+ /* Sort out the error message; use the GError message if
+ * possible. The only case where is isn't is where we couldn't
+ * get the message from the editor. */
+ if (error && *error)
+ errmsg = (*error)->message;
+ else
+ errmsg = _("Unable to retrieve message from editor");
+
+ filename = e_composer_autosave_get_filename (composer);
+ e_error_run (
+ GTK_WINDOW (composer), "mail-composer:no-autosave",
+ (filename != NULL) ? filename : "", errmsg, NULL);
+ g_free (filename);
+
+ return FALSE;
+ }
+
+ return TRUE;
}
-const gchar *
+gchar *
e_composer_autosave_get_filename (EMsgComposer *composer)
{
AutosaveState *state;
@@ -364,7 +464,7 @@ e_composer_autosave_get_filename (EMsgComposer *composer)
state = g_object_get_data (G_OBJECT (composer), "autosave");
g_return_val_if_fail (state != NULL, NULL);
- return state->filename;
+ return g_file_get_path (state->file);
}
gboolean
diff --git a/composer/e-composer-autosave.h b/composer/e-composer-autosave.h
index fe03b7b0bc..93b7a630d0 100644
--- a/composer/e-composer-autosave.h
+++ b/composer/e-composer-autosave.h
@@ -28,9 +28,15 @@ GList * e_composer_autosave_find_orphans (GError **error);
void e_composer_autosave_register (EMsgComposer *composer);
void e_composer_autosave_unregister (EMsgComposer *composer,
gboolean delete_file);
-gboolean e_composer_autosave_snapshot (EMsgComposer *composer);
-gint e_composer_autosave_get_fd (EMsgComposer *composer);
-const gchar * e_composer_autosave_get_filename (EMsgComposer *composer);
+void e_composer_autosave_snapshot_async
+ (EMsgComposer *composer,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_composer_autosave_snapshot_finish
+ (EMsgComposer *composer,
+ GAsyncResult *result,
+ GError **error);
+gchar * e_composer_autosave_get_filename (EMsgComposer *composer);
gboolean e_composer_autosave_get_enabled (EMsgComposer *composer);
void e_composer_autosave_set_enabled (EMsgComposer *composer,
gboolean enabled);
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index 926553fdff..178f3cdbdd 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -1273,6 +1273,15 @@ set_editor_text (EMsgComposer *composer,
/* Commands. */
+static void
+autosave_load_draft_cb (EMsgComposer *composer, GAsyncResult *result,
+ gchar *filename)
+{
+ if (e_composer_autosave_snapshot_finish (composer, result, NULL))
+ g_unlink (filename);
+ g_free (filename);
+}
+
static EMsgComposer *
autosave_load_draft (const gchar *filename)
{
@@ -1293,8 +1302,13 @@ autosave_load_draft (const gchar *filename)
composer = e_msg_composer_new_with_message (msg);
if (composer) {
- if (e_composer_autosave_snapshot (composer))
- g_unlink (filename);
+ /* Mark the message as changed so it gets autosaved again, then
+ * we can safely remove the old autosave file in
+ * autosave_load_draft_cb */
+ gtkhtml_editor_set_changed (GTKHTML_EDITOR (composer), FALSE);
+ e_composer_autosave_snapshot_async (composer,
+ (GAsyncReadyCallback) autosave_load_draft_cb,
+ g_strdup (filename));
g_signal_connect (
composer, "send",
@@ -3918,7 +3932,9 @@ e_msg_composer_request_close_all (void)
* which is misleading.
*/
composer->priv->application_exiting = TRUE;
- e_composer_autosave_snapshot (composer);
+ e_composer_autosave_snapshot_async (composer,
+ (GAsyncReadyCallback) e_composer_autosave_snapshot_finish,
+ NULL);
gtk_action_activate (ACTION (CLOSE));
}