[geary/mjog/account-command-stacks: 42/77] Implement moving conversations as undoable commands



commit 0132981bddab010faa838916fd917521a3dada6b
Author: Michael Gratton <mike vee net>
Date:   Sun Oct 6 10:30:19 2019 +1100

    Implement moving conversations as undoable commands

 src/client/application/application-controller.vala | 223 +++++++++++++++++++++
 src/client/components/main-window.vala             |  85 ++++++++
 2 files changed, 308 insertions(+)
---
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index a03c3428..8cc1a099 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -650,6 +650,113 @@ public class Application.Controller : Geary.BaseObject {
         }
     }
 
+    public async void move_conversations(Geary.FolderSupport.Move source,
+                                         Geary.Folder destination,
+                                         Gee.Collection<Geary.App.Conversation> conversations)
+        throws GLib.Error {
+        AccountContext? context = this.accounts.get(source.account.information);
+        if (context != null) {
+            yield this.commands.execute(
+                new MoveEmailCommand(
+                    source,
+                    destination,
+                    to_in_folder_email_ids(conversations),
+                    /// Translators: Label for in-app undo
+                    /// notification. String substitution is the name
+                    /// of the destination folder.
+                    ngettext(
+                        "Conversation moved to %s",
+                        "Conversations moved to %s",
+                        conversations.size
+                    ).printf(destination.get_display_name()),
+                    /// Translators: Label for in-app undo
+                    /// notification. String substitution is the name
+                    /// of the source folder.
+                    ngettext(
+                        "Conversation restored to %s",
+                        "Conversations restored to %s",
+                        conversations.size
+                    ).printf(source.get_display_name())
+                ),
+                context.cancellable
+            );
+        }
+    }
+
+    public async void move_conversations_special(Geary.FolderSupport.Move source,
+                                                 Geary.SpecialFolderType destination,
+                                                 Gee.Collection<Geary.App.Conversation> conversations)
+        throws GLib.Error {
+        AccountContext? context = this.accounts.get(source.account.information);
+        if (context != null) {
+            Geary.Folder? dest = source.account.get_special_folder(destination);
+            if (dest == null) {
+                throw new Geary.EngineError.NOT_FOUND(
+                    "No folder found for: %s", destination.to_string()
+                );
+            }
+
+            yield this.commands.execute(
+                new MoveEmailCommand(
+                    source,
+                    dest,
+                    to_in_folder_email_ids(conversations),
+                    /// Translators: Label for in-app undo
+                    /// notification. String substitution is the name
+                    /// of the destination folder.
+                    ngettext(
+                        "Conversation moved to %s",
+                        "Conversations moved to %s",
+                        conversations.size
+                    ).printf(dest.get_display_name()),
+                    /// Translators: Label for in-app undo
+                    /// notification. String substitution is the name
+                    /// of the source folder.
+                    ngettext(
+                        "Conversation restored to %s",
+                        "Conversations restored to %s",
+                        conversations.size
+                    ).printf(source.get_display_name())
+                ),
+                context.cancellable
+            );
+        }
+    }
+
+    public async void move_messages_special(Geary.FolderSupport.Move source,
+                                            Geary.SpecialFolderType destination,
+                                            Gee.Collection<Geary.EmailIdentifier> messages)
+        throws GLib.Error {
+        AccountContext? context = this.accounts.get(source.account.information);
+        if (context != null) {
+            Geary.Folder? dest = source.account.get_special_folder(destination);
+            if (dest == null) {
+                throw new Geary.EngineError.NOT_FOUND(
+                    "No folder found for: %s", destination.to_string()
+                );
+            }
+
+            yield this.commands.execute(
+                new MoveEmailCommand(
+                    source,
+                    dest,
+                    messages,
+                    /// Translators: Label for in-app undo
+                    /// notification. String substitution is the name
+                    /// of the destination folder.
+                    ngettext(
+                        "Message moved to %s",
+                        "Messages moved to %s",
+                        messages.size
+                    ).printf(destination.get_display_name()),
+                    /// Translators: Label for in-app undo
+                    /// notification. String substitution is the name
+                    /// of the source folder.
+                    ngettext(
+                        "Message restored to %s",
+                        "Messages restored to %s",
+                        messages.size
+                    ).printf(source.get_display_name())
                 ),
                 context.cancellable
             );
@@ -1917,6 +2024,19 @@ public class Application.Controller : Geary.BaseObject {
         return false;
     }
 
+    private Gee.Collection<Geary.EmailIdentifier>
+        to_in_folder_email_ids(Gee.Collection<Geary.App.Conversation> conversations) {
+        Gee.Collection<Geary.EmailIdentifier> messages =
+            new Gee.LinkedList<Geary.EmailIdentifier>();
+        foreach (Geary.App.Conversation conversation in conversations) {
+            foreach (Geary.Email email in
+                     conversation.get_emails(RECV_DATE_ASCENDING, IN_FOLDER)) {
+                messages.add(email.id);
+            }
+        }
+        return messages;
+    }
+
     private void on_account_available(Geary.AccountInformation info) {
         Geary.Account? account = null;
         try {
@@ -2132,3 +2252,106 @@ private class Application.MarkEmailCommand : Command {
 }
 
 
+private abstract class Application.RevokableCommand : Command {
+
+
+    public override bool can_undo {
+        get { return this.revokable != null && this.revokable.valid; }
+    }
+
+    private Geary.Revokable? revokable = null;
+
+
+    public override async void execute(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        set_revokable(yield execute_impl(cancellable));
+        if (this.revokable != null && this.revokable.valid) {
+            yield this.revokable.commit_async(cancellable);
+        }
+    }
+
+    public override async void undo(GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        if (this.revokable == null) {
+            throw new Geary.EngineError.UNSUPPORTED(
+                "Cannot undo command, no revokable available"
+            );
+        }
+
+        yield this.revokable.revoke_async(cancellable);
+        set_revokable(null);
+    }
+
+    protected abstract async Geary.Revokable
+        execute_impl(GLib.Cancellable cancellable)
+        throws GLib.Error;
+
+    private void set_revokable(Geary.Revokable? updated) {
+        if (this.revokable != null) {
+            this.revokable.committed.disconnect(on_revokable_committed);
+        }
+
+        this.revokable = updated;
+
+        if (this.revokable != null) {
+            this.revokable.committed.connect(on_revokable_committed);
+        }
+    }
+
+    private void on_revokable_committed(Geary.Revokable? updated) {
+        set_revokable(updated);
+    }
+
+}
+
+
+private class Application.MoveEmailCommand : RevokableCommand {
+
+
+    private Geary.FolderSupport.Move source;
+    private Gee.Collection<Geary.EmailIdentifier> source_messages;
+
+    private Geary.Folder destination;
+
+
+    public MoveEmailCommand(Geary.FolderSupport.Move source,
+                            Geary.Folder destination,
+                            Gee.Collection<Geary.EmailIdentifier> messages,
+                            string? executed_label = null,
+                            string? undone_label = null) {
+        this.source = source;
+        this.source_messages = messages;
+        this.destination = destination;
+
+        this.executed_label = executed_label;
+        this.undone_label = undone_label;
+    }
+
+    protected override async Geary.Revokable
+        execute_impl(GLib.Cancellable cancellable)
+        throws GLib.Error {
+        bool open = false;
+        try {
+            yield this.source.open_async(
+                Geary.Folder.OpenFlags.NO_DELAY, cancellable
+            );
+            open = true;
+            return yield this.source.move_email_async(
+                this.source_messages,
+                this.destination.path,
+                cancellable
+            );
+        } finally {
+            if (open) {
+                try {
+                    yield this.source.close_async(null);
+                } catch (GLib.Error err) {
+                    // ignored
+                }
+            }
+        }
+    }
+
+}
+
+
diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala
index cf30b1b5..eb441611 100644
--- a/src/client/components/main-window.vala
+++ b/src/client/components/main-window.vala
@@ -1499,18 +1499,87 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     }
 
     private void on_mark_as_spam_toggle() {
+        Geary.FolderSupport.Move? source =
+            this.current_folder as Geary.FolderSupport.Move;
+        if (source != null) {
+            Geary.SpecialFolderType destination =
+                (source.special_folder_type != SPAM)
+                ? Geary.SpecialFolderType.SPAM
+                : Geary.SpecialFolderType.INBOX;
+            this.application.controller.move_conversations_special.begin(
+                source,
+                destination,
+                this.conversation_list_view.get_selected_conversations(),
+                (obj, res) => {
+                    try {
+                        this.application.controller.move_conversations_special.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(source.account.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_move_conversation(Geary.Folder destination) {
+        Geary.FolderSupport.Move source =
+            this.current_folder as Geary.FolderSupport.Move;
+        if (source != null) {
+            this.application.controller.move_conversations.begin(
+                source,
+                destination,
+                this.conversation_list_view.get_selected_conversations(),
+                (obj, res) => {
+                    try {
+                        this.application.controller.move_conversations.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(source.account.information, err);
+                    }
+                }
+            );
+
+        }
     }
 
     private void on_copy_conversation(Geary.Folder destination) {
     }
 
     private void on_archive_conversation() {
+        Geary.FolderSupport.Move source =
+            this.current_folder as Geary.FolderSupport.Move;
+        if (source != null) {
+            this.application.controller.move_conversations_special.begin(
+                source,
+                Geary.SpecialFolderType.ARCHIVE,
+                this.conversation_list_view.get_selected_conversations(),
+                (obj, res) => {
+                    try {
+                        this.application.controller.move_conversations_special.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(source.account.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_trash_conversation() {
+        Geary.FolderSupport.Move source =
+            this.current_folder as Geary.FolderSupport.Move;
+        if (source != null) {
+            this.application.controller.move_conversations_special.begin(
+                source,
+                Geary.SpecialFolderType.TRASH,
+                this.conversation_list_view.get_selected_conversations(),
+                (obj, res) => {
+                    try {
+                        this.application.controller.move_conversations_special.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(source.account.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_delete_conversation() {
@@ -1662,6 +1731,22 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
     }
 
     private void on_trash_message(ConversationEmail target_view) {
+        Geary.FolderSupport.Move? source =
+            this.current_folder as Geary.FolderSupport.Move;
+        if (source != null) {
+            this.application.controller.move_messages_special.begin(
+                source,
+                TRASH,
+                Geary.Collection.single(target_view.email.id),
+                (obj, res) => {
+                    try {
+                        this.application.controller.move_messages_special.end(res);
+                    } catch (GLib.Error err) {
+                        handle_error(source.account.information, err);
+                    }
+                }
+            );
+        }
     }
 
     private void on_delete_message(ConversationEmail target_view) {


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