[evolution] [Composer] Better care of autosave snapshot after WebKitWebProcess crash



commit d93768c9213584a4809af17d42bbc441e11a365e
Author: Milan Crha <mcrha redhat com>
Date:   Thu Dec 7 18:29:38 2017 +0100

    [Composer] Better care of autosave snapshot after WebKitWebProcess crash
    
    This is related to bug 791291, but doesn't fix it, it rather makes sure that:
    a) any autosave snapshot is not overwritten with empty body of the message
    b) the snapshot is not deleted after "broken" composer close
    c) it's offered to recover the last snapshot (if exists) on close of the composer.
    
    This all makes sure that users will not lose all their work, only up
    to one minute of time, which is the interval of the autosave.

 src/composer/e-composer-actions.c                  |    4 +-
 src/composer/e-composer-private.h                  |    1 +
 src/composer/e-msg-composer.c                      |   22 +++++-
 src/composer/mail-composer.error.xml               |    7 ++
 src/e-util/e-content-editor.c                      |   28 ++++++
 src/e-util/e-content-editor.h                      |    1 +
 src/modules/composer-autosave/e-autosave-utils.c   |   28 ++++++
 src/modules/composer-autosave/e-autosave-utils.h   |    4 +
 .../composer-autosave/e-composer-autosave.c        |   91 +++++++++++++++++++-
 .../composer-autosave/e-composer-registry.c        |    2 +-
 src/modules/webkit-editor/e-webkit-editor.c        |   22 +++++
 11 files changed, 206 insertions(+), 4 deletions(-)
---
diff --git a/src/composer/e-composer-actions.c b/src/composer/e-composer-actions.c
index 3abc62e..d305c6f 100644
--- a/src/composer/e-composer-actions.c
+++ b/src/composer/e-composer-actions.c
@@ -58,8 +58,10 @@ static void
 action_close_cb (GtkAction *action,
                  EMsgComposer *composer)
 {
-       if (e_msg_composer_can_close (composer, TRUE))
+       if (e_msg_composer_can_close (composer, TRUE)) {
+               e_composer_emit_before_destroy (composer);
                gtk_widget_destroy (GTK_WIDGET (composer));
+       }
 }
 
 static void
diff --git a/src/composer/e-composer-private.h b/src/composer/e-composer-private.h
index f8a06b6..c590aae 100644
--- a/src/composer/e-composer-private.h
+++ b/src/composer/e-composer-private.h
@@ -142,6 +142,7 @@ gboolean    e_composer_selection_is_image_uris
                                                (EMsgComposer *composer,
                                                 GtkSelectionData *selection);
 void           e_composer_update_signature     (EMsgComposer *composer);
+void           e_composer_emit_before_destroy  (EMsgComposer *composer);
 
 G_END_DECLS
 
diff --git a/src/composer/e-msg-composer.c b/src/composer/e-msg-composer.c
index 43fd974..dbadf7a 100644
--- a/src/composer/e-msg-composer.c
+++ b/src/composer/e-msg-composer.c
@@ -101,6 +101,7 @@ enum {
        SAVE_TO_DRAFTS,
        SAVE_TO_OUTBOX,
        PRINT,
+       BEFORE_DESTROY,
        LAST_SIGNAL
 };
 
@@ -2746,6 +2747,23 @@ e_msg_composer_class_init (EMsgComposerClass *class)
                GTK_TYPE_PRINT_OPERATION_ACTION,
                CAMEL_TYPE_MIME_MESSAGE,
                E_TYPE_ACTIVITY);
+
+       signals[BEFORE_DESTROY] = g_signal_new (
+               "before-destroy",
+               G_OBJECT_CLASS_TYPE (class),
+               G_SIGNAL_RUN_LAST,
+               0, NULL, NULL,
+               g_cclosure_marshal_VOID__VOID,
+               G_TYPE_NONE, 0,
+               G_TYPE_NONE);
+}
+
+void
+e_composer_emit_before_destroy (EMsgComposer *composer)
+{
+       g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+       g_signal_emit (composer, signals[BEFORE_DESTROY], 0);
 }
 
 static void
