From cce04b814f572014c7b8d9ec16f701b8e2f0fe82 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Wed, 29 Jan 2014 18:18:31 -0800 Subject: [PATCH] Add option to save sent mail This adds the ability for Geary to push sent mail up to the account's Sent Mail folder (if available). There's an accompanying account option that defaults to on (meaning: push sent mail). The current implementation will leave messages in the Outbox (though they won't be sent again) if they fail to be pushed to Sent Mail. This isn't the best solution, but it at least means you have a way of seeing the problem and hopefully copying the data elsewhere manually if you need to save it. Note that Geary might not always recognize an account's Sent Mail folder. This is the case for any "Other" accounts that don't support the "special use" or "xlist" IMAP extensions. In this case, Geary will either throw an error and leave messages in the Outbox, or erase the message from the Outbox when it's sent, depending on the value of the account's save sent mail option. Better support for detecting the Sent Mail folder in every case is coming soon. Closes: bgo #713263 --- po/POTFILES.in | 1 + sql/version-017.sql | 7 + src/CMakeLists.txt | 1 + src/client/accounts/add-edit-page.vala | 16 ++ src/client/application/geary-controller.vala | 29 +++- src/client/components/status-bar.vala | 9 +- src/client/composer/composer-window.vala | 2 +- .../conversation-viewer.vala | 13 ++ .../abstract/geary-abstract-account.vala | 8 +- src/engine/api/geary-account-information.vala | 31 ++++ src/engine/api/geary-account.vala | 1 + src/engine/api/geary-email-flags.vala | 11 ++ .../imap-db/outbox/smtp-outbox-folder.vala | 153 ++++++++++++++---- .../imap-engine-generic-account.vala | 5 +- .../imap-engine-generic-sent-mail-folder.vala | 10 +- src/engine/imap/api/imap-folder.vala | 2 + src/engine/memory/memory-offset-buffer.vala | 47 ++++++ src/engine/rfc822/rfc822-message.vala | 91 +++++------ src/mailer/main.vala | 2 +- theming/message-viewer.css | 9 ++ theming/message-viewer.html | 1 + ui/login.glade | 21 +++ 22 files changed, 374 insertions(+), 96 deletions(-) create mode 100644 sql/version-017.sql create mode 100644 src/engine/memory/memory-offset-buffer.vala diff --git a/po/POTFILES.in b/po/POTFILES.in index 6279475f..521a3d52 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -277,6 +277,7 @@ src/engine/memory/memory-byte-buffer.vala src/engine/memory/memory-empty-buffer.vala src/engine/memory/memory-file-buffer.vala src/engine/memory/memory-growable-buffer.vala +src/engine/memory/memory-offset-buffer.vala src/engine/memory/memory-string-buffer.vala src/engine/memory/memory-unowned-byte-array-buffer.vala src/engine/memory/memory-unowned-bytes-buffer.vala diff --git a/sql/version-017.sql b/sql/version-017.sql new file mode 100644 index 00000000..f1cb4d33 --- /dev/null +++ b/sql/version-017.sql @@ -0,0 +1,7 @@ +-- +-- We're now keeping sent mail around after sending, so we can also push it up +-- to the Sent Mail folder. This column lets us keep track of the state of +-- messages in the outbox. +-- + +ALTER TABLE SmtpOutboxTable ADD COLUMN sent INTEGER DEFAULT 0; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4c3a45a1..4a04d926 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -218,6 +218,7 @@ engine/memory/memory-byte-buffer.vala engine/memory/memory-empty-buffer.vala engine/memory/memory-file-buffer.vala engine/memory/memory-growable-buffer.vala +engine/memory/memory-offset-buffer.vala engine/memory/memory-string-buffer.vala engine/memory/memory-unowned-byte-array-buffer.vala engine/memory/memory-unowned-bytes-buffer.vala diff --git a/src/client/accounts/add-edit-page.vala b/src/client/accounts/add-edit-page.vala index d8f5830c..edcb7b5b 100644 --- a/src/client/accounts/add-edit-page.vala +++ b/src/client/accounts/add-edit-page.vala @@ -50,6 +50,11 @@ public class AddEditPage : Gtk.Box { set { check_remember_password.active = value; } } + public bool save_sent_mail { + get { return check_save_sent_mail.active; } + set { check_save_sent_mail.active = value; } + } + public string smtp_username { get { return entry_smtp_username.text; } set { entry_smtp_username.text = value; } @@ -139,6 +144,7 @@ public class AddEditPage : Gtk.Box { private Gtk.Entry entry_nickname; private Gtk.ComboBoxText combo_service; private Gtk.CheckButton check_remember_password; + private Gtk.CheckButton check_save_sent_mail; private Gtk.Alignment other_info; @@ -198,6 +204,7 @@ public class AddEditPage : Gtk.Box { label_password = (Gtk.Label) builder.get_object("label: password"); entry_password = (Gtk.Entry) builder.get_object("entry: password"); check_remember_password = (Gtk.CheckButton) builder.get_object("check: remember_password"); + check_save_sent_mail = (Gtk.CheckButton) builder.get_object("check: save_sent_mail"); label_error = (Gtk.Label) builder.get_object("label: error"); @@ -242,6 +249,7 @@ public class AddEditPage : Gtk.Box { entry_real_name.changed.connect(on_changed); entry_nickname.changed.connect(on_changed); check_remember_password.toggled.connect(on_changed); + check_save_sent_mail.toggled.connect(on_changed); combo_service.changed.connect(on_changed); entry_imap_host.changed.connect(on_changed); entry_imap_port.changed.connect(on_changed); @@ -280,6 +288,8 @@ public class AddEditPage : Gtk.Box { info.smtp_credentials != null ? info.smtp_credentials.user : null, info.smtp_credentials != null ? info.smtp_credentials.pass : null, info.service_provider, + info.save_sent_mail, + info.allow_save_sent_mail(), info.default_imap_server_host, info.default_imap_server_port, info.default_imap_server_ssl, @@ -304,6 +314,8 @@ public class AddEditPage : Gtk.Box { string? initial_smtp_username = null, string? initial_smtp_password = null, int initial_service_provider = Geary.ServiceProvider.GMAIL, + bool initial_save_sent_mail = true, + bool allow_save_sent_mail = true, string? initial_default_imap_host = null, uint16 initial_default_imap_port = Geary.Imap.ClientConnection.DEFAULT_PORT_SSL, bool initial_default_imap_ssl = true, @@ -322,6 +334,8 @@ public class AddEditPage : Gtk.Box { email_address = initial_email ?? ""; password = initial_imap_password != null ? initial_imap_password : ""; remember_password = initial_remember_password; + save_sent_mail = initial_save_sent_mail; + check_save_sent_mail.sensitive = allow_save_sent_mail; set_service_provider((Geary.ServiceProvider) initial_service_provider); combo_imap_encryption.active = Encryption.NONE; // Must be default; set to real value below. combo_smtp_encryption.active = Encryption.NONE; @@ -538,6 +552,7 @@ public class AddEditPage : Gtk.Box { account_information.imap_remember_password = remember_password; account_information.smtp_remember_password = remember_password; account_information.service_provider = get_service_provider(); + account_information.save_sent_mail = save_sent_mail; account_information.default_imap_server_host = imap_host; account_information.default_imap_server_port = imap_port; account_information.default_imap_server_ssl = imap_ssl; @@ -573,6 +588,7 @@ public class AddEditPage : Gtk.Box { welcome_box.visible = mode == PageMode.WELCOME; entry_nickname.visible = label_nickname.visible = mode != PageMode.WELCOME; storage_container.visible = mode == PageMode.EDIT; + check_save_sent_mail.visible = mode != PageMode.WELCOME; if (get_service_provider() == Geary.ServiceProvider.OTHER) { // Display all options for custom providers. diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index 6218f56a..6b61deab 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -653,7 +653,11 @@ public class GearyController : Geary.BaseObject { break; case Geary.Account.Problem.EMAIL_DELIVERY_FAILURE: - handle_send_failure(); + handle_outbox_failure(StatusBar.Message.OUTBOX_SEND_FAILURE); + break; + + case Geary.Account.Problem.SAVE_SENT_MAIL_FAILED: + handle_outbox_failure(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED); break; default: @@ -661,7 +665,7 @@ public class GearyController : Geary.BaseObject { } } - private void handle_send_failure() { + private void handle_outbox_failure(StatusBar.Message message) { bool activate_message = false; try { // Due to a timing hole where it's possible to delete a message @@ -685,16 +689,29 @@ public class GearyController : Geary.BaseObject { } if (activate_message) { - if (!main_window.status_bar.is_message_active(StatusBar.Message.OUTBOX_SEND_FAILURE)) - main_window.status_bar.activate_message(StatusBar.Message.OUTBOX_SEND_FAILURE); - libnotify.set_error_notification(_("Error sending email"), - _("Geary encountered an error sending an email. If the problem persists, please manually delete the email from your Outbox folder.")); + if (!main_window.status_bar.is_message_active(message)) + main_window.status_bar.activate_message(message); + switch (message) { + case StatusBar.Message.OUTBOX_SEND_FAILURE: + libnotify.set_error_notification(_("Error sending email"), + _("Geary encountered an error sending an email. If the problem persists, please manually delete the email from your Outbox folder.")); + break; + + case StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED: + libnotify.set_error_notification(_("Error saving sent mail"), + _("Geary encountered an error saving a sent message to Sent Mail. The message will stay in your Outbox folder until you delete it.")); + break; + + default: + assert_not_reached(); + } } } private void on_account_email_removed(Geary.Folder folder, Gee.Collection ids) { if (folder.special_folder_type == Geary.SpecialFolderType.OUTBOX) { main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SEND_FAILURE); + main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED); libnotify.clear_error_notification(); } } diff --git a/src/client/components/status-bar.vala b/src/client/components/status-bar.vala index 95c877b1..6a7bf582 100644 --- a/src/client/components/status-bar.vala +++ b/src/client/components/status-bar.vala @@ -16,7 +16,8 @@ public class StatusBar : Gtk.Statusbar { public enum Message { OUTBOX_SENDING, - OUTBOX_SEND_FAILURE; + OUTBOX_SEND_FAILURE, + OUTBOX_SAVE_SENT_MAIL_FAILED; internal string get_text() { switch (this) { @@ -26,6 +27,10 @@ public class StatusBar : Gtk.Statusbar { case Message.OUTBOX_SEND_FAILURE: /// Displayed in the space-limited status bar when a message fails to be sent due to error. return _("Error sending email"); + case Message.OUTBOX_SAVE_SENT_MAIL_FAILED: + // Displayed in the space-limited status bar when a message fails to be uploaded + // to Sent Mail after being sent. + return _("Error saving sent mail"); default: assert_not_reached(); } @@ -37,6 +42,8 @@ public class StatusBar : Gtk.Statusbar { return Context.OUTBOX; case Message.OUTBOX_SEND_FAILURE: return Context.OUTBOX; + case Message.OUTBOX_SAVE_SENT_MAIL_FAILED: + return Context.OUTBOX; default: assert_not_reached(); } diff --git a/src/client/composer/composer-window.vala b/src/client/composer/composer-window.vala index f1532a67..0f8f5b0b 100644 --- a/src/client/composer/composer-window.vala +++ b/src/client/composer/composer-window.vala @@ -850,7 +850,7 @@ public class ComposerWindow : Gtk.Window { // only save HTML drafts to avoid resetting the DOM (which happens when converting the // HTML to flowed text) draft_id = yield drafts_folder.create_email_async(new Geary.RFC822.Message.from_composed_email( - get_composed_email(null, true)), flags, null, draft_id, cancellable); + get_composed_email(null, true), null), flags, null, draft_id, cancellable); draft_save_label.label = DRAFT_SAVED_TEXT; } catch (Error e) { diff --git a/src/client/conversation-viewer/conversation-viewer.vala b/src/client/conversation-viewer/conversation-viewer.vala index c1ea5f93..94375a92 100644 --- a/src/client/conversation-viewer/conversation-viewer.vala +++ b/src/client/conversation-viewer/conversation-viewer.vala @@ -593,6 +593,7 @@ public class ConversationViewer : Gtk.Box { //