aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthew Barnes <mbarnes@redhat.com>2010-09-03 01:21:08 +0800
committerMatthew Barnes <mbarnes@redhat.com>2010-09-03 01:37:31 +0800
commit429234ff213ba04b6d0b02a28ed68aaa8af7c02c (patch)
tree69fd485d98a78148137ab29e5c1e8d16add7c0f1
parentcccdb143a571cde36db9fe906864647aab546cff (diff)
downloadgsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.gz
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.bz2
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.lz
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.xz
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.tar.zst
gsoc2013-evolution-429234ff213ba04b6d0b02a28ed68aaa8af7c02c.zip
Convert composer autosave to an EExtension.
Given the way the autosave feature was awkwardly bolted on to the composer, an EExtension seemed like a natural fit. And it helped clean up some object lifecycle hacks (and bugs). What we have now is a new module consisting of two EExtensions: EComposerAutosave extends EMsgComposer and determines when to kick off an asynchronous autosave operation. EComposerRegistry extends EShell and offers to restore orphaned autosave files on startup (which is also asynchronous now). e-autosave-utils.c holds the actual asynchronous functions and a few other miscellaneous utility functions. Source code for the new module lives in /modules/composer-autosave.
-rw-r--r--composer/Makefile.am2
-rw-r--r--composer/e-composer-autosave.c511
-rw-r--r--composer/e-composer-autosave.h45
-rw-r--r--composer/e-composer-private.h1
-rw-r--r--composer/e-msg-composer.c142
-rw-r--r--composer/e-msg-composer.h5
-rw-r--r--configure.ac1
-rw-r--r--mail/em-composer-utils.c7
-rw-r--r--modules/composer-autosave/Makefile.am28
-rw-r--r--modules/composer-autosave/e-autosave-utils.c499
-rw-r--r--modules/composer-autosave/e-autosave-utils.h48
-rw-r--r--modules/composer-autosave/e-composer-autosave.c227
-rw-r--r--modules/composer-autosave/e-composer-registry.c236
-rw-r--r--modules/composer-autosave/evolution-composer-autosave.c40
-rw-r--r--modules/mail/e-mail-shell-backend.c8
-rw-r--r--shell/e-shell.c17
16 files changed, 1113 insertions, 704 deletions
diff --git a/composer/Makefile.am b/composer/Makefile.am
index 68a7ec142c..5caea41ee7 100644
--- a/composer/Makefile.am
+++ b/composer/Makefile.am
@@ -18,7 +18,6 @@ libcomposerinclude_HEADERS = \
e-composer-text-header.h \
e-composer-common.h \
e-composer-actions.h \
- e-composer-autosave.h \
e-msg-composer.h
libcomposer_la_CPPFLAGS = \
@@ -43,7 +42,6 @@ libcomposer_la_CPPFLAGS = \
libcomposer_la_SOURCES = \
$(libcomposerinclude_HEADERS) \
e-composer-actions.c \
- e-composer-autosave.c \
e-composer-header.c \
e-composer-header-table.c \
e-composer-from-header.c \
diff --git a/composer/e-composer-autosave.c b/composer/e-composer-autosave.c
deleted file mode 100644
index 82a44d570a..0000000000
--- a/composer/e-composer-autosave.c
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * This program 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 of the License, or (at your option) version 3.
- *
- * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
- *
- * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
- */
-
-#include "e-composer-autosave.h"
-
-#include <errno.h>
-#include <sys/stat.h>
-#include <glib/gi18n.h>
-#include <glib/gstdio.h>
-
-#include <e-util/e-alert-dialog.h>
-#include <e-util/e-util.h>
-
-#define AUTOSAVE_PREFIX ".evolution-composer.autosave"
-#define AUTOSAVE_SEED AUTOSAVE_PREFIX "-XXXXXX"
-#define AUTOSAVE_INTERVAL 60 /* seconds */
-
-typedef struct _AutosaveState AutosaveState;
-
-struct _AutosaveState {
- EMsgComposer *composer;
- GFile *file;
- gboolean changed;
- guint source_id; /* timeout source ID */
- gboolean enabled;
- gboolean error_shown;
-};
-
-static GList *autosave_registry;
-
-static void composer_changed_cb (EMsgComposer *composer);
-
-static EMsgComposer *
-composer_autosave_registry_lookup (const gchar *basename)
-{
- GList *iter;
-
- /* Find the composer with the given autosave filename. */
- 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->file == NULL)
- continue;
-
- _basename = g_file_get_basename (state->file);
- if (strcmp (_basename, basename) == 0) {
- g_free (_basename);
- return composer;
- }
- g_free (_basename);
- }
-
- return NULL;
-}
-
-static AutosaveState *
-composer_autosave_state_new (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- state = g_slice_new0 (AutosaveState);
- state->enabled = TRUE;
- state->changed = FALSE;
- state->source_id = 0;
- state->composer = composer;
-
- g_signal_connect (
- composer, "notify::changed",
- G_CALLBACK (composer_changed_cb), NULL);
-
- return state;
-}
-
-static void
-composer_autosave_state_free (AutosaveState *state)
-{
- if (state->source_id)
- g_source_remove (state->source_id);
- if (state->file)
- g_object_unref (state->file);
- g_slice_free (AutosaveState, state);
-}
-
-static gboolean
-composer_autosave_state_open (AutosaveState *state,
- GError **error)
-{
- const gchar *user_data_dir;
- gchar *path;
- gint fd;
-
- if (state->file != NULL)
- return TRUE;
-
- user_data_dir = e_get_user_data_dir ();
- path = g_build_filename (user_data_dir, AUTOSAVE_SEED, NULL);
-
- /* Since GIO doesn't have support for creating temporary files
- * from a template (and in a given directory), we have to use
- * g_mkstemp(), which brings a small risk of overwriting another
- * autosave file. The risk is, however, miniscule. */
- errno = 0;
- fd = g_mkstemp (path);
- if (fd == -1) {
- g_set_error (
- error, G_FILE_ERROR,
- g_file_error_from_errno (errno),
- "%s", g_strerror (errno));
- g_free (path);
- return FALSE;
- }
-
- close (fd);
-
- /* Create the GFile */
- state->file = g_file_new_for_path (path);
- g_free (path);
-
- return TRUE;
-}
-
-static void
-composer_autosave_finish_cb (EMsgComposer *composer,
- GAsyncResult *result)
-{
- AutosaveState *state;
- GError *error = NULL;
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
-
- e_composer_autosave_snapshot_finish (composer, result, &error);
-
- if (error != NULL) {
- gchar *basename;
-
- if (G_IS_FILE (state->file))
- basename = g_file_get_basename (state->file);
- else
- basename = g_strdup (" ");
-
- /* Only show one error dialog at a
- * time to avoid cascading dialogs. */
- if (!state->error_shown) {
- state->error_shown = TRUE;
- e_alert_run_dialog_for_args (
- GTK_WINDOW (composer),
- "mail-composer:no-autosave",
- basename, error->message, NULL);
- state->error_shown = FALSE;
- } else
- g_warning ("%s: %s", basename, error->message);
-
- g_free (basename);
- g_error_free (error);
- }
-}
-
-static gboolean
-composer_autosave_timeout (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_val_if_fail (composer != NULL, FALSE);
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, FALSE);
- g_return_val_if_fail (state->composer == composer, FALSE);
-
- if (!state->changed) {
- state->source_id = 0;
- return FALSE;
- }
-
- composer = state->composer;
-
- if (e_composer_autosave_get_enabled (composer)) {
- state->changed = FALSE;
- e_composer_autosave_snapshot_async (
- composer, (GAsyncReadyCallback)
- composer_autosave_finish_cb, NULL);
- }
-
- return TRUE;
-}
-
-static void
-composer_changed_cb (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_if_fail (composer != NULL);
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
- g_return_if_fail (state->composer == composer);
-
- g_object_get (G_OBJECT (composer), "changed", &state->changed, NULL);
-
- if (state->changed && state->source_id == 0) {
- state->source_id = g_timeout_add_seconds (
- AUTOSAVE_INTERVAL, (GSourceFunc)
- composer_autosave_timeout, state->composer);
- }
-}
-
-static void
-composer_autosave_notify (gpointer unused,
- GObject *where_the_object_was)
-{
- /* Remove the dead composer from the registry. */
- autosave_registry = g_list_remove (
- autosave_registry, where_the_object_was);
-}
-
-GList *
-e_composer_autosave_find_orphans (GError **error)
-{
- GDir *dir;
- const gchar *dirname;
- const gchar *basename;
- GList *orphans = NULL;
-
- dirname = e_get_user_data_dir ();
- dir = g_dir_open (dirname, 0, error);
- if (dir == NULL)
- return NULL;
-
- /* Scan the user directory for autosave files. */
- while ((basename = g_dir_read_name (dir)) != NULL) {
- const gchar *errmsg;
- gchar *filename;
- struct stat st;
-
- /* Is this an autosave file? */
- if (!g_str_has_prefix (basename, AUTOSAVE_PREFIX))
- continue;
-
- /* Is this an orphaned autosave file? */
- if (composer_autosave_registry_lookup (basename) != NULL)
- continue;
-
- filename = g_build_filename (dirname, basename, NULL);
-
- /* Try to examine the autosave file. Failure here
- * is non-fatal; just emit a warning and move on. */
- errno = 0;
- if (g_stat (filename, &st) < 0) {
- errmsg = g_strerror (errno);
- g_warning ("%s: %s", filename, errmsg);
- g_free (filename);
- continue;
- }
-
- /* If the file is empty, delete it. Failure here
- * is non-fatal; just emit a warning and move on. */
- if (st.st_size == 0) {
- errno = 0;
- if (g_unlink (filename) < 0) {
- errmsg = g_strerror (errno);
- g_warning ("%s: %s", filename, errmsg);
- }
- g_free (filename);
- continue;
- }
-
- orphans = g_list_prepend (orphans, filename);
- }
-
- g_dir_close (dir);
-
- return g_list_reverse (orphans);
-}
-
-void
-e_composer_autosave_register (EMsgComposer *composer)
-{
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- g_object_set_data_full (
- G_OBJECT (composer), "autosave",
- composer_autosave_state_new (composer),
- (GDestroyNotify) composer_autosave_state_free);
-
- autosave_registry = g_list_prepend (autosave_registry, composer);
-
- g_object_weak_ref (
- G_OBJECT (composer), (GWeakNotify)
- composer_autosave_notify, NULL);
-}
-
-void
-e_composer_autosave_unregister (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- if (state == NULL || state->file == NULL)
- return;
-
- g_file_delete (state->file, NULL, NULL);
-
- g_object_set_data (G_OBJECT (composer), "autosave", NULL);
-}
-
-static void
-autosave_snapshot_splice_cb (GOutputStream *output_stream,
- GAsyncResult *result,
- GSimpleAsyncResult *simple)
-{
- GError *error = NULL;
-
- g_output_stream_splice_finish (output_stream, result, &error);
-
- if (error != NULL) {
- g_simple_async_result_set_from_error (simple, error);
- g_error_free (error);
- }
-
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
-}
-
-static void
-autosave_snapshot_cb (GFile *file,
- GAsyncResult *result,
- GSimpleAsyncResult *simple)
-{
- GObject *object;
- EMsgComposer *composer;
- CamelMimeMessage *message;
- GFileOutputStream *output_stream;
- GInputStream *input_stream;
- CamelStream *camel_stream;
- GByteArray *buffer;
- GError *error = NULL;
-
- object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
-
- output_stream = g_file_replace_finish (file, result, &error);
-
- if (error != NULL) {
- g_simple_async_result_set_from_error (simple, error);
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
- g_error_free (error);
- return;
- }
-
- /* Extract a MIME message from the composer. */
- composer = E_MSG_COMPOSER (object);
- message = e_msg_composer_get_message_draft (composer, &error);
- if (error != NULL) {
- g_simple_async_result_set_from_error (simple, error);
- g_simple_async_result_complete (simple);
- g_object_unref (output_stream);
- g_object_unref (simple);
- return;
- }
-
- /* Decode the MIME part to an in-memory buffer. We have to do
- * this because CamelStream is synchronous-only, and using threads
- * 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, NULL);
- g_object_unref (message);
- g_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);
-
- /* 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, simple);
-
- g_object_unref (output_stream);
- g_object_unref (input_stream);
-}
-
-void
-e_composer_autosave_snapshot_async (EMsgComposer *composer,
- GAsyncReadyCallback callback,
- gpointer user_data)
-{
- AutosaveState *state;
- GSimpleAsyncResult *simple;
- GError *error = NULL;
-
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
-
- simple = g_simple_async_result_new (
- G_OBJECT (composer), callback, user_data,
- e_composer_autosave_snapshot_async);
-
- /* If the contents are unchanged, exit early. */
- if (!gtkhtml_editor_get_changed (GTKHTML_EDITOR (composer))) {
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
- return;
- }
-
- /* Open the autosave file on-demand. */
- if (!composer_autosave_state_open (state, &error)) {
- g_simple_async_result_set_from_error (simple, error);
- g_simple_async_result_complete (simple);
- g_object_unref (simple);
- return;
- }
-
- /* Overwrite the file. */
- g_file_replace_async (
- state->file, NULL, FALSE, G_FILE_CREATE_PRIVATE,
- G_PRIORITY_DEFAULT, NULL, (GAsyncReadyCallback)
- autosave_snapshot_cb, simple);
-}
-
-gboolean
-e_composer_autosave_snapshot_finish (EMsgComposer *composer,
- GAsyncResult *result,
- GError **error)
-{
- GSimpleAsyncResult *simple;
-
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
- g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE);
- g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
-
- simple = G_SIMPLE_ASYNC_RESULT (result);
-
- /* Success is assumed in the absense of a GError. */
- return !g_simple_async_result_propagate_error (simple, error);
-}
-
-gchar *
-e_composer_autosave_get_filename (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, NULL);
-
- return g_file_get_path (state->file);
-}
-
-gboolean
-e_composer_autosave_get_enabled (EMsgComposer *composer)
-{
- AutosaveState *state;
-
- g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), FALSE);
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_val_if_fail (state != NULL, FALSE);
-
- return state->enabled;
-}
-
-void
-e_composer_autosave_set_enabled (EMsgComposer *composer,
- gboolean enabled)
-{
- AutosaveState *state;
-
- g_return_if_fail (E_IS_MSG_COMPOSER (composer));
-
- state = g_object_get_data (G_OBJECT (composer), "autosave");
- g_return_if_fail (state != NULL);
-
- state->enabled = enabled;
-}
diff --git a/composer/e-composer-autosave.h b/composer/e-composer-autosave.h
deleted file mode 100644
index 6f55f10b1a..0000000000
--- a/composer/e-composer-autosave.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * This program 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 of the License, or (at your option) version 3.
- *
- * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
- *
- * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
- */
-
-#ifndef E_COMPOSER_AUTOSAVE_H
-#define E_COMPOSER_AUTOSAVE_H
-
-#include "e-composer-common.h"
-#include "e-msg-composer.h"
-
-G_BEGIN_DECLS
-
-GList * e_composer_autosave_find_orphans (GError **error);
-
-void e_composer_autosave_register (EMsgComposer *composer);
-void e_composer_autosave_unregister (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);
-
-G_END_DECLS
-
-#endif /* E_COMPOSER_AUTOSAVE_H */
diff --git a/composer/e-composer-private.h b/composer/e-composer-private.h
index 8d1408956d..026ed96954 100644
--- a/composer/e-composer-private.h
+++ b/composer/e-composer-private.h
@@ -28,7 +28,6 @@
#include <glib/gstdio.h>
#include "e-composer-actions.h"
-#include "e-composer-autosave.h"
#include "e-composer-header-table.h"
#include "e-util/e-binding.h"
#include "e-util/e-charset.h"
diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c
index 65523677e9..480bfbe3bd 100644
--- a/composer/e-msg-composer.c
+++ b/composer/e-msg-composer.c
@@ -45,12 +45,13 @@
#include <gconf/gconf.h>
#include <gconf/gconf-client.h>
-#include "e-util/e-dialog-utils.h"
+#include "e-util/e-account-utils.h"
#include "e-util/e-alert-dialog.h"
+#include "e-util/e-dialog-utils.h"
+#include "e-util/e-extensible.h"
#include "e-util/e-plugin-ui.h"
-#include "e-util/e-util-private.h"
-#include "e-util/e-account-utils.h"
#include "e-util/e-signature-utils.h"
+#include "e-util/e-util-private.h"
#include "e-signature-combo-box.h"
#include "shell/e-shell.h"
#include "em-format/em-format.h"
@@ -59,7 +60,6 @@
#include "e-msg-composer.h"
#include "e-attachment.h"
-#include "e-composer-autosave.h"
#include "e-composer-private.h"
#include "e-composer-header-table.h"
@@ -113,10 +113,11 @@ static void handle_multipart_signed (EMsgComposer *composer,
CamelMultipart *multipart,
gint depth);
-G_DEFINE_TYPE (
+G_DEFINE_TYPE_WITH_CODE (
EMsgComposer,
e_msg_composer,
- GTKHTML_TYPE_EDITOR)
+ GTKHTML_TYPE_EDITOR,
+ G_IMPLEMENT_INTERFACE (E_TYPE_EXTENSIBLE, NULL))
/**
* emcu_part_to_html:
@@ -1439,71 +1440,6 @@ set_editor_text (EMsgComposer *composer,
g_free (body);
}
-/* Commands. */
-
-static void
-autosave_load_draft_cb (EMsgComposer *composer,
- GAsyncResult *result,
- gchar *filename)
-{
- GError *error = NULL;
-
- if (e_composer_autosave_snapshot_finish (composer, result, &error))
- g_unlink (filename);
-
- else {
- e_alert_run_dialog_for_args (
- GTK_WINDOW (composer),
- "mail-composer:no-autosave",
- (filename != NULL) ? filename : "",
- (error != NULL) ? error->message :
- _("Unable to reconstruct message from autosave file"),
- NULL);
-
- if (error != NULL)
- g_error_free (error);
- }
-
- g_free (filename);
-}
-
-static EMsgComposer *
-autosave_load_draft (EShell *shell,
- const gchar *filename)
-{
- CamelStream *stream;
- CamelMimeMessage *message;
- EMsgComposer *composer;
-
- g_return_val_if_fail (E_IS_SHELL (shell), NULL);
- g_return_val_if_fail (filename != NULL, NULL);
-
- stream = camel_stream_fs_new_with_name (
- filename, O_RDONLY, 0, NULL);
- if (stream == NULL)
- return NULL;
-
- message = camel_mime_message_new ();
- camel_data_wrapper_construct_from_stream (
- CAMEL_DATA_WRAPPER (message), stream, NULL);
- g_object_unref (stream);
-
- composer = e_msg_composer_new_with_message (shell, message);
- if (composer) {
- /* Mark the message as changed so it gets autosaved again,
- * then we can safely remove the old autosave file in the
- * callback function. */
- gtkhtml_editor_set_changed (GTKHTML_EDITOR (composer), TRUE);
- e_composer_autosave_snapshot_async (
- composer, (GAsyncReadyCallback)
- autosave_load_draft_cb, g_strdup (filename));
-
- gtk_widget_show (GTK_WIDGET (composer));
- }
-
- return composer;
-}
-
/* Miscellaneous callbacks. */
static void
@@ -1830,7 +1766,6 @@ msg_composer_finalize (GObject *object)
{
EMsgComposer *composer = E_MSG_COMPOSER (object);
- e_composer_autosave_unregister (composer);
e_composer_private_finalize (composer);
/* Chain up to parent's finalize() method. */
@@ -1980,14 +1915,14 @@ msg_composer_constructed (GObject *object)
store, "row-inserted",
G_CALLBACK (attachment_store_changed_cb), composer);
- e_composer_autosave_register (composer);
-
/* Initialization may have tripped the "changed" state. */
gtkhtml_editor_set_changed (editor, FALSE);
id = "org.gnome.evolution.composer";
e_plugin_ui_register_manager (ui_manager, id, composer);
e_plugin_ui_enable_manager (ui_manager, id);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (composer));
}
static void
@@ -2068,7 +2003,7 @@ msg_composer_key_press_event (GtkWidget *widget,
#ifdef HAVE_XFREE
if (event->keyval == XF86XK_Send) {
- g_signal_emit (G_OBJECT (composer), signals[SEND], 0);
+ e_msg_composer_send (composer);
return TRUE;
}
#endif /* HAVE_XFREE */
@@ -3250,9 +3185,16 @@ e_msg_composer_get_shell (EMsgComposer *composer)
void
e_msg_composer_send (EMsgComposer *composer)
{
+ GtkhtmlEditor *editor;
+
g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+ editor = GTKHTML_EDITOR (composer);
+
g_signal_emit (composer, signals[SEND], 0);
+
+ /* XXX This should be elsewhere. */
+ gtkhtml_editor_set_changed (editor, FALSE);
}
/**
@@ -4093,13 +4035,6 @@ e_msg_composer_get_raw_message_text (EMsgComposer *composer)
return array;
}
-void
-e_msg_composer_set_enable_autosave (EMsgComposer *composer,
- gboolean enabled)
-{
- e_composer_autosave_set_enabled (composer, enabled);
-}
-
gboolean
e_msg_composer_is_exiting (EMsgComposer *composer)
{
@@ -4202,49 +4137,6 @@ e_msg_composer_load_from_file (EShell *shell,
}
void
-e_msg_composer_check_autosave (EShell *shell)
-{
- GtkWindow *parent;
- GList *orphans = NULL;
- gint response;
- GError *error = NULL;
-
- g_return_if_fail (E_IS_SHELL (shell));
-
- parent = e_shell_get_active_window (shell);
-
- /* Look for orphaned autosave files. */
- orphans = e_composer_autosave_find_orphans (&error);
- if (orphans == NULL) {
- if (error != NULL) {
- g_warning ("%s", error->message);
- g_error_free (error);
- }
- return;
- }
-
- /* Ask if the user wants to recover the orphaned files. */
- response = e_alert_run_dialog_for_args (
- parent, "mail-composer:recover-autosave", NULL);
-
- /* Based on the user's response, recover or delete them. */
- while (orphans != NULL) {
- const gchar *filename = orphans->data;
- EMsgComposer *composer;
-
- if (response == GTK_RESPONSE_YES) {
- /* FIXME: composer is never used */
- composer = autosave_load_draft (shell, filename);
- } else {
- g_unlink (filename);
- }
-
- g_free (orphans->data);
- orphans = g_list_delete_link (orphans, orphans);
- }
-}
-
-void
e_msg_composer_set_alternative (EMsgComposer *composer,
gboolean alt)
{
diff --git a/composer/e-msg-composer.h b/composer/e-msg-composer.h
index 4c51929f06..5cb439512c 100644
--- a/composer/e-msg-composer.h
+++ b/composer/e-msg-composer.h
@@ -133,10 +133,6 @@ CamelInternetAddress *
void e_msg_composer_clear_inlined_table
(EMsgComposer *composer);
-void e_msg_composer_set_enable_autosave
- (EMsgComposer *composer,
- gboolean enabled);
-
void e_msg_composer_add_message_attachments
(EMsgComposer *composer,
CamelMimeMessage *message,
@@ -148,7 +144,6 @@ gboolean e_msg_composer_can_close (EMsgComposer *composer,
EMsgComposer * e_msg_composer_load_from_file (EShell *shell,
const gchar *filename);
-void e_msg_composer_check_autosave (EShell *shell);
void e_msg_composer_reply_indent (EMsgComposer *composer);
diff --git a/configure.ac b/configure.ac
index e38d8c31ee..c7e2c8749a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1803,6 +1803,7 @@ modules/calendar/Makefile
modules/mail/Makefile
modules/mailto-handler/Makefile
modules/network-manager/Makefile
+modules/composer-autosave/Makefile
modules/connman/Makefile
modules/plugin-lib/Makefile
modules/plugin-mono/Makefile
diff --git a/mail/em-composer-utils.c b/mail/em-composer-utils.c
index aa2a550197..82ba4bcd5c 100644
--- a/mail/em-composer-utils.c
+++ b/mail/em-composer-utils.c
@@ -50,7 +50,6 @@
#include "em-composer-utils.h"
#include "composer/e-msg-composer.h"
#include "composer/e-composer-actions.h"
-#include "composer/e-composer-autosave.h"
#include "composer/e-composer-post-header.h"
#include "em-folder-selector.h"
#include "em-folder-tree.h"
@@ -255,10 +254,8 @@ composer_send_queued_cb (CamelFolder *folder, CamelMimeMessage *msg, CamelMessag
/* queue a message send */
mail_send ();
}
- } else {
- e_msg_composer_set_enable_autosave (send->composer, TRUE);
+ } else
gtk_widget_show (GTK_WIDGET (send->composer));
- }
camel_message_info_free (info);
@@ -555,8 +552,6 @@ em_utils_composer_send_cb (EMsgComposer *composer)
send->composer = g_object_ref (composer);
gtk_widget_hide (GTK_WIDGET (composer));
- e_msg_composer_set_enable_autosave (composer, FALSE);
-
mail_append_mail (
folder, message, info, composer_send_queued_cb, send);
diff --git a/modules/composer-autosave/Makefile.am b/modules/composer-autosave/Makefile.am
new file mode 100644
index 0000000000..0b802e11bf
--- /dev/null
+++ b/modules/composer-autosave/Makefile.am
@@ -0,0 +1,28 @@
+module_LTLIBRARIES = libevolution-module-composer-autosave.la
+
+libevolution_module_composer_autosave_la_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/widgets \
+ -DG_LOG_DOMAIN=\"evolution-composer-autosave\" \
+ $(EVOLUTION_MAIL_CFLAGS) \
+ $(GNOME_PLATFORM_CFLAGS)
+
+libevolution_module_composer_autosave_la_SOURCES = \
+ evolution-composer-autosave.c \
+ e-autosave-utils.c \
+ e-autosave-utils.h \
+ e-composer-autosave.c \
+ e-composer-registry.c
+
+libevolution_module_composer_autosave_la_LIBADD = \
+ $(top_builddir)/shell/libeshell.la \
+ $(top_builddir)/composer/libcomposer.la \
+ $(top_builddir)/widgets/misc/libemiscwidgets.la \
+ $(EVOLUTION_MAIL_LIBS) \
+ $(GNOME_PLATFORM_LIBS)
+
+libevolution_module_composer_autosave_la_LDFLAGS = \
+ -module -avoid-version $(NO_UNDEFINED)
+
+-include $(top_srcdir)/git.mk
diff --git a/modules/composer-autosave/e-autosave-utils.c b/modules/composer-autosave/e-autosave-utils.c
new file mode 100644
index 0000000000..ec0e322322
--- /dev/null
+++ b/modules/composer-autosave/e-autosave-utils.c
@@ -0,0 +1,499 @@
+/*
+ * e-autosave-utils.c
+ *
+ * This program 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 of the License, or (at your option) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include "e-autosave-utils.h"
+
+#include <errno.h>
+#include <glib/gstdio.h>
+#include <camel/camel.h>
+
+#include <e-util/e-util.h>
+
+#define SNAPSHOT_FILE_KEY "e-composer-snapshot-file"
+#define SNAPSHOT_FILE_PREFIX ".evolution-composer.autosave"
+#define SNAPSHOT_FILE_SEED SNAPSHOT_FILE_PREFIX "-XXXXXX"
+
+typedef struct _LoadContext LoadContext;
+typedef struct _SaveContext SaveContext;
+
+struct _LoadContext {
+ EMsgComposer *composer;
+};
+
+struct _SaveContext {
+ GCancellable *cancellable;
+};
+
+static void
+load_context_free (LoadContext *context)
+{
+ if (context->composer != NULL)
+ g_object_unref (context->composer);
+
+ g_slice_free (LoadContext, context);
+}
+
+static void
+save_context_free (SaveContext *context)
+{
+ if (context->cancellable != NULL)
+ g_object_unref (context->cancellable);
+
+ g_slice_free (SaveContext, context);
+}
+
+static void
+delete_snapshot_file (GFile *snapshot_file)
+{
+ g_file_delete (snapshot_file, NULL, NULL);
+ g_object_unref (snapshot_file);
+}
+
+static GFile *
+create_snapshot_file (EMsgComposer *composer,
+ GError **error)
+{
+ GFile *snapshot_file;
+ const gchar *user_data_dir;
+ gchar *path;
+ gint fd;
+
+ snapshot_file = e_composer_get_snapshot_file (composer);
+
+ if (G_IS_FILE (snapshot_file))
+ return snapshot_file;
+
+ user_data_dir = e_get_user_data_dir ();
+ path = g_build_filename (user_data_dir, SNAPSHOT_FILE_SEED, NULL);
+
+ /* g_mkstemp() modifies the XXXXXX part of the
+ * template string to form the actual filename. */
+ errno = 0;
+ fd = g_mkstemp (path);
+ if (fd == -1) {
+ g_set_error (
+ error, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+ g_free (path);
+ return FALSE;
+ }
+
+ close (fd);
+
+ snapshot_file = g_file_new_for_path (path);
+
+ /* Save the GFile for subsequent snapshots. */
+ g_object_set_data_full (
+ G_OBJECT (composer),
+ SNAPSHOT_FILE_KEY, snapshot_file,
+ (GDestroyNotify) delete_snapshot_file);
+
+ return snapshot_file;
+}
+
+static void
+load_snapshot_loaded_cb (GFile *snapshot_file,
+ GAsyncResult *result,
+ GSimpleAsyncResult *simple)
+{
+ EShell *shell;
+ GObject *object;
+ LoadContext *context;
+ EMsgComposer *composer;
+ CamelMimeMessage *message;
+ CamelStream *camel_stream;
+ gchar *contents = NULL;
+ gsize length;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ g_file_load_contents_finish (
+ snapshot_file, result, &contents, &length, NULL, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (contents == NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_error_free (error);
+ return;
+ }
+
+ /* Create an in-memory buffer for the MIME parser to read from.
+ * We have to do this because CamelStreams are syncrhonous-only,
+ * and feeding the parser a direct file stream would block. */
+ message = camel_mime_message_new ();
+ camel_stream = camel_stream_mem_new_with_buffer (contents, length);
+ camel_data_wrapper_construct_from_stream (
+ CAMEL_DATA_WRAPPER (message), camel_stream, &error);
+ g_object_unref (camel_stream);
+ g_free (contents);
+
+ if (error != NULL) {
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (message);
+ g_error_free (error);
+ return;
+ }
+
+ /* g_async_result_get_source_object() returns a new reference. */
+ object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
+
+ /* Create a new composer window from the loaded message and
+ * restore its snapshot file so it continues auto-saving to
+ * the same file. */
+ shell = E_SHELL (object);
+ g_object_ref (snapshot_file);
+ composer = e_msg_composer_new_with_message (shell, message);
+ g_object_set_data_full (
+ G_OBJECT (composer),
+ SNAPSHOT_FILE_KEY, snapshot_file,
+ (GDestroyNotify) delete_snapshot_file);
+ context->composer = g_object_ref_sink (composer);
+ g_object_unref (message);
+
+ g_object_unref (object);
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+save_snapshot_splice_cb (GOutputStream *output_stream,
+ GAsyncResult *result,
+ GSimpleAsyncResult *simple)
+{
+ GError *error = NULL;
+
+ g_output_stream_splice_finish (output_stream, result, &error);
+
+ if (error != NULL) {
+ g_simple_async_result_set_from_error (simple, error);
+ g_error_free (error);
+ }
+
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+}
+
+static void
+save_snapshot_replace_cb (GFile *snapshot_file,
+ GAsyncResult *result,
+ GSimpleAsyncResult *simple)
+{
+ GObject *object;
+ EMsgComposer *composer;
+ SaveContext *context;
+ CamelMimeMessage *message;
+ GFileOutputStream *output_stream;
+ GInputStream *input_stream;
+ CamelStream *camel_stream;
+ GByteArray *buffer;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ output_stream = g_file_replace_finish (snapshot_file, result, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (output_stream == NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (G_IS_OUTPUT_STREAM (output_stream));
+
+ /* g_async_result_get_source_object() returns a new reference. */
+ object = g_async_result_get_source_object (G_ASYNC_RESULT (simple));
+
+ /* Extract a MIME message from the composer. */
+ composer = E_MSG_COMPOSER (object);
+ message = e_msg_composer_get_message_draft (composer, &error);
+
+ g_object_unref (object);
+
+ if (error != NULL) {
+ g_warn_if_fail (message == NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (output_stream);
+ g_object_unref (simple);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ /* Decode the message to an in-memory buffer. We have to do this
+ * because CamelStreams are synchronous-only, and using threads 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, NULL);
+ g_object_unref (camel_stream);
+ g_object_unref (message);
+
+ /* Load the buffer into a GMemoryInputStream. */
+ 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);
+
+ /* 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, context->cancellable,
+ (GAsyncReadyCallback) save_snapshot_splice_cb,
+ simple);
+
+ g_object_unref (input_stream);
+ g_object_unref (output_stream);
+}
+
+static EMsgComposer *
+composer_registry_lookup (GQueue *registry,
+ const gchar *basename)
+{
+ GList *iter;
+
+ /* Find the composer with the given snapshot filename. */
+ for (iter = registry->head; iter != NULL; iter = iter->next) {
+ EMsgComposer *composer;
+ GFile *snapshot_file;
+ gchar *snapshot_name;
+
+ composer = E_MSG_COMPOSER (iter->data);
+ snapshot_file = e_composer_get_snapshot_file (composer);
+
+ if (!G_IS_FILE (snapshot_file))
+ continue;
+
+ snapshot_name = g_file_get_basename (snapshot_file);
+ if (g_strcmp0 (basename, snapshot_name) == 0) {
+ g_free (snapshot_name);
+ return composer;
+ }
+
+ g_free (snapshot_name);
+ }
+
+ return NULL;
+}
+
+GList *
+e_composer_find_orphans (GQueue *registry,
+ GError **error)
+{
+ GDir *dir;
+ const gchar *dirname;
+ const gchar *basename;
+ GList *orphans = NULL;
+
+ g_return_val_if_fail (registry != NULL, NULL);
+
+ dirname = e_get_user_data_dir ();
+ dir = g_dir_open (dirname, 0, error);
+ if (dir == NULL)
+ return NULL;
+
+ /* Scan the user data directory for snapshot files. */
+ while ((basename = g_dir_read_name (dir)) != NULL) {
+ const gchar *errmsg;
+ gchar *filename;
+ struct stat st;
+
+ /* Is this a snapshot file? */
+ if (!g_str_has_prefix (basename, SNAPSHOT_FILE_PREFIX))
+ continue;
+
+ /* Is this an orphaned snapshot file? */
+ if (composer_registry_lookup (registry, basename) != NULL)
+ continue;
+
+ filename = g_build_filename (dirname, basename, NULL);
+
+ /* Try to examine the snapshot file. Failure here
+ * is non-fatal; just emit a warning and move on. */
+ errno = 0;
+ if (g_stat (filename, &st) < 0) {
+ errmsg = g_strerror (errno);
+ g_warning ("%s: %s", filename, errmsg);
+ g_free (filename);
+ continue;
+ }
+
+ /* If the file is empty, delete it. Failure here
+ * is non-fatal; just emit a warning and move on. */
+ if (st.st_size == 0) {
+ errno = 0;
+ if (g_unlink (filename) < 0) {
+ errmsg = g_strerror (errno);
+ g_warning ("%s: %s", filename, errmsg);
+ }
+ g_free (filename);
+ continue;
+ }
+
+ orphans = g_list_prepend (
+ orphans, g_file_new_for_path (filename));
+
+ g_free (filename);
+ }
+
+ g_dir_close (dir);
+
+ return g_list_reverse (orphans);
+}
+
+void
+e_composer_load_snapshot (EShell *shell,
+ GFile *snapshot_file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ LoadContext *context;
+
+ g_return_if_fail (E_IS_SHELL (shell));
+ g_return_if_fail (G_IS_FILE (snapshot_file));
+
+ context = g_slice_new0 (LoadContext);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (shell), callback, user_data,
+ e_composer_load_snapshot);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) load_context_free);
+
+ g_file_load_contents_async (
+ snapshot_file, cancellable, (GAsyncReadyCallback)
+ load_snapshot_loaded_cb, simple);
+}
+
+EMsgComposer *
+e_composer_load_snapshot_finish (EShell *shell,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ LoadContext *context;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (shell),
+ e_composer_load_snapshot), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ return NULL;
+
+ g_return_val_if_fail (E_IS_MSG_COMPOSER (context->composer), NULL);
+
+ return g_object_ref (context->composer);
+}
+
+void
+e_composer_save_snapshot (EMsgComposer *composer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ SaveContext *context;
+ GFile *snapshot_file;
+ GError *error = NULL;
+
+ g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+ context = g_slice_new0 (SaveContext);
+
+ if (G_IS_CANCELLABLE (cancellable))
+ context->cancellable = g_object_ref (cancellable);
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (composer), callback, user_data,
+ e_composer_save_snapshot);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) save_context_free);
+
+ snapshot_file = e_composer_get_snapshot_file (composer);
+
+ if (!G_IS_FILE (snapshot_file))
+ snapshot_file = create_snapshot_file (composer, &error);
+
+ if (error != NULL) {
+ g_warn_if_fail (snapshot_file == NULL);
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ g_object_unref (simple);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (G_IS_FILE (snapshot_file));
+
+ g_file_replace_async (
+ snapshot_file, NULL, FALSE,
+ G_FILE_CREATE_PRIVATE, G_PRIORITY_DEFAULT,
+ context->cancellable, (GAsyncReadyCallback)
+ save_snapshot_replace_cb, simple);
+}
+
+gboolean
+e_composer_save_snapshot_finish (EMsgComposer *composer,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+
+ g_return_val_if_fail (
+ g_simple_async_result_is_valid (
+ result, G_OBJECT (composer),
+ e_composer_save_snapshot), FALSE);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+
+ /* Success is assumed unless a GError is set. */
+ return !g_simple_async_result_propagate_error (simple, error);
+}
+
+GFile *
+e_composer_get_snapshot_file (EMsgComposer *composer)
+{
+ g_return_val_if_fail (E_IS_MSG_COMPOSER (composer), NULL);
+
+ return g_object_get_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
+}
diff --git a/modules/composer-autosave/e-autosave-utils.h b/modules/composer-autosave/e-autosave-utils.h
new file mode 100644
index 0000000000..3a33d6b90a
--- /dev/null
+++ b/modules/composer-autosave/e-autosave-utils.h
@@ -0,0 +1,48 @@
+/*
+ * e-autosave-utils.h
+ *
+ * This program 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 of the License, or (at your option) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#ifndef E_AUTOSAVE_UTILS_H
+#define E_AUTOSAVE_UTILS_H
+
+#include <shell/e-shell.h>
+#include <composer/e-msg-composer.h>
+
+G_BEGIN_DECLS
+
+GList * e_composer_find_orphans (GQueue *registry,
+ GError **error);
+void e_composer_load_snapshot (EShell *shell,
+ GFile *snapshot_file,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+EMsgComposer * e_composer_load_snapshot_finish (EShell *shell,
+ GAsyncResult *result,
+ GError **error);
+void e_composer_save_snapshot (EMsgComposer *composer,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean e_composer_save_snapshot_finish (EMsgComposer *composer,
+ GAsyncResult *result,
+ GError **error);
+GFile * e_composer_get_snapshot_file (EMsgComposer *composer);
+
+G_END_DECLS
+
+#endif /* E_AUTOSAVE_UTILS_H */
diff --git a/modules/composer-autosave/e-composer-autosave.c b/modules/composer-autosave/e-composer-autosave.c
new file mode 100644
index 0000000000..b0a22ad4cf
--- /dev/null
+++ b/modules/composer-autosave/e-composer-autosave.c
@@ -0,0 +1,227 @@
+/*
+ * e-composer-autosave.c
+ *
+ * This program 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 of the License, or (at your option) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <e-util/e-extension.h>
+#include <e-util/e-alert-dialog.h>
+#include <composer/e-msg-composer.h>
+
+#include "e-autosave-utils.h"
+
+/* Standard GObject macros */
+#define E_TYPE_COMPOSER_AUTOSAVE \
+ (e_composer_autosave_get_type ())
+#define E_COMPOSER_AUTOSAVE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_COMPOSER_AUTOSAVE, EComposerAutosave))
+
+#define AUTOSAVE_INTERVAL 60 /* seconds */
+
+typedef struct _EComposerAutosave EComposerAutosave;
+typedef struct _EComposerAutosaveClass EComposerAutosaveClass;
+
+struct _EComposerAutosave {
+ EExtension parent;
+
+ GCancellable *cancellable;
+ guint timeout_id;
+
+ /* Composer contents have changed since
+ * the last auto-save or explicit save. */
+ gboolean changed;
+
+ /* Prevent error dialogs from piling up. */
+ gboolean error_shown;
+};
+
+struct _EComposerAutosaveClass {
+ EExtensionClass parent_class;
+};
+
+/* Forward Declarations */
+GType e_composer_autosave_get_type (void);
+void e_composer_autosave_type_register (GTypeModule *type_module);
+
+G_DEFINE_DYNAMIC_TYPE (
+ EComposerAutosave,
+ e_composer_autosave,
+ E_TYPE_EXTENSION)
+
+static void
+composer_autosave_finished_cb (EMsgComposer *composer,
+ GAsyncResult *result,
+ EComposerAutosave *autosave)
+{
+ GFile *snapshot_file;
+ GError *error = NULL;
+
+ snapshot_file = e_composer_get_snapshot_file (composer);
+ e_composer_save_snapshot_finish (composer, result, &error);
+
+ /* Return silently if we were cancelled. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_error_free (error);
+
+ else if (error != NULL) {
+ gchar *basename;
+
+ if (G_IS_FILE (snapshot_file))
+ basename = g_file_get_basename (snapshot_file);
+ else
+ basename = g_strdup (" ");
+
+ /* Only show one error dialog at a time. */
+ if (!autosave->error_shown) {
+ autosave->error_shown = TRUE;
+ e_alert_run_dialog_for_args (
+ GTK_WINDOW (composer),
+ "mail-composer:no-autosave",
+ basename, error->message, NULL);
+ autosave->error_shown = FALSE;
+ } else
+ g_warning ("%s: %s", basename, error->message);
+
+ g_free (basename);
+ g_error_free (error);
+ }
+
+ g_object_unref (autosave);
+}
+
+static gboolean
+composer_autosave_timeout_cb (EComposerAutosave *autosave)
+{
+ EExtensible *extensible;
+
+ extensible = e_extension_get_extensible (E_EXTENSION (autosave));
+
+ /* User may have reverted or explicitly saved
+ * the changes since the timeout was scheduled. */
+ if (autosave->changed) {
+
+ /* Cancel the previous snapshot if it's still in
+ * progress and start a new snapshot operation. */
+ g_cancellable_cancel (autosave->cancellable);
+ g_object_unref (autosave->cancellable);
+ autosave->cancellable = g_cancellable_new ();
+
+ e_composer_save_snapshot (
+ E_MSG_COMPOSER (extensible),
+ autosave->cancellable,
+ (GAsyncReadyCallback)
+ composer_autosave_finished_cb,
+ g_object_ref (autosave));
+ }
+
+ autosave->timeout_id = 0;
+ autosave->changed = FALSE;
+
+ return FALSE;
+}
+
+static void
+composer_autosave_changed_cb (EComposerAutosave *autosave)
+{
+ GtkhtmlEditor *editor;
+ EExtensible *extensible;
+
+ extensible = e_extension_get_extensible (E_EXTENSION (autosave));
+
+ editor = GTKHTML_EDITOR (extensible);
+ autosave->changed = gtkhtml_editor_get_changed (editor);
+
+ if (autosave->changed && autosave->timeout_id == 0)
+ autosave->timeout_id = g_timeout_add_seconds (
+ AUTOSAVE_INTERVAL, (GSourceFunc)
+ composer_autosave_timeout_cb, autosave);
+}
+
+static void
+composer_autosave_dispose (GObject *object)
+{
+ EComposerAutosave *autosave;
+ GObjectClass *parent_class;
+
+ autosave = E_COMPOSER_AUTOSAVE (object);
+
+ /* Cancel any snapshots in progress. */
+ if (autosave->cancellable != NULL) {
+ g_cancellable_cancel (autosave->cancellable);
+ g_object_unref (autosave->cancellable);
+ autosave->cancellable = NULL;
+ }
+
+ if (autosave->timeout_id > 0) {
+ g_source_remove (autosave->timeout_id);
+ autosave->timeout_id = 0;
+ }
+
+ /* Chain up to parent's dispose() method. */
+ parent_class = G_OBJECT_CLASS (e_composer_autosave_parent_class);
+ parent_class->dispose (object);
+}
+
+static void
+composer_autosave_constructed (GObject *object)
+{
+ EExtensible *extensible;
+ GObjectClass *parent_class;
+
+ /* Chain up to parent's constructed() method. */
+ parent_class = G_OBJECT_CLASS (e_composer_autosave_parent_class);
+ parent_class->constructed (object);
+
+ extensible = e_extension_get_extensible (E_EXTENSION (object));
+
+ g_signal_connect_swapped (
+ extensible, "notify::changed",
+ G_CALLBACK (composer_autosave_changed_cb), object);
+}
+
+static void
+e_composer_autosave_class_init (EComposerAutosaveClass *class)
+{
+ GObjectClass *object_class;
+ EExtensionClass *extension_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->dispose = composer_autosave_dispose;
+ object_class->constructed = composer_autosave_constructed;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_MSG_COMPOSER;
+}
+
+static void
+e_composer_autosave_class_finalize (EComposerAutosaveClass *class)
+{
+}
+
+static void
+e_composer_autosave_init (EComposerAutosave *autosave)
+{
+ autosave->cancellable = g_cancellable_new ();
+}
+
+void
+e_composer_autosave_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_composer_autosave_register_type (type_module);
+}
diff --git a/modules/composer-autosave/e-composer-registry.c b/modules/composer-autosave/e-composer-registry.c
new file mode 100644
index 0000000000..a48464addd
--- /dev/null
+++ b/modules/composer-autosave/e-composer-registry.c
@@ -0,0 +1,236 @@
+/*
+ * e-composer-registry.c
+ *
+ * This program 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 of the License, or (at your option) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <glib/gstdio.h>
+#include <shell/e-shell.h>
+#include <shell/e-shell-window.h>
+#include <e-util/e-extension.h>
+#include <e-util/e-alert-dialog.h>
+#include <composer/e-msg-composer.h>
+
+#include "e-autosave-utils.h"
+
+/* Standard GObject macros */
+#define E_TYPE_COMPOSER_REGISTRY \
+ (e_composer_registry_get_type ())
+#define E_COMPOSER_REGISTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_COMPOSER_REGISTRY, EComposerRegistry))
+
+typedef struct _EComposerRegistry EComposerRegistry;
+typedef struct _EComposerRegistryClass EComposerRegistryClass;
+
+struct _EComposerRegistry {
+ EExtension parent;
+ GQueue composers;
+ gboolean orphans_restored;
+};
+
+struct _EComposerRegistryClass {
+ EExtensionClass parent_class;
+};
+
+/* Forward Declarations */
+GType e_composer_registry_get_type (void);
+void e_composer_registry_type_register (GTypeModule *type_module);
+
+G_DEFINE_DYNAMIC_TYPE (
+ EComposerRegistry,
+ e_composer_registry,
+ E_TYPE_EXTENSION)
+
+static void
+composer_registry_recovered_cb (EShell *shell,
+ GAsyncResult *result,
+ EComposerRegistry *registry)
+{
+ EMsgComposer *composer;
+ GError *error = NULL;
+
+ composer = e_composer_load_snapshot_finish (shell, result, &error);
+
+ if (error != NULL) {
+ /* FIXME Show an alert dialog here explaining
+ * why we could not recover the message.
+ * Will need a new error XML entry. */
+ g_warn_if_fail (composer == NULL);
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ goto exit;
+ }
+
+ gtk_widget_show (GTK_WIDGET (composer));
+
+ g_object_unref (composer);
+
+exit:
+ g_object_unref (registry);
+}
+
+static gboolean
+composer_registry_map_event_cb (GtkWindow *parent,
+ GdkEvent *event,
+ EComposerRegistry *registry)
+{
+ EExtensible *extensible;
+ GList *orphans;
+ gint response;
+ GError *error = NULL;
+
+ extensible = e_extension_get_extensible (E_EXTENSION (registry));
+
+ /* Look for orphaned auto-save files. */
+ orphans = e_composer_find_orphans (
+ &registry->composers, &error);
+ if (orphans == NULL) {
+ if (error != NULL) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+ }
+ goto exit;
+ }
+
+ /* Ask if the user wants to recover the orphaned files. */
+ response = e_alert_run_dialog_for_args (
+ parent, "mail-composer:recover-autosave", NULL);
+
+ /* Based on the user's reponse, recover or delete them. */
+ while (orphans != NULL) {
+ GFile *file = orphans->data;
+
+ if (response == GTK_RESPONSE_YES)
+ e_composer_load_snapshot (
+ E_SHELL (extensible),
+ file, NULL, (GAsyncReadyCallback)
+ composer_registry_recovered_cb,
+ g_object_ref (registry));
+ else
+ g_file_delete (file, NULL, NULL);
+
+ g_object_unref (file);
+
+ orphans = g_list_delete_link (orphans, orphans);
+ }
+
+exit:
+ registry->orphans_restored = TRUE;
+
+ return FALSE;
+}
+
+static void
+composer_registry_notify_cb (EComposerRegistry *registry,
+ GObject *where_the_object_was)
+{
+ /* Remove the finalized composer from the registry. */
+ g_queue_remove (&registry->composers, where_the_object_was);
+
+ g_object_unref (registry);
+}
+
+static void
+composer_registry_window_created_cb (EShell *shell,
+ GtkWindow *window,
+ EComposerRegistry *registry)
+{
+ /* Offer to restore any orphaned auto-save files from the
+ * previous session once the first EShellWindow is mapped. */
+ if (E_IS_SHELL_WINDOW (window) && !registry->orphans_restored)
+ g_signal_connect (
+ window, "map-event",
+ G_CALLBACK (composer_registry_map_event_cb),
+ registry);
+
+ /* Track the new composer window. */
+ else if (E_IS_MSG_COMPOSER (window)) {
+ g_queue_push_tail (&registry->composers, window);
+ g_object_weak_ref (
+ G_OBJECT (window), (GWeakNotify)
+ composer_registry_notify_cb,
+ g_object_ref (registry));
+ }
+}
+
+static void
+composer_registry_finalize (GObject *object)
+{
+ GObjectClass *parent_class;
+ EComposerRegistry *registry;
+
+ registry = E_COMPOSER_REGISTRY (object);
+
+ /* All composers should have been finalized by now. */
+ g_warn_if_fail (g_queue_is_empty (&registry->composers));
+
+ /* Chain up to parent's finalize() method. */
+ parent_class = G_OBJECT_CLASS (e_composer_registry_parent_class);
+ parent_class->finalize (object);
+}
+
+static void
+composer_registry_constructed (GObject *object)
+{
+ EExtensible *extensible;
+ GObjectClass *parent_class;
+
+ /* Chain up to parent's constructed() method. */
+ parent_class = G_OBJECT_CLASS (e_composer_registry_parent_class);
+ parent_class->constructed (object);
+
+ extensible = e_extension_get_extensible (E_EXTENSION (object));
+
+ /* Listen for new watched windows. */
+ g_signal_connect (
+ extensible, "window-created",
+ G_CALLBACK (composer_registry_window_created_cb),
+ object);
+}
+
+static void
+e_composer_registry_class_init (EComposerRegistryClass *class)
+{
+ GObjectClass *object_class;
+ EExtensionClass *extension_class;
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->finalize = composer_registry_finalize;
+ object_class->constructed = composer_registry_constructed;
+
+ extension_class = E_EXTENSION_CLASS (class);
+ extension_class->extensible_type = E_TYPE_SHELL;
+}
+
+static void
+e_composer_registry_class_finalize (EComposerRegistryClass *class)
+{
+}
+
+static void
+e_composer_registry_init (EComposerRegistry *registry)
+{
+ g_queue_init (&registry->composers);
+}
+
+void
+e_composer_registry_type_register (GTypeModule *type_module)
+{
+ /* XXX G_DEFINE_DYNAMIC_TYPE declares a static type registration
+ * function, so we have to wrap it with a public function in
+ * order to register types from a separate compilation unit. */
+ e_composer_registry_register_type (type_module);
+}
diff --git a/modules/composer-autosave/evolution-composer-autosave.c b/modules/composer-autosave/evolution-composer-autosave.c
new file mode 100644
index 0000000000..d7e32f2f69
--- /dev/null
+++ b/modules/composer-autosave/evolution-composer-autosave.c
@@ -0,0 +1,40 @@
+/*
+ * evolution-module-composer-autosave.c
+ *
+ * This program 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 of the License, or (at your option) version 3.
+ *
+ * This program 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 the program; if not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+#include <gmodule.h>
+#include <glib-object.h>
+
+/* Module Entry Points */
+void e_module_load (GTypeModule *type_module);
+void e_module_unload (GTypeModule *type_module);
+
+/* Forward Declarations */
+void e_composer_autosave_type_register (GTypeModule *type_module);
+void e_composer_registry_type_register (GTypeModule *type_module);
+
+G_MODULE_EXPORT void
+e_module_load (GTypeModule *type_module)
+{
+ e_composer_autosave_type_register (type_module);
+ e_composer_registry_type_register (type_module);
+}
+
+G_MODULE_EXPORT void
+e_module_unload (GTypeModule *type_module)
+{
+}
diff --git a/modules/mail/e-mail-shell-backend.c b/modules/mail/e-mail-shell-backend.c
index 870437aeb6..f1696692dd 100644
--- a/modules/mail/e-mail-shell-backend.c
+++ b/modules/mail/e-mail-shell-backend.c
@@ -375,7 +375,6 @@ mail_shell_backend_window_created_cb (EShell *shell,
GtkWindow *window,
EShellBackend *shell_backend)
{
- static gboolean first_time = TRUE;
const gchar *backend_name;
/* This applies to both the composer and signature editor. */
@@ -425,13 +424,6 @@ mail_shell_backend_window_created_cb (EShell *shell,
g_object_weak_ref (
G_OBJECT (window), (GWeakNotify)
mail_shell_backend_window_weak_notify_cb, shell);
-
- if (first_time) {
- g_signal_connect_swapped (
- window, "map-event",
- G_CALLBACK (e_msg_composer_check_autosave), shell);
- first_time = FALSE;
- }
}
static void
diff --git a/shell/e-shell.c b/shell/e-shell.c
index fcb3d9abf3..db9efc9331 100644
--- a/shell/e-shell.c
+++ b/shell/e-shell.c
@@ -180,6 +180,16 @@ shell_window_focus_in_event_cb (EShell *shell,
return FALSE;
}
+static gboolean
+shell_emit_window_destroyed_cb (EShell *shell)
+{
+ g_signal_emit (shell, signals[WINDOW_DESTROYED], 0);
+
+ g_object_unref (shell);
+
+ return FALSE;
+}
+
static void
shell_window_weak_notify_cb (EShell *shell,
GObject *where_the_object_was)
@@ -190,7 +200,12 @@ shell_window_weak_notify_cb (EShell *shell,
list = g_list_remove (list, where_the_object_was);
shell->priv->watched_windows = list;
- g_signal_emit (shell, signals[WINDOW_DESTROYED], 0);
+ /* Let the watched window finish finalizing itself before we
+ * emit the "window-destroyed" signal, which may trigger the
+ * application to initiate shutdown. */
+ g_idle_add (
+ (GSourceFunc) shell_emit_window_destroyed_cb,
+ g_object_ref (shell));
}
static void