[evolution/gnome-3-32] I#343 - Composer autosave can overwrite backup with empty content



commit 30303a645fb8145bbe281b7e2afb4a91af0abb8c
Author: Milan Crha <mcrha redhat com>
Date:   Tue Mar 12 14:08:45 2019 +0100

    I#343 - Composer autosave can overwrite backup with empty content
    
    Closes https://gitlab.gnome.org/GNOME/evolution/issues/343

 src/composer/e-msg-composer.c                      | 12 +++-
 src/e-util/e-content-editor.c                      | 40 +++++++++++
 src/e-util/e-content-editor.h                      |  3 +
 src/e-util/e-misc-utils.c                          | 32 +++++++++
 src/e-util/e-misc-utils.h                          |  6 ++
 src/e-util/test-html-editor.c                      |  4 +-
 src/modules/composer-autosave/e-autosave-utils.c   | 79 ++++++++--------------
 .../composer-autosave/e-composer-autosave.c        | 30 ++++----
 src/modules/webkit-editor/e-webkit-editor.c        | 52 ++++++++++++--
 9 files changed, 185 insertions(+), 73 deletions(-)
---
diff --git a/src/composer/e-msg-composer.c b/src/composer/e-msg-composer.c
index 098340e285..eedc538251 100644
--- a/src/composer/e-msg-composer.c
+++ b/src/composer/e-msg-composer.c
@@ -1249,6 +1249,7 @@ composer_build_message (EMsgComposer *composer,
        gchar *charset, *message_uid;
        const gchar *from_domain;
        gint i;
+       GError *last_error = NULL;
 
        e_msg_composer_inc_soft_busy (composer);
 
@@ -1420,6 +1421,8 @@ composer_build_message (EMsgComposer *composer,
                if (!text) {
                        g_warning ("%s: Failed to retrieve text/plain processed content", G_STRFUNC);
                        text = g_strdup ("");
+
+                       last_error = e_content_editor_dup_last_error (cnt_editor);
                }
 
                g_byte_array_append (data, (guint8 *) text, strlen (text));
@@ -1529,6 +1532,9 @@ composer_build_message (EMsgComposer *composer,
                        }
                }
 
+               if (!last_error)
+                       last_error = e_content_editor_dup_last_error (cnt_editor);
+
                length = strlen (text);
                g_byte_array_append (data, (guint8 *) text, (guint) length);
                if (!g_str_has_suffix (text, "\r\n"))
@@ -1646,8 +1652,12 @@ composer_build_message (EMsgComposer *composer,
                context->top_level_part = CAMEL_DATA_WRAPPER (multipart);
        }
 
+       if (last_error) {
+               g_simple_async_result_take_error (simple, last_error);
+               g_simple_async_result_complete (simple);
+
        /* Run any blocking operations in a separate thread. */
-       if (context->need_thread) {
+       } else if (context->need_thread) {
                if (!context->is_draft)
                        context->recipients_with_certificate = 
composer_get_completed_recipients_with_certificate (composer);
 
diff --git a/src/e-util/e-content-editor.c b/src/e-util/e-content-editor.c
index ba70189963..de6a20f64e 100644
--- a/src/e-util/e-content-editor.c
+++ b/src/e-util/e-content-editor.c
@@ -493,6 +493,23 @@ e_content_editor_default_init (EContentEditorInterface *iface)
                        G_PARAM_READWRITE |
                        G_PARAM_STATIC_STRINGS));
 
+       /**
+        * EContentEditor:last-error:
+        *
+        * GError of the last operation; can be %NULL.
+        *
+        * Since: 3.32.1
+        */
+       g_object_interface_install_property (
+               iface,
+               g_param_spec_boxed (
+                       "last-error",
+                       NULL,
+                       NULL,
+                       G_TYPE_ERROR,
+                       G_PARAM_READWRITE |
+                       G_PARAM_STATIC_STRINGS));
+
        /**
         * EContentEditor:paste-clipboard
         *
@@ -2158,6 +2175,29 @@ e_content_editor_is_ready (EContentEditor *editor)
        return iface->is_ready (editor);
 }
 
+GError *
+e_content_editor_dup_last_error (EContentEditor *editor)
+{
+       GError *last_error = NULL;
+
+       g_return_val_if_fail (E_IS_CONTENT_EDITOR (editor), NULL);
+
+       g_object_get (G_OBJECT (editor), "last-error", &last_error, NULL);
+
+       return last_error;
+}
+
+void
+e_content_editor_take_last_error (EContentEditor *editor,
+                                 GError *error)
+{
+       g_return_if_fail (E_IS_CONTENT_EDITOR (editor));
+
+       g_object_set (G_OBJECT (editor), "last-error", error, NULL);
+
+       g_clear_error (&error);
+}
+
 gchar *
 e_content_editor_insert_signature (EContentEditor *editor,
                                    const gchar *content,
diff --git a/src/e-util/e-content-editor.h b/src/e-util/e-content-editor.h
index 49aa65886b..f1630522a2 100644
--- a/src/e-util/e-content-editor.h
+++ b/src/e-util/e-content-editor.h
@@ -637,6 +637,9 @@ gchar *             e_content_editor_get_current_signature_uid
                                                (EContentEditor *editor);
 
 gboolean       e_content_editor_is_ready       (EContentEditor *editor);
+GError *       e_content_editor_dup_last_error (EContentEditor *editor);
+void           e_content_editor_take_last_error(EContentEditor *editor,
+                                                GError *error);
 
 gchar *                e_content_editor_insert_signature
                                                (EContentEditor *editor,
diff --git a/src/e-util/e-misc-utils.c b/src/e-util/e-misc-utils.c
index afc80c465b..03f51e7235 100644
--- a/src/e-util/e-misc-utils.c
+++ b/src/e-util/e-misc-utils.c
@@ -3848,6 +3848,38 @@ e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check (GDBusProxy *dbus_
        return result;
 }
 
+/**
+ * e_util_invoke_g_dbus_proxy_call_sync_wrapper:
+ * @dbus_proxy: a #GDBusProxy instance
+ * @method_name: a method name to invoke
+ * @parameters: (allow-none): parameters of the method, or %NULL
+ * @cancellable: (allow-none): a #GCancellable, or %NULL
+ * @error: (allow-none): Return location for error, or %NULL
+ *
+ * Wraps GDBusProxy synchronous call into an asynchronous without blocking
+ * the main context. This can be useful when doing calls on a WebExtension,
+ * because it can avoid freeze when this is called in the UI process and
+ * the WebProcess also does its own IPC call.
+ *
+ * This function should be called only from the main thread.
+ *
+ * See e_util_invoke_g_dbus_proxy_call_sync_wrapper_full().
+ *
+ * Returns: (transfer full): The result of the method call, or %NULL on error. Free with g_variant_unref().
+ *
+ * Since: 3.32.1
+ **/
+GVariant *
+e_util_invoke_g_dbus_proxy_call_sync_wrapper (GDBusProxy *dbus_proxy,
+                                             const gchar *method_name,
+                                             GVariant *parameters,
+                                             GCancellable *cancellable,
+                                             GError **error)
+{
+       return e_util_invoke_g_dbus_proxy_call_sync_wrapper_full (dbus_proxy, method_name, parameters,
+               G_DBUS_CALL_FLAGS_NONE, -1, cancellable, error);
+}
+
 static void
 sync_wrapper_result_callback (GObject *source_object,
                              GAsyncResult *result,
diff --git a/src/e-util/e-misc-utils.h b/src/e-util/e-misc-utils.h
index 086505c68a..b10bdf5adf 100644
--- a/src/e-util/e-misc-utils.h
+++ b/src/e-util/e-misc-utils.h
@@ -324,6 +324,12 @@ GVariant * e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check
                                                 const gchar *method_name,
                                                 GVariant *parameters,
                                                 GCancellable *cancellable);
+GVariant *     e_util_invoke_g_dbus_proxy_call_sync_wrapper
+                                               (GDBusProxy *dbus_proxy,
+                                                const gchar *method_name,
+                                                GVariant *parameters,
+                                                GCancellable *cancellable,
+                                                GError **error);
 GVariant *     e_util_invoke_g_dbus_proxy_call_sync_wrapper_full
                                                (GDBusProxy *dbus_proxy,
                                                 const gchar *method_name,
diff --git a/src/e-util/test-html-editor.c b/src/e-util/test-html-editor.c
index 339658260d..81b58a4eb5 100644
--- a/src/e-util/test-html-editor.c
+++ b/src/e-util/test-html-editor.c
@@ -195,11 +195,11 @@ view_source_dialog (EHTMLEditor *editor,
 
                content = gtk_text_view_new ();
                buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (content));
-               gtk_text_buffer_set_text (buffer, html, -1);
+               gtk_text_buffer_set_text (buffer, html ? html : "", -1);
                gtk_text_view_set_editable (GTK_TEXT_VIEW (content), FALSE);
        } else {
                content = webkit_web_view_new ();
-               webkit_web_view_load_html (WEBKIT_WEB_VIEW (content), html, "evo-file://");
+               webkit_web_view_load_html (WEBKIT_WEB_VIEW (content), html ? html : "", "evo-file://");
        }
        g_free (html);
 
diff --git a/src/modules/composer-autosave/e-autosave-utils.c 
b/src/modules/composer-autosave/e-autosave-utils.c
index bd7035a857..fabd89d019 100644
--- a/src/modules/composer-autosave/e-autosave-utils.c
+++ b/src/modules/composer-autosave/e-autosave-utils.c
@@ -38,7 +38,7 @@ struct _LoadContext {
 
 struct _SaveContext {
        GCancellable *cancellable;
-       GOutputStream *output_stream;
+       GFile *snapshot_file;
 };
 
 static void
@@ -53,11 +53,8 @@ load_context_free (LoadContext *context)
 static void
 save_context_free (SaveContext *context)
 {
-       if (context->cancellable != NULL)
-               g_object_unref (context->cancellable);
-
-       if (context->output_stream != NULL)
-               g_object_unref (context->output_stream);
+       g_clear_object (&context->cancellable);
+       g_clear_object (&context->snapshot_file);
 
        g_slice_free (SaveContext, context);
 }
@@ -241,11 +238,27 @@ write_message_to_stream_thread (GTask *task,
                                gpointer task_data,
                                GCancellable *cancellable)
 {
+       GFileOutputStream *file_output_stream;
        GOutputStream *output_stream;
+       GFile *snapshot_file;
        gssize bytes_written;
        GError *local_error = NULL;
 
-       output_stream = task_data;
+       snapshot_file = task_data;
+
+       file_output_stream = g_file_replace (snapshot_file, NULL, FALSE,
+               G_FILE_CREATE_PRIVATE, cancellable, &local_error);
+
+       if (!file_output_stream) {
+               if (local_error)
+                       g_task_return_error (task, local_error);
+               else
+                       g_task_return_int (task, 0);
+
+               return;
+       }
+
+       output_stream = G_OUTPUT_STREAM (file_output_stream);
 
        bytes_written = camel_data_wrapper_decode_to_output_stream_sync (
                CAMEL_DATA_WRAPPER (source_object),
@@ -253,6 +266,8 @@ write_message_to_stream_thread (GTask *task,
 
        g_output_stream_close (output_stream, cancellable, local_error ? NULL : &local_error);
 
+       g_object_unref (file_output_stream);
+
        if (local_error != NULL) {
                g_task_return_error (task, local_error);
        } else {
@@ -287,7 +302,7 @@ save_snapshot_get_message_cb (EMsgComposer *composer,
 
        task = g_task_new (message, context->cancellable, (GAsyncReadyCallback) save_snapshot_splice_cb, 
simple);
 
-       g_task_set_task_data (task, g_object_ref (context->output_stream), g_object_unref);
+       g_task_set_task_data (task, g_object_ref (context->snapshot_file), g_object_unref);
 
        g_task_run_in_thread (task, write_message_to_stream_thread);
 
@@ -295,45 +310,6 @@ save_snapshot_get_message_cb (EMsgComposer *composer,
        g_object_unref (message);
 }
 
-static void
-save_snapshot_replace_cb (GFile *snapshot_file,
-                          GAsyncResult *result,
-                          GSimpleAsyncResult *simple)
-{
-       GObject *object;
-       SaveContext *context;
-       GFileOutputStream *output_stream;
-       GError *local_error = NULL;
-
-       context = g_simple_async_result_get_op_res_gpointer (simple);
-
-       /* Output stream might be NULL, so don't use cast macro. */
-       output_stream = g_file_replace_finish (
-               snapshot_file, result, &local_error);
-       context->output_stream = (GOutputStream *) output_stream;
-
-       if (local_error != NULL) {
-               g_warn_if_fail (output_stream == NULL);
-               g_simple_async_result_take_error (simple, local_error);
-               g_simple_async_result_complete (simple);
-               g_object_unref (simple);
-               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. */
-       e_msg_composer_get_message_draft (
-               E_MSG_COMPOSER (object), G_PRIORITY_DEFAULT,
-               context->cancellable, (GAsyncReadyCallback)
-               save_snapshot_get_message_cb, simple);
-
-       g_object_unref (object);
-}
-
 static EMsgComposer *
 composer_registry_lookup (GQueue *registry,
                           const gchar *basename)
@@ -524,11 +500,12 @@ e_composer_save_snapshot (EMsgComposer *composer,
 
        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->snapshot_file = g_object_ref (snapshot_file);
+
+       e_msg_composer_get_message_draft (
+               composer, G_PRIORITY_DEFAULT,
                context->cancellable, (GAsyncReadyCallback)
-               save_snapshot_replace_cb, simple);
+               save_snapshot_get_message_cb, simple);
 }
 
 gboolean
diff --git a/src/modules/composer-autosave/e-composer-autosave.c 
b/src/modules/composer-autosave/e-composer-autosave.c
index 0b214fe081..a6787058c0 100644
--- a/src/modules/composer-autosave/e-composer-autosave.c
+++ b/src/modules/composer-autosave/e-composer-autosave.c
@@ -33,9 +33,6 @@ struct _EComposerAutosavePrivate {
        GCancellable *cancellable;
        guint timeout_id;
 
-       /* Prevent error dialogs from piling up. */
-       gboolean error_shown;
-
        GFile *malfunction_snapshot_file;
        gboolean editor_is_malfunction;
 };
@@ -45,6 +42,8 @@ G_DEFINE_DYNAMIC_TYPE (
        e_composer_autosave,
        E_TYPE_EXTENSION)
 
+static void composer_autosave_changed_cb (EComposerAutosave *autosave);
+
 static void
 composer_autosave_finished_cb (GObject *source_object,
                                GAsyncResult *result,
@@ -66,6 +65,7 @@ composer_autosave_finished_cb (GObject *source_object,
                g_error_free (local_error);
 
        else if (local_error != NULL) {
+               EHTMLEditor *editor;
                gchar *basename;
 
                if (G_IS_FILE (snapshot_file))
@@ -73,19 +73,21 @@ composer_autosave_finished_cb (GObject *source_object,
                else
                        basename = g_strdup (" ");
 
-               /* Only show one error dialog at a time. */
-               if (!autosave->priv->error_shown) {
-                       autosave->priv->error_shown = TRUE;
-                       e_alert_run_dialog_for_args (
-                               GTK_WINDOW (composer),
+               editor = e_msg_composer_get_editor (composer);
+
+               if (editor) {
+                       e_alert_submit (
+                               E_ALERT_SINK (editor),
                                "mail-composer:no-autosave",
                                basename, local_error->message, NULL);
-                       autosave->priv->error_shown = FALSE;
                } else
                        g_warning ("%s: %s", basename, local_error->message);
 
                g_free (basename);
                g_error_free (local_error);
+
+               /* Re-schedule on error, maybe it'll work a bit later */
+               composer_autosave_changed_cb (autosave);
        }
 
        g_object_unref (autosave);
@@ -109,10 +111,8 @@ composer_autosave_timeout_cb (gpointer user_data)
        composer = E_MSG_COMPOSER (extensible);
 
        /* Do not do anything when it's busy */
-       if (e_msg_composer_is_soft_busy (composer)) {
-               autosave->priv->timeout_id = 0;
-               return FALSE;
-       }
+       if (e_msg_composer_is_soft_busy (composer))
+               return TRUE;
 
        /* Cancel the previous snapshot if it's still in
         * progress and start a new snapshot operation. */
@@ -120,14 +120,14 @@ composer_autosave_timeout_cb (gpointer user_data)
        g_object_unref (autosave->priv->cancellable);
        autosave->priv->cancellable = g_cancellable_new ();
 
+       autosave->priv->timeout_id = 0;
+
        e_composer_save_snapshot (
                composer,
                autosave->priv->cancellable,
                composer_autosave_finished_cb,
                g_object_ref (autosave));
 
-       autosave->priv->timeout_id = 0;
-
        return FALSE;
 }
 
diff --git a/src/modules/webkit-editor/e-webkit-editor.c b/src/modules/webkit-editor/e-webkit-editor.c
index 0b8f67f389..9ab8734158 100644
--- a/src/modules/webkit-editor/e-webkit-editor.c
+++ b/src/modules/webkit-editor/e-webkit-editor.c
@@ -52,6 +52,7 @@ enum {
        PROP_START_BOTTOM,
        PROP_TOP_SIGNATURE,
        PROP_VISUALLY_WRAP_LONG_LINES,
+       PROP_LAST_ERROR,
 
        PROP_ALIGNMENT,
        PROP_BACKGROUND_COLOR,
@@ -148,6 +149,8 @@ struct _EWebKitEditorPrivate {
        EThreeState start_bottom;
        EThreeState top_signature;
        gboolean is_malfunction;
+
+       GError *last_error;
 };
 
 static const GdkRGBA black = { 0, 0, 0, 1 };
@@ -191,6 +194,26 @@ e_webkit_editor_new (void)
        return g_object_new (E_TYPE_WEBKIT_EDITOR, NULL);
 }
 
+static void
+webkit_editor_set_last_error (EWebKitEditor *wk_editor,
+                             const GError *error)
+{
+       g_return_if_fail (E_IS_WEBKIT_EDITOR (wk_editor));
+
+       g_clear_error (&wk_editor->priv->last_error);
+
+       if (error)
+               wk_editor->priv->last_error = g_error_copy (error);
+}
+
+static const GError *
+webkit_editor_get_last_error (EWebKitEditor *wk_editor)
+{
+       g_return_val_if_fail (E_IS_WEBKIT_EDITOR (wk_editor), NULL);
+
+       return wk_editor->priv->last_error;
+}
+
 static void
 webkit_editor_can_paste_cb (WebKitWebView *view,
                             GAsyncResult *result,
@@ -2038,10 +2061,11 @@ webkit_editor_get_content (EContentEditor *editor,
 {
        EWebKitEditor *wk_editor;
        GVariant *result;
+       GError *local_error = NULL;
 
        wk_editor = E_WEBKIT_EDITOR (editor);
        if (!wk_editor->priv->web_extension)
-               return g_strdup ("");
+               return NULL;
 
        if ((flags & E_CONTENT_EDITOR_GET_TEXT_HTML) &&
            !(flags & E_CONTENT_EDITOR_GET_PROCESSED) &&
@@ -2055,7 +2079,7 @@ webkit_editor_get_content (EContentEditor *editor,
                                wk_editor->priv->current_user_stylesheet),
                        wk_editor->priv->cancellable);
 
-       result = e_util_invoke_g_dbus_proxy_call_sync_wrapper_with_error_check (
+       result = e_util_invoke_g_dbus_proxy_call_sync_wrapper (
                wk_editor->priv->web_extension,
                "DOMGetContent",
                g_variant_new (
@@ -2063,7 +2087,11 @@ webkit_editor_get_content (EContentEditor *editor,
                        current_page_id (wk_editor),
                        inline_images_from_domain ? inline_images_from_domain : "",
                        (gint32) flags),
-               NULL);
+               wk_editor->priv->cancellable,
+               &local_error);
+
+       webkit_editor_set_last_error (wk_editor, local_error);
+       g_clear_error (&local_error);
 
        if ((flags & E_CONTENT_EDITOR_GET_TEXT_HTML) &&
            !(flags & E_CONTENT_EDITOR_GET_PROCESSED) &&
@@ -2087,7 +2115,7 @@ webkit_editor_get_content (EContentEditor *editor,
                return value;
        }
 
-       return g_strdup ("");
+       return NULL;
 }
 
 static gboolean
@@ -5473,6 +5501,7 @@ webkit_editor_finalize (GObject *object)
 
        g_clear_object (&priv->spell_checker);
        g_clear_object (&priv->cancellable);
+       g_clear_error (&priv->last_error);
 
        g_free (priv->font_name);
 
@@ -5620,6 +5649,12 @@ webkit_editor_set_property (GObject *object,
                                E_WEBKIT_EDITOR (object),
                                g_value_get_boolean (value));
                        return;
+
+               case PROP_LAST_ERROR:
+                       webkit_editor_set_last_error (
+                               E_WEBKIT_EDITOR (object),
+                               g_value_get_boxed (value));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -5831,6 +5866,13 @@ webkit_editor_get_property (GObject *object,
                                webkit_editor_get_visually_wrap_long_lines (
                                        E_WEBKIT_EDITOR (object)));
                        return;
+
+               case PROP_LAST_ERROR:
+                       g_value_set_boxed (
+                               value,
+                               webkit_editor_get_last_error (
+                                       E_WEBKIT_EDITOR (object)));
+                       return;
        }
 
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -6548,6 +6590,8 @@ e_webkit_editor_class_init (EWebKitEditorClass *class)
                object_class, PROP_SPELL_CHECK_ENABLED, "spell-check-enabled");
        g_object_class_override_property (
                object_class, PROP_VISUALLY_WRAP_LONG_LINES, "visually-wrap-long-lines");
+       g_object_class_override_property (
+               object_class, PROP_LAST_ERROR, "last-error");
        g_object_class_override_property (
                object_class, PROP_SPELL_CHECKER, "spell-checker");
 }


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