@@ -4016,6 +4034,7 @@ msg_composer_save_to_drafts_done_cb (gpointer user_data,
 
        if (e_msg_composer_is_exiting (composer) &&
            !e_content_editor_get_changed (cnt_editor)) {
+               e_composer_emit_before_destroy (composer);
                gtk_widget_destroy (GTK_WIDGET (composer));
        } else if (e_msg_composer_is_exiting (composer)) {
                gtk_widget_set_sensitive (GTK_WIDGET (composer), TRUE);
@@ -5366,7 +5385,8 @@ e_msg_composer_can_close (EMsgComposer *composer,
        if (!gtk_action_group_get_sensitive (composer->priv->async_actions))
                return FALSE;
 
-       if (!e_content_editor_get_changed (cnt_editor))
+       if (!e_content_editor_get_changed (cnt_editor) ||
+           e_content_editor_is_malfunction (cnt_editor))
                return TRUE;
 
        window = gtk_widget_get_window (widget);
diff --git a/src/composer/mail-composer.error.xml b/src/composer/mail-composer.error.xml
index 8e4f5a7..c6a7dd2 100644
--- a/src/composer/mail-composer.error.xml
+++ b/src/composer/mail-composer.error.xml
@@ -23,6 +23,13 @@
   <button _label="_Recover" response="GTK_RESPONSE_YES"/>
  </error>
 
+ <error id="recover-autosave-malfunction" type="question" default="GTK_RESPONSE_YES">
+  <_primary>Do you want to recover last saved version of the message?</_primary>
+  <_secondary>Recovering the message will allow you to continue where it had been saved the last 
time.</_secondary>
+  <button _label="_Do not Recover" response="GTK_RESPONSE_CANCEL"/>
+  <button _label="_Recover" response="GTK_RESPONSE_YES"/>
+ </error>
+
  <error id="no-autosave" type="error">
   <_primary>Could not save to autosave file “{0}”.</_primary>
   <_secondary>Error saving to autosave because “{1}”.</_secondary>
diff --git a/src/e-util/e-content-editor.c b/src/e-util/e-content-editor.c
index 45ae992..ba70189 100644
--- a/src/e-util/e-content-editor.c
+++ b/src/e-util/e-content-editor.c
@@ -45,6 +45,22 @@ static void
 e_content_editor_default_init (EContentEditorInterface *iface)
 {
        /**
+        * EContentEditor:is-malfunction
+        *
+        * Determines whether the composer is malfunction. If it does, then
+        * the result of calling functions like get_content() is undefined.
+        */
+       g_object_interface_install_property (
+               iface,
+               g_param_spec_boolean (
+                       "is-malfunction",
+                       "Is Malfunction",
+                       NULL,
+                       FALSE,
+                       G_PARAM_READABLE |
+                       G_PARAM_STATIC_STRINGS));
+
+       /**
         * EContentEditor:can-copy
         *
         * Determines whether it's possible to copy to clipboard. The action
@@ -612,6 +628,18 @@ e_content_editor_ref_spell_checker (EContentEditor *editor)
 }
 
 gboolean
+e_content_editor_is_malfunction (EContentEditor *editor)
+{
+       gboolean value = FALSE;
+
+       g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), FALSE);
+
+       g_object_get (G_OBJECT (editor), "is-malfunction", &value, NULL);
+
+       return value;
+}
+
+gboolean
 e_content_editor_can_cut (EContentEditor *editor)
 {
        gboolean value = FALSE;
diff --git a/src/e-util/e-content-editor.h b/src/e-util/e-content-editor.h
index dd9744e..49aa658 100644
--- a/src/e-util/e-content-editor.h
+++ b/src/e-util/e-content-editor.h
@@ -446,6 +446,7 @@ struct _EContentEditorInterface {
 
 ESpellChecker *        e_content_editor_ref_spell_checker
                                                (EContentEditor *editor);
+gboolean       e_content_editor_is_malfunction (EContentEditor *editor);
 gboolean       e_content_editor_can_cut        (EContentEditor *editor);
 gboolean       e_content_editor_can_copy       (EContentEditor *editor);
 gboolean       e_content_editor_can_paste      (EContentEditor *editor);
diff --git a/src/modules/composer-autosave/e-autosave-utils.c 
b/src/modules/composer-autosave/e-autosave-utils.c
index e4e307a..bd7035a 100644
--- a/src/modules/composer-autosave/e-autosave-utils.c
+++ b/src/modules/composer-autosave/e-autosave-utils.c
@@ -556,3 +556,31 @@ e_composer_get_snapshot_file (EMsgComposer *composer)
 
        return g_object_get_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
 }
+
+void
+e_composer_prevent_snapshot_file_delete (EMsgComposer *composer)
+{
+       GFile *snapshot_file;
+
+       g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+       snapshot_file = g_object_steal_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
+       if (snapshot_file) {
+               g_object_set_data_full (G_OBJECT (composer), SNAPSHOT_FILE_KEY,
+                       snapshot_file, g_object_unref);
+       }
+}
+
+void
+e_composer_allow_snapshot_file_delete (EMsgComposer *composer)
+{
+       GFile *snapshot_file;
+
+       g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+       snapshot_file = g_object_steal_data (G_OBJECT (composer), SNAPSHOT_FILE_KEY);
+       if (snapshot_file) {
+               g_object_set_data_full (G_OBJECT (composer), SNAPSHOT_FILE_KEY,
+                       snapshot_file, (GDestroyNotify) delete_snapshot_file);
+       }
+}
diff --git a/src/modules/composer-autosave/e-autosave-utils.h 
b/src/modules/composer-autosave/e-autosave-utils.h
index a27a877..8acd490 100644
--- a/src/modules/composer-autosave/e-autosave-utils.h
+++ b/src/modules/composer-autosave/e-autosave-utils.h
@@ -41,6 +41,10 @@ gboolean     e_composer_save_snapshot_finish (EMsgComposer *composer,
                                                 GAsyncResult *result,
                                                 GError **error);
 GFile *                e_composer_get_snapshot_file    (EMsgComposer *composer);
+void           e_composer_prevent_snapshot_file_delete
+                                               (EMsgComposer *composer);
+void           e_composer_allow_snapshot_file_delete
+                                               (EMsgComposer *composer);
 
 G_END_DECLS
 
diff --git a/src/modules/composer-autosave/e-composer-autosave.c 
b/src/modules/composer-autosave/e-composer-autosave.c
index 0e5705f..b34e2b4 100644
--- a/src/modules/composer-autosave/e-composer-autosave.c
+++ b/src/modules/composer-autosave/e-composer-autosave.c
@@ -35,6 +35,9 @@ struct _EComposerAutosavePrivate {
 
        /* Prevent error dialogs from piling up. */
        gboolean error_shown;
+
+       GFile *malfunction_snapshot_file;
+       gboolean editor_is_malfunction;
 };
 
 G_DEFINE_DYNAMIC_TYPE (
@@ -95,6 +98,10 @@ composer_autosave_timeout_cb (gpointer user_data)
        EExtensible *extensible;
 
        autosave = E_COMPOSER_AUTOSAVE (user_data);
+
+       if (autosave->priv->editor_is_malfunction)
+               return FALSE;
+
        extensible = e_extension_get_extensible (E_EXTENSION (autosave));
 
        /* Cancel the previous snapshot if it's still in
@@ -126,7 +133,9 @@ composer_autosave_changed_cb (EComposerAutosave *autosave)
        editor = e_msg_composer_get_editor (E_MSG_COMPOSER (extensible));
        cnt_editor = e_html_editor_get_content_editor (editor);
 
-       if (autosave->priv->timeout_id == 0 && e_content_editor_get_changed (cnt_editor)) {
+       if (autosave->priv->timeout_id == 0 &&
+           !autosave->priv->editor_is_malfunction &&
+           e_content_editor_get_changed (cnt_editor)) {
                autosave->priv->timeout_id = e_named_timeout_add_seconds (
                        AUTOSAVE_INTERVAL,
                        composer_autosave_timeout_cb, autosave);
@@ -134,6 +143,74 @@ composer_autosave_changed_cb (EComposerAutosave *autosave)
 }
 
 static void
+composer_autosave_editor_is_malfunction_cb (EComposerAutosave *autosave)
+{
+       EHTMLEditor *editor;
+       EContentEditor *cnt_editor;
+       EExtensible *extensible;
+
+       extensible = e_extension_get_extensible (E_EXTENSION (autosave));
+
+       editor = e_msg_composer_get_editor (E_MSG_COMPOSER (extensible));
+       cnt_editor = e_html_editor_get_content_editor (editor);
+
+       g_clear_object (&autosave->priv->malfunction_snapshot_file);
+       autosave->priv->editor_is_malfunction = e_content_editor_is_malfunction (cnt_editor);
+
+       if (autosave->priv->editor_is_malfunction) {
+               e_composer_prevent_snapshot_file_delete (E_MSG_COMPOSER (extensible));
+               autosave->priv->malfunction_snapshot_file = e_composer_get_snapshot_file (E_MSG_COMPOSER 
(extensible));
+               if (autosave->priv->malfunction_snapshot_file)
+                       g_object_ref (autosave->priv->malfunction_snapshot_file);
+       } else {
+               e_composer_allow_snapshot_file_delete (E_MSG_COMPOSER (extensible));
+               composer_autosave_changed_cb (autosave);
+       }
+}
+
+static void
+composer_autosave_recovered_cb (GObject *source_object,
+                               GAsyncResult *result,
+                               gpointer user_data)
+{
+       EMsgComposer *composer;
+       GError *local_error = NULL;
+
+       composer = e_composer_load_snapshot_finish (E_SHELL (source_object), result, &local_error);
+
+       if (local_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: %s", G_STRFUNC, local_error->message);
+               g_error_free (local_error);
+       } else {
+               gtk_widget_show (GTK_WIDGET (composer));
+               g_object_unref (composer);
+       }
+}
+
+static void
+composer_autosave_msg_composer_before_destroy_cb (EMsgComposer *composer,
+                                                 gpointer user_data)
+{
+       EComposerAutosave *autosave = user_data;
+
+       g_return_if_fail (autosave != NULL);
+
+       if (autosave->priv->malfunction_snapshot_file) {
+               if (e_alert_run_dialog_for_args (GTK_WINDOW (composer), 
"mail-composer:recover-autosave-malfunction", NULL) == GTK_RESPONSE_YES) {
+                       e_composer_load_snapshot (
+                               e_msg_composer_get_shell (composer), 
autosave->priv->malfunction_snapshot_file, NULL,
+                               composer_autosave_recovered_cb, NULL);
+               } else {
+                       g_file_delete (autosave->priv->malfunction_snapshot_file, NULL, NULL);
+               }
+       }
+}
+
+static void
 composer_autosave_dispose (GObject *object)
 {
        EComposerAutosavePrivate *priv;
@@ -150,6 +227,8 @@ composer_autosave_dispose (GObject *object)
 
        g_clear_object (&priv->cancellable);
 
+       g_clear_object (&priv->malfunction_snapshot_file);
+
        /* Chain up to parent's dispose() method. */
        G_OBJECT_CLASS (e_composer_autosave_parent_class)->dispose (object);
 }
@@ -168,6 +247,14 @@ composer_autosave_constructed (GObject *object)
        editor = e_msg_composer_get_editor (E_MSG_COMPOSER (extensible));
        cnt_editor = e_html_editor_get_content_editor (editor);
 
+       g_signal_connect (
+               extensible, "before-destroy",
+               G_CALLBACK (composer_autosave_msg_composer_before_destroy_cb), object);
+
+       e_signal_connect_notify_swapped (
+               cnt_editor, "notify::is-malfunction",
+               G_CALLBACK (composer_autosave_editor_is_malfunction_cb), object);
+
        /* Do not use e_signal_connect_notify_swapped() here,
           this module relies on "false" change notifications. */
        g_signal_connect_swapped (
@@ -205,6 +292,8 @@ e_composer_autosave_init (EComposerAutosave *autosave)
 {
        autosave->priv = E_COMPOSER_AUTOSAVE_GET_PRIVATE (autosave);
        autosave->priv->cancellable = g_cancellable_new ();
+       autosave->priv->malfunction_snapshot_file = NULL;
+       autosave->priv->editor_is_malfunction = FALSE;
 }
 
 void
diff --git a/src/modules/composer-autosave/e-composer-registry.c 
b/src/modules/composer-autosave/e-composer-registry.c
index 29b2118..4ff50ce 100644
--- a/src/modules/composer-autosave/e-composer-registry.c
+++ b/src/modules/composer-autosave/e-composer-registry.c
@@ -62,7 +62,7 @@ composer_registry_recovered_cb (GObject *source_object,
                 *       why we could not recover the message.
                 *       Will need a new error XML entry. */
                g_warn_if_fail (composer == NULL);
-               g_warning ("%s", local_error->message);
+               g_warning ("%s: %s", G_STRFUNC, local_error->message);
                g_error_free (local_error);
                goto exit;
        }
diff --git a/src/modules/webkit-editor/e-webkit-editor.c b/src/modules/webkit-editor/e-webkit-editor.c
index 62ddc6d..69ac535 100644
--- a/src/modules/webkit-editor/e-webkit-editor.c
+++ b/src/modules/webkit-editor/e-webkit-editor.c
@@ -38,6 +38,7 @@
 enum {
        PROP_0,
        PROP_WEB_EXTENSION, /* for test purposes */
+       PROP_IS_MALFUNCTION,
        PROP_CAN_COPY,
        PROP_CAN_CUT,
        PROP_CAN_PASTE,
@@ -146,6 +147,7 @@ struct _EWebKitEditorPrivate {
 
        EThreeState start_bottom;
        EThreeState top_signature;
+       gboolean is_malfunction;
 };
 
 static const GdkRGBA black = { 0, 0, 0, 1 };
@@ -256,6 +258,14 @@ webkit_editor_can_copy_cb (WebKitWebView *view,
 }
 
 static gboolean
+webkit_editor_is_malfunction (EWebKitEditor *wk_editor)
+{
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), FALSE);
+
+       return wk_editor->priv->is_malfunction;
+}
+
+static gboolean
 webkit_editor_can_copy (EWebKitEditor *wk_editor)
 {
        g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), FALSE);
@@ -5554,6 +5564,12 @@ webkit_editor_get_property (GObject *object,
                                E_WEBKIT_EDITOR (object)));
                        return;
 
+               case PROP_IS_MALFUNCTION:
+                       g_value_set_boolean (
+                               value, webkit_editor_is_malfunction (
+                               E_WEBKIT_EDITOR (object)));
+                       return;
+
                case PROP_CAN_COPY:
                        g_value_set_boolean (
                                value, webkit_editor_can_copy (
@@ -6162,6 +6178,9 @@ webkit_editor_web_process_crashed_cb (EWebKitEditor *wk_editor)
 
        g_return_if_fail (E_IS_WEBKIT_EDITOR (wk_editor));
 
+       wk_editor->priv->is_malfunction = TRUE;
+       g_object_notify (G_OBJECT (wk_editor), "is-malfunction");
+
        widget = GTK_WIDGET (wk_editor);
        while (widget) {
                if (E_IS_ALERT_SINK (widget)) {
@@ -6380,6 +6399,8 @@ e_webkit_editor_class_init (EWebKitEditorClass *class)
                        G_PARAM_STATIC_STRINGS));
 
        g_object_class_override_property (
+               object_class, PROP_IS_MALFUNCTION, "is-malfunction");
+       g_object_class_override_property (
                object_class, PROP_CAN_COPY, "can-copy");
        g_object_class_override_property (
                object_class, PROP_CAN_CUT, "can-cut");
@@ -6445,6 +6466,7 @@ e_webkit_editor_init (EWebKitEditor *wk_editor)
 
        /* To be able to cancel any pending calls when 'dispose' is called. */
        wk_editor->priv->cancellable = g_cancellable_new ();
+       wk_editor->priv->is_malfunction = FALSE;
        wk_editor->priv->spell_check_enabled = TRUE;
        wk_editor->priv->spell_checker = e_spell_checker_new ();
        wk_editor->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, 
(GDestroyNotify) g_variant_unref);


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