[evolution] Convert composer autosave to an EExtension.



commit 429234ff213ba04b6d0b02a28ed68aaa8af7c02c
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu Sep 2 13:21:08 2010 -0400

    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.

 composer/Makefile.am                               |    2 -
 composer/e-composer-autosave.c                     |  511 --------------------
 composer/e-composer-autosave.h                     |   45 --
 composer/e-composer-private.h                      |    1 -
 composer/e-msg-composer.c                          |  142 +-----
 composer/e-msg-composer.h                          |    5 -
 configure.ac                                       |    1 +
 mail/em-composer-utils.c                           |    7 +-
 modules/composer-autosave/Makefile.am              |   28 ++
 modules/composer-autosave/e-autosave-utils.c       |  499 +++++++++++++++++++
 modules/composer-autosave/e-autosave-utils.h       |   48 ++
 modules/composer-autosave/e-composer-autosave.c    |  227 +++++++++
 modules/composer-autosave/e-composer-registry.c    |  236 +++++++++
 .../evolution-composer-autosave.c                  |   40 ++
 modules/mail/e-mail-shell-backend.c                |    8 -
 shell/e-shell.c                                    |   17 +-
 16 files changed, 1113 insertions(+), 704 deletions(-)
---
diff --git a/composer/Makefile.am b/composer/Makefile.am
index 68a7ec1..5caea41 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-private.h b/composer/e-composer-private.h
index 8d14089..026ed96 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 6552367..480bfbe 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 4c51929..5cb4395 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 e38d8c3..c7e2c87 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 aa2a550..82ba4bc 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 0000000..0b802e1
--- /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 0000000..ec0e322
--- /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 0000000..3a33d6b
--- /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 0000000..b0a22ad
--- /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 0000000..a48464a
--- /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 0000000..d7e32f2
--- /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 870437a..f169669 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 fcb3d9a..db9efc9 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



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]