From 429234ff213ba04b6d0b02a28ed68aaa8af7c02c Mon Sep 17 00:00:00 2001 From: Matthew Barnes Date: Thu, 2 Sep 2010 13:21:08 -0400 Subject: 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. --- modules/composer-autosave/e-composer-autosave.c | 227 ++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 modules/composer-autosave/e-composer-autosave.c (limited to 'modules/composer-autosave/e-composer-autosave.c') 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 + * + */ + +#include +#include +#include + +#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); +} -- cgit v1.2.3