diff options
author | Milan Crha <mcrha@redhat.com> | 2023-05-09 15:44:22 +0200 |
---|---|---|
committer | Milan Crha <mcrha@redhat.com> | 2023-05-09 15:46:45 +0200 |
commit | 1df5266bd673453803722912a0b61603dd4b5cd4 (patch) | |
tree | e9a28e063fca177c1dd2fdda1aea1a2054fc4bf4 | |
parent | 2eebe2d656a82613d547d5cb9d7b95f412107fa3 (diff) | |
download | evolution-1df5266bd673453803722912a0b61603dd4b5cd4.tar.gz |
I#2354 - Composer: Notify about attachment file change before send
Closes https://gitlab.gnome.org/GNOME/evolution/-/issues/2354
-rw-r--r-- | data/org.gnome.evolution.mail.gschema.xml.in | 5 | ||||
-rw-r--r-- | src/e-util/e-attachment-view.c | 88 | ||||
-rw-r--r-- | src/e-util/e-attachment.c | 146 | ||||
-rw-r--r-- | src/e-util/e-attachment.h | 6 | ||||
-rw-r--r-- | src/mail/em-composer-utils.c | 77 | ||||
-rw-r--r-- | src/mail/mail-config.ui | 16 | ||||
-rw-r--r-- | src/mail/mail.error.xml | 7 | ||||
-rw-r--r-- | src/modules/mail/em-composer-prefs.c | 6 |
8 files changed, 303 insertions, 48 deletions
diff --git a/data/org.gnome.evolution.mail.gschema.xml.in b/data/org.gnome.evolution.mail.gschema.xml.in index ce1c6414dd..441042f23c 100644 --- a/data/org.gnome.evolution.mail.gschema.xml.in +++ b/data/org.gnome.evolution.mail.gschema.xml.in @@ -939,6 +939,11 @@ <_summary>(Deprecated) Asks whether to close the message window when the user forwards or replies to the message shown in the window</_summary> <_description>This key was deprecated in version 3.10 and should no longer be used. Use “browser-close-on-reply-policy” instead.</_description> </key> + <key name="prompt-on-changed-attachment" type="b"> + <default>true</default> + <_summary>Prompt before send when attachment changed on the disk</_summary> + <_description>Ask whether can send a message with attachments which changed on the disk since they had been attached to the message.</_description> + </key> </schema> diff --git a/src/e-util/e-attachment-view.c b/src/e-util/e-attachment-view.c index 210fad648e..80a0d4c113 100644 --- a/src/e-util/e-attachment-view.c +++ b/src/e-util/e-attachment-view.c @@ -45,6 +45,7 @@ static const gchar *ui = "<ui>" " <popup name='context'>" " <menuitem action='cancel'/>" +" <menuitem action='reload'/>" " <menuitem action='save-as'/>" " <menuitem action='remove'/>" " <menuitem action='properties'/>" @@ -68,6 +69,21 @@ G_DEFINE_INTERFACE ( GTK_TYPE_WIDGET) static void +call_attachment_load_handle_error (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GtkWindow *window = user_data; + + g_return_if_fail (E_IS_ATTACHMENT (source_object)); + g_return_if_fail (!window || GTK_IS_WINDOW (window)); + + e_attachment_load_handle_error (E_ATTACHMENT (source_object), result, window); + + g_clear_object (&window); +} + +static void action_add_cb (GtkAction *action, EAttachmentView *view) { @@ -196,6 +212,37 @@ action_properties_cb (GtkAction *action, } static void +action_reload_cb (GtkAction *action, + EAttachmentView *view) +{ + GList *list, *link; + gpointer parent; + + g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); + + parent = gtk_widget_get_toplevel (GTK_WIDGET (view)); + parent = gtk_widget_is_toplevel (parent) ? parent : NULL; + + list = e_attachment_view_get_selected_attachments (view); + + for (link = list; link; link = g_list_next (link)) { + EAttachment *attachment = link->data; + GFile *file; + + file = e_attachment_ref_file (attachment); + if (file) { + e_attachment_load_async ( + attachment, (GAsyncReadyCallback) + call_attachment_load_handle_error, parent ? g_object_ref (parent) : NULL); + + g_clear_object (&file); + } + } + + g_list_free_full (list, g_object_unref); +} + +static void action_remove_cb (GtkAction *action, EAttachmentView *view) { @@ -350,6 +397,13 @@ static GtkActionEntry editable_entries[] = { NULL, /* XXX Add a tooltip! */ G_CALLBACK (action_properties_cb) }, + { "reload", + "view-refresh", + N_("Re_load"), + NULL, + N_("Reload attachment content"), + G_CALLBACK (action_reload_cb) }, + { "remove", "list-remove", N_("_Remove"), @@ -359,21 +413,6 @@ static GtkActionEntry editable_entries[] = { }; static void -call_attachment_load_handle_error (GObject *source_object, - GAsyncResult *result, - gpointer user_data) -{ - GtkWindow *window = user_data; - - g_return_if_fail (E_IS_ATTACHMENT (source_object)); - g_return_if_fail (!window || GTK_IS_WINDOW (window)); - - e_attachment_load_handle_error (E_ATTACHMENT (source_object), result, window); - - g_clear_object (&window); -} - -static void attachment_view_netscape_url (EAttachmentView *view, GdkDragContext *drag_context, gint x, @@ -702,13 +741,26 @@ attachment_view_update_actions (EAttachmentView *view) GList *list, *iter; guint n_selected; gboolean busy = FALSE; + gboolean may_reload = FALSE; g_return_if_fail (E_IS_ATTACHMENT_VIEW (view)); priv = e_attachment_view_get_private (view); list = e_attachment_view_get_selected_attachments (view); - n_selected = g_list_length (list); + n_selected = 0; + + for (iter = list; iter && (!busy || !may_reload); iter = g_list_next (iter)) { + EAttachment *attach = iter->data; + + n_selected++; + + if (e_attachment_get_may_reload (attach)) { + may_reload = TRUE; + busy |= e_attachment_get_loading (attach); + busy |= e_attachment_get_saving (attach); + } + } if (n_selected == 1) { attachment = g_object_ref (list->data); @@ -729,6 +781,10 @@ attachment_view_update_actions (EAttachmentView *view) action = e_attachment_view_get_action (view, "properties"); gtk_action_set_visible (action, !busy && n_selected == 1); + action = e_attachment_view_get_action (view, "reload"); + gtk_action_set_visible (action, may_reload); + gtk_action_set_sensitive (action, !busy); + action = e_attachment_view_get_action (view, "remove"); gtk_action_set_visible (action, !busy && n_selected > 0); diff --git a/src/e-util/e-attachment.c b/src/e-util/e-attachment.c index f59787ad9c..97b7a490e0 100644 --- a/src/e-util/e-attachment.c +++ b/src/e-util/e-attachment.c @@ -47,6 +47,7 @@ #define EMBLEM_CANCELLED "process-stop" #define EMBLEM_LOADING "emblem-downloads" #define EMBLEM_SAVING "document-save" +#define EMBLEM_MAY_RELOAD "dialog-warning" #define EMBLEM_ENCRYPT_WEAK "security-low" #define EMBLEM_ENCRYPT_STRONG "security-high" #define EMBLEM_ENCRYPT_UNKNOWN "security-medium" @@ -74,6 +75,7 @@ struct _EAttachmentPrivate { guint loading : 1; guint saving : 1; guint initially_shown : 1; + guint may_reload : 1; guint save_self : 1; guint save_extracted : 1; @@ -104,7 +106,8 @@ enum { PROP_SAVE_EXTRACTED, PROP_SAVING, PROP_INITIALLY_SHOWN, - PROP_SIGNED + PROP_SIGNED, + PROP_MAY_RELOAD }; enum { @@ -385,6 +388,9 @@ attachment_update_icon_column_idle_cb (gpointer weak_ref) else if (e_attachment_get_saving (attachment)) emblem_name = EMBLEM_SAVING; + else if (e_attachment_get_may_reload (attachment)) + emblem_name = EMBLEM_MAY_RELOAD; + else if (e_attachment_get_encrypted (attachment)) switch (e_attachment_get_encrypted (attachment)) { case CAMEL_CIPHER_VALIDITY_ENCRYPT_WEAK: @@ -658,6 +664,12 @@ attachment_set_property (GObject *object, E_ATTACHMENT (object), g_value_get_boolean (value)); return; + + case PROP_MAY_RELOAD: + e_attachment_set_may_reload ( + E_ATTACHMENT (object), + g_value_get_boolean (value)); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -767,6 +779,13 @@ attachment_get_property (GObject *object, e_attachment_get_signed ( E_ATTACHMENT (object))); return; + + case PROP_MAY_RELOAD: + g_value_set_boolean ( + value, + e_attachment_get_may_reload ( + E_ATTACHMENT (object))); + return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -986,6 +1005,17 @@ e_attachment_class_init (EAttachmentClass *class) G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property ( + object_class, + PROP_MAY_RELOAD, + g_param_spec_boolean ( + "may-reload", + "May Reload", + NULL, + FALSE, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + signals[UPDATE_FILE_INFO] = g_signal_new ( "update-file-info", G_TYPE_FROM_CLASS (class), @@ -1780,6 +1810,85 @@ e_attachment_update_store_columns (EAttachment *attachment) attachment_update_progress_columns (attachment); } +gboolean +e_attachment_check_file_changed (EAttachment *attachment, + gboolean *out_file_exists, + GCancellable *cancellable) +{ + GFileInfo *disk_file_info; + GFile *file; + gboolean same = FALSE; + gboolean file_exists = FALSE; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + file = e_attachment_ref_file (attachment); + if (!file) { + if (out_file_exists) + *out_file_exists = FALSE; + return FALSE; + } + + disk_file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, cancellable, NULL); + if (disk_file_info) { + GFileInfo *attachment_file_info; + + attachment_file_info = e_attachment_ref_file_info (attachment); + if (attachment_file_info) { + guint64 a_size, f_size; + + a_size = g_file_info_get_attribute_uint64 (attachment_file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); + f_size = g_file_info_get_attribute_uint64 (disk_file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); + + same = a_size == f_size; + file_exists = TRUE; + + if (same) { + guint64 a_modified, f_modified; + + a_modified = g_file_info_get_attribute_uint64 (attachment_file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + f_modified = g_file_info_get_attribute_uint64 (disk_file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); + + same = a_modified == f_modified; + } + } + + g_clear_object (&attachment_file_info); + g_clear_object (&disk_file_info); + } + + g_object_unref (file); + + if (out_file_exists) + *out_file_exists = file_exists; + + return !same; +} + +void +e_attachment_set_may_reload (EAttachment *attachment, + gboolean may_reload) +{ + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + + if ((!attachment->priv->may_reload) == (!may_reload)) + return; + + attachment->priv->may_reload = may_reload; + + g_object_notify (G_OBJECT (attachment), "may-reload"); + + attachment_update_icon_column (attachment); +} + +gboolean +e_attachment_get_may_reload (EAttachment *attachment) +{ + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + + return attachment->priv->may_reload; +} + /************************* e_attachment_load_async() *************************/ typedef struct _LoadContext LoadContext; @@ -2424,6 +2533,7 @@ e_attachment_load_finish (EAttachment *attachment, attachment, load_context->file_info); e_attachment_set_mime_part ( attachment, load_context->mime_part); + e_attachment_set_may_reload (attachment, FALSE); } attachment_set_loading (attachment, FALSE); @@ -2694,38 +2804,10 @@ e_attachment_open_async (EAttachment *attachment, /* open existing file only if it did not change */ if (file && mime_part) { - GFileInfo *disk_file_info; - gboolean same = FALSE; - - disk_file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, NULL); - if (disk_file_info) { - GFileInfo *attachment_file_info; - - attachment_file_info = e_attachment_ref_file_info (attachment); - if (attachment_file_info) { - guint64 a_size, f_size; - - a_size = g_file_info_get_attribute_uint64 (attachment_file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); - f_size = g_file_info_get_attribute_uint64 (disk_file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); - - same = a_size == f_size; - - if (same) { - guint64 a_modified, f_modified; - - a_modified = g_file_info_get_attribute_uint64 (attachment_file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); - f_modified = g_file_info_get_attribute_uint64 (disk_file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); - - same = a_modified == f_modified; - } - } - - g_clear_object (&attachment_file_info); - g_clear_object (&disk_file_info); - } - - if (!same) + if (e_attachment_check_file_changed (attachment, NULL, NULL)) { + e_attachment_set_may_reload (attachment, TRUE); g_clear_object (&file); + } } /* If the attachment already references a GFile, we can launch diff --git a/src/e-util/e-attachment.h b/src/e-util/e-attachment.h index 2a060a76f6..d46752a49b 100644 --- a/src/e-util/e-attachment.h +++ b/src/e-util/e-attachment.h @@ -131,6 +131,12 @@ GList * e_attachment_list_apps (EAttachment *attachment); GAppInfo * e_attachment_ref_default_app (EAttachment *attachment); void e_attachment_update_store_columns (EAttachment *attachment); +gboolean e_attachment_check_file_changed (EAttachment *attachment, + gboolean *out_file_exists, + GCancellable *cancellable); +void e_attachment_set_may_reload (EAttachment *attachment, + gboolean may_reload); +gboolean e_attachment_get_may_reload (EAttachment *attachment); /* Asynchronous Operations */ void e_attachment_load_async (EAttachment *attachment, diff --git a/src/mail/em-composer-utils.c b/src/mail/em-composer-utils.c index 122c6079fd..b82cff6211 100644 --- a/src/mail/em-composer-utils.c +++ b/src/mail/em-composer-utils.c @@ -602,6 +602,79 @@ composer_presend_check_unwanted_html (EMsgComposer *composer, return check_passed; } +static gboolean +composer_presend_check_attachments (EMsgComposer *composer, + EMailSession *session) +{ + EAttachmentView *view; + EAttachmentStore *store; + GList *attachments, *link; + gboolean can_send = TRUE; + EAttachment *first_changed = NULL; + guint n_changed = 0; + + view = e_msg_composer_get_attachment_view (composer); + store = e_attachment_view_get_store (view); + attachments = e_attachment_store_get_attachments (store); + + for (link = attachments; link; link = g_list_next (link)) { + EAttachment *attach = link->data; + gboolean file_exists = FALSE; + + if (e_attachment_check_file_changed (attach, &file_exists, NULL) && + file_exists) { + e_attachment_set_may_reload (attach, TRUE); + if (!first_changed) + first_changed = attach; + n_changed++; + } else { + e_attachment_set_may_reload (attach, FALSE); + } + } + + if (n_changed > 0) { + GFileInfo *file_info = NULL; + const gchar *display_name = NULL; + gchar *title, *text; + + if (n_changed == 1) { + file_info = e_attachment_ref_file_info (first_changed); + display_name = g_file_info_get_display_name (file_info); + if (display_name && !*display_name) + display_name = NULL; + } + + title = g_strdup_printf (ngettext ( + "Attachment changed", + "%d attachments changed", + n_changed), n_changed); + + if (n_changed == 1 && display_name) { + text = g_strdup_printf (_("Attachment “%s” changed after being added to the message. Do you want to send the message anyway?"), + display_name); + } else { + text = g_strdup_printf (ngettext ("One attachment changed after being added to the message. Do you want to send the message anyway?", + "%d attachments changed after being added to the message. Do you want to send the message anyway?", + n_changed), n_changed); + } + + can_send = e_util_prompt_user ( + GTK_WINDOW (composer), + "org.gnome.evolution.mail", + "prompt-on-changed-attachment", + "mail:ask-composer-changed-attachment", + title, text, NULL); + + g_clear_object (&file_info); + g_free (title); + g_free (text); + } + + g_list_free_full (attachments, g_object_unref); + + return can_send; +} + static void composer_send_completed (GObject *source_object, GAsyncResult *result, @@ -4934,6 +5007,10 @@ em_configure_new_composer (EMsgComposer *composer, G_CALLBACK (composer_presend_check_unwanted_html), session); g_signal_connect ( + composer, "presend", + G_CALLBACK (composer_presend_check_attachments), session); + + g_signal_connect ( composer, "send", G_CALLBACK (em_utils_composer_send_cb), session); diff --git a/src/mail/mail-config.ui b/src/mail/mail-config.ui index 3dadcae6be..84a2fdc3ca 100644 --- a/src/mail/mail-config.ui +++ b/src/mail/mail-config.ui @@ -1177,6 +1177,22 @@ <property name="position">8</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="chkPromptChangedAttachment"> + <property name="label" translatable="yes" comments="This is in the context of: Ask for confirmation before...">Sending a message with _attachments which changed since being attached</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="xalign">0.5</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">9</property> + </packing> + </child> </object> </child> </object> diff --git a/src/mail/mail.error.xml b/src/mail/mail.error.xml index e58559ebb5..ad1fa82d61 100644 --- a/src/mail/mail.error.xml +++ b/src/mail/mail.error.xml @@ -679,4 +679,11 @@ in the folder will be available in offline mode.</_secondary> <_primary>Label named “{0}” already exists</_primary> <_secondary>Choose a different name, please.</_secondary> </error> + + <error id="ask-composer-changed-attachment" type="warning" default="GTK_RESPONSE_CANCEL"> + <primary>{0}</primary> + <secondary>{1}</secondary> + <button response="GTK_RESPONSE_CANCEL" _label="_Cancel"/> + <button response="GTK_RESPONSE_YES" _label="_Send"></button> + </error> </error-list> diff --git a/src/modules/mail/em-composer-prefs.c b/src/modules/mail/em-composer-prefs.c index 24b6734a80..77ba8dc728 100644 --- a/src/modules/mail/em-composer-prefs.c +++ b/src/modules/mail/em-composer-prefs.c @@ -1223,6 +1223,12 @@ em_composer_prefs_construct (EMComposerPrefs *prefs, widget, "active", G_SETTINGS_BIND_DEFAULT); + widget = e_builder_get_widget (prefs->builder, "chkPromptChangedAttachment"); + g_settings_bind ( + settings, "prompt-on-changed-attachment", + widget, "active", + G_SETTINGS_BIND_DEFAULT); + widget = e_builder_get_widget (prefs->builder, "chkAutoSmileys"); g_settings_bind ( settings, "composer-magic-smileys", |