[geary/mjog/mutiple-main-windows: 6/14] Add Application.Controller::account_available and unavailable



commit ab8755aa7820a7ce07635171061a421e664dc943
Author: Michael Gratton <mike vee net>
Date:   Wed Nov 13 15:19:38 2019 +1100

    Add Application.Controller::account_available and unavailable
    
    Clean up methods for opening an account, emit the signals when accounts
    have been opened/before being closed. Use these signals in MainWindow to
    manage its own internal state rather than relying on the controller.

 src/client/application/application-controller.vala | 197 ++++++++++----------
 .../application/application-main-window.vala       | 206 +++++++++++++--------
 src/client/dialogs/upgrade-dialog.vala             |  20 +-
 3 files changed, 241 insertions(+), 182 deletions(-)
---
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index 8bdef3ff..0c5889c7 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -34,7 +34,7 @@ internal class Application.Controller : Geary.BaseObject {
     /** Determines if the controller is open. */
     public bool is_open {
         get {
-            return !this.open_cancellable.is_cancelled();
+            return !this.controller_open.is_cancelled();
         }
     }
 
@@ -62,15 +62,13 @@ internal class Application.Controller : Geary.BaseObject {
         new Gee.HashMap<Geary.AccountInformation,AccountContext>();
 
     // Cancelled if the controller is closed
-    private GLib.Cancellable open_cancellable;
+    private GLib.Cancellable controller_open;
 
     private UpgradeDialog upgrade_dialog;
     private Folks.IndividualAggregator folks;
 
     private PluginManager plugin_manager;
 
-    private Cancellable cancellable_open_account = new Cancellable();
-
     // List composers that have not yet been closed
     private Gee.Collection<Composer.Widget> composer_widgets =
         new Gee.LinkedList<Composer.Widget>();
@@ -79,13 +77,38 @@ internal class Application.Controller : Geary.BaseObject {
     private Gee.List<string?> pending_mailtos = new Gee.ArrayList<string>();
 
 
+    /**
+     * Emitted when an account is added or is enabled.
+     *
+     * This will be emitted after an account is opened and added to
+     * the controller.
+     */
+    public signal void account_available(AccountContext context);
+
+    /**
+     * Emitted when an account is removed or is disabled.
+     *
+     * This will be emitted after the account is removed from the
+     * controller's collection of accounts, but before the {@link
+     * AccountContext.cancellable} is cancelled and before the account
+     * itself is closed.
+     *
+     * The `is_shutdown` argument will be true if the application is
+     * in the middle of quitting, otherwise if the account was simply
+     * removed but the application will keep running, then it will be
+     * false.
+     */
+    public signal void account_unavailable(AccountContext context,
+                                           bool is_shutdown);
+
+
     /**
      * Constructs a new instance of the controller.
      */
     public async Controller(Client application,
                             GLib.Cancellable cancellable) {
         this.application = application;
-        this.open_cancellable = cancellable;
+        this.controller_open = cancellable;
 
         Geary.Engine engine = this.application.engine;
 
@@ -139,9 +162,8 @@ internal class Application.Controller : Geary.BaseObject {
         );
         this.plugin_manager.load();
 
-        // Create the main window (must be done after creating actions.)
-        main_window = new MainWindow(this.application);
-        main_window.retry_service_problem.connect(on_retry_service_problem);
+        this.main_window = new MainWindow(application, this);
+        this.main_window.retry_service_problem.connect(on_retry_service_problem);
 
         engine.account_available.connect(on_account_available);
 
@@ -265,7 +287,7 @@ internal class Application.Controller : Geary.BaseObject {
         // Now that all composers are closed, we can shut down the
         // rest of the client and engine. Cancel internal processes
         // first so they don't block shutdown.
-        this.open_cancellable.cancel();
+        this.controller_open.cancel();
 
         // Release folder and conversations in the main window
         yield this.main_window.select_folder(null, false);
@@ -278,8 +300,6 @@ internal class Application.Controller : Geary.BaseObject {
         // be freed up
         this.plugin_manager.notifications.clear_folders();
 
-        this.cancellable_open_account.cancel();
-
         // Create a copy of known accounts so the loop below does not
         // explode if accounts are removed while iterating.
         var closing_accounts = new Gee.LinkedList<AccountContext>();
@@ -859,16 +879,35 @@ internal class Application.Controller : Geary.BaseObject {
         }
     }
 
+    /** Returns the first open account, sorted by ordinal. */
+    internal Geary.AccountInformation? get_first_account() {
+        return this.accounts.keys.iterator().fold<Geary.AccountInformation?>(
+            (next, prev) => {
+                return prev == null || next.ordinal < prev.ordinal ? next : prev;
+            },
+            null
+        );
+    }
+
     /** Expunges removed accounts while the controller remains open. */
     internal async void expunge_accounts() {
         try {
-            yield this.account_manager.expunge_accounts(this.open_cancellable);
+            yield this.account_manager.expunge_accounts(this.controller_open);
         } catch (GLib.Error err) {
             report_problem(new Geary.ProblemReport(err));
         }
     }
 
-    private void open_account(Geary.Account account) {
+    private async void open_account(Geary.Account account) {
+        AccountContext context = new AccountContext(
+            account,
+            new Geary.App.EmailStore(account),
+            new Application.ContactStore(account, this.folks)
+        );
+        this.accounts.set(account.information, context);
+
+        this.upgrade_dialog.add_account(account, this.controller_open);
+
         account.information.authentication_failure.connect(
             on_authentication_failure
         );
@@ -876,8 +915,49 @@ internal class Application.Controller : Geary.BaseObject {
         account.notify["current-status"].connect(
             on_account_status_notify
         );
+        account.email_removed.connect(on_account_email_removed);
+        account.folders_available_unavailable.connect(on_folders_available_unavailable);
         account.report_problem.connect(on_report_problem);
-        connect_account_async.begin(account, cancellable_open_account);
+
+        Geary.Smtp.ClientService? smtp = (
+            account.outgoing as Geary.Smtp.ClientService
+        );
+        if (smtp != null) {
+            smtp.email_sent.connect(on_sent);
+            smtp.sending_monitor.start.connect(on_sending_started);
+            smtp.sending_monitor.finish.connect(on_sending_finished);
+        }
+
+        bool retry = false;
+        do {
+            try {
+                account.set_data(PROP_ATTEMPT_OPEN_ACCOUNT, true);
+                yield account.open_async(this.controller_open);
+                retry = false;
+            } catch (Error open_err) {
+                debug("Unable to open account %s: %s", account.to_string(), open_err.message);
+
+                if (open_err is Geary.EngineError.CORRUPT) {
+                    retry = yield account_database_error_async(account);
+                }
+
+                if (!retry) {
+                    report_problem(
+                        new Geary.AccountProblemReport(
+                            account.information,
+                            open_err
+                        )
+                    );
+
+                    this.account_manager.disable_account(account.information);
+                    this.accounts.unset(account.information);
+                }
+            }
+        } while (retry);
+
+        account_available(context);
+        display_main_window_if_ready();
+        update_account_status();
     }
 
     private async void close_account(Geary.AccountInformation config,
@@ -890,6 +970,8 @@ internal class Application.Controller : Geary.BaseObject {
             // Guard against trying to close the account twice
             this.accounts.unset(account.information);
 
+            this.upgrade_dialog.remove_account(account);
+
             // Stop updating status and showing errors when closing
             // the account - the user doesn't care any more
             account.report_problem.disconnect(on_report_problem);
@@ -917,21 +999,7 @@ internal class Application.Controller : Geary.BaseObject {
             // status notifications for it
             update_account_status();
 
-            // If we're not shutting down, select the inbox of the
-            // first account so that we show something other than
-            // empty conversation list/viewer.
-            Geary.Folder? to_select = null;
-            if (!is_shutdown) {
-                Geary.AccountInformation? first_account = get_first_account();
-                if (first_account != null) {
-                    AccountContext? first_context = this.accounts[first_account];
-                    if (first_context != null) {
-                        to_select = first_context.inbox;
-                    }
-                }
-            }
-
-            yield this.main_window.remove_account(account, to_select);
+            account_unavailable(context, is_shutdown);
 
             context.cancellable.cancel();
             context.contacts.close();
@@ -1159,61 +1227,6 @@ internal class Application.Controller : Geary.BaseObject {
         main_window.status_bar.deactivate_message(StatusBar.Message.OUTBOX_SENDING);
     }
 
-    private async void connect_account_async(Geary.Account account, Cancellable? cancellable = null) {
-        AccountContext context = new AccountContext(
-            account,
-            new Geary.App.EmailStore(account),
-            new Application.ContactStore(account, this.folks)
-        );
-
-        // XXX Need to set this early since
-        // on_folders_available_unavailable expects it to be there
-        this.accounts.set(account.information, context);
-
-        account.email_removed.connect(on_account_email_removed);
-        account.folders_available_unavailable.connect(on_folders_available_unavailable);
-
-        Geary.Smtp.ClientService? smtp = (
-            account.outgoing as Geary.Smtp.ClientService
-        );
-        if (smtp != null) {
-            smtp.email_sent.connect(on_sent);
-            smtp.sending_monitor.start.connect(on_sending_started);
-            smtp.sending_monitor.finish.connect(on_sending_finished);
-        }
-
-        bool retry = false;
-        do {
-            try {
-                account.set_data(PROP_ATTEMPT_OPEN_ACCOUNT, true);
-                yield account.open_async(cancellable);
-                retry = false;
-            } catch (Error open_err) {
-                debug("Unable to open account %s: %s", account.to_string(), open_err.message);
-
-                if (open_err is Geary.EngineError.CORRUPT) {
-                    retry = yield account_database_error_async(account);
-                }
-
-                if (!retry) {
-                    report_problem(
-                        new Geary.AccountProblemReport(
-                            account.information,
-                            open_err
-                        )
-                    );
-
-                    this.account_manager.disable_account(account.information);
-                    this.accounts.unset(account.information);
-                }
-            }
-        } while (retry);
-
-        main_window.folder_list.set_user_folders_root_name(account, _("Labels"));
-        display_main_window_if_ready();
-        update_account_status();
-    }
-
     // Returns true if the caller should try opening the account again
     private async bool account_database_error_async(Geary.Account account) {
         bool retry = true;
@@ -1272,8 +1285,8 @@ internal class Application.Controller : Geary.BaseObject {
      */
     private void display_main_window_if_ready() {
         if (did_attempt_open_all_accounts() &&
-            !upgrade_dialog.visible &&
-            !cancellable_open_account.is_cancelled() &&
+            !this.upgrade_dialog.visible &&
+            !this.controller_open.is_cancelled() &&
             !this.application.is_background_service)
             main_window.show();
     }
@@ -1624,15 +1637,6 @@ internal class Application.Controller : Geary.BaseObject {
         }
     }
 
-    private Geary.AccountInformation? get_first_account() {
-        return this.accounts.keys.iterator().fold<Geary.AccountInformation?>(
-            (next, prev) => {
-                return prev == null || next.ordinal < prev.ordinal ? next : prev;
-            },
-            null
-        );
-    }
-
     private bool should_add_folder(Gee.Collection<Geary.Folder>? all,
                                    Geary.Folder folder) {
         // if folder is openable, add it
@@ -1673,8 +1677,7 @@ internal class Application.Controller : Geary.BaseObject {
         }
 
         if (account != null) {
-            upgrade_dialog.add_account(account, cancellable_open_account);
-            open_account(account);
+            this.open_account.begin(account);
         }
     }
 
diff --git a/src/client/application/application-main-window.vala 
b/src/client/application/application-main-window.vala
index 255c105d..b262a3b0 100644
--- a/src/client/application/application-main-window.vala
+++ b/src/client/application/application-main-window.vala
@@ -208,9 +208,10 @@ public class Application.MainWindow :
     public ConversationListView conversation_list_view  { get; private set; }
     public ConversationViewer conversation_viewer { get; private set; }
     public StatusBar status_bar { get; private set; default = new StatusBar(); }
+
     private MonitoredSpinner spinner = new MonitoredSpinner();
 
-    private AccountContext? context = null;
+    private Gee.Set<AccountContext> accounts = new Gee.HashSet<AccountContext>();
 
     private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
 
@@ -273,7 +274,7 @@ public class Application.MainWindow :
     public signal void retry_service_problem(Geary.ClientService.Status problem);
 
 
-    public MainWindow(Client application) {
+    internal MainWindow(Client application, Controller controller) {
         Object(
             application: application,
             show_menubar: false
@@ -299,22 +300,43 @@ public class Application.MainWindow :
 
         this.attachments = new AttachmentManager(this);
 
-        this.application.engine.account_available.connect(on_account_available);
-        this.application.engine.account_unavailable.connect(on_account_unavailable);
-
         this.update_ui_timeout = new Geary.TimeoutManager.seconds(
             UPDATE_UI_INTERVAL, on_update_ui_timeout
         );
         this.update_ui_timeout.repetition = FOREVER;
 
+        // Add future and existing accounts to the main window
+        controller.account_available.connect(
+            on_account_available
+        );
+        controller.account_unavailable.connect(
+            on_account_unavailable
+        );
+        foreach (AccountContext context in controller.get_account_contexts()) {
+            add_account(context);
+        }
+
         this.main_layout.show_all();
     }
 
     ~MainWindow() {
-        this.update_ui_timeout.reset();
         base_unref();
     }
 
+    /** {@inheritDoc} */
+    public override void destroy() {
+        if (this.application != null) {
+            this.application.controller.account_available.disconnect(
+                on_account_available
+            );
+            this.application.controller.account_unavailable.disconnect(
+                on_account_unavailable
+            );
+        }
+        this.update_ui_timeout.reset();
+        base.destroy();
+    }
+
     /** Updates the window's account status info bars. */
     public void update_account_status(Geary.Account.Status status,
                                       bool has_auth_error,
@@ -666,7 +688,7 @@ public class Application.MainWindow :
     }
 
     /** Adds a folder to the window. */
-    public void add_folder(Geary.Folder to_add) {
+    internal void add_folder(Geary.Folder to_add) {
         this.folder_list.add_folder(to_add);
         if (to_add.account == this.selected_account) {
             this.main_toolbar.copy_folder_menu.add_folder(to_add);
@@ -675,7 +697,7 @@ public class Application.MainWindow :
     }
 
     /** Removes a folder from the window. */
-    public void remove_folder(Geary.Folder to_remove) {
+    internal void remove_folder(Geary.Folder to_remove) {
         if (to_remove.account == this.selected_account) {
             this.main_toolbar.copy_folder_menu.remove_folder(to_remove);
             this.main_toolbar.move_folder_menu.remove_folder(to_remove);
@@ -683,35 +705,82 @@ public class Application.MainWindow :
         this.folder_list.remove_folder(to_remove);
     }
 
+    private void add_account(AccountContext to_add) {
+        if (!this.accounts.contains(to_add)) {
+            this.folder_list.set_user_folders_root_name(
+                to_add.account, _("Labels")
+            );
+
+            this.progress_monitor.add(to_add.account.opening_monitor);
+            Geary.Smtp.ClientService? smtp = (
+                to_add.account.outgoing as Geary.Smtp.ClientService
+            );
+            if (smtp != null) {
+                this.progress_monitor.add(smtp.sending_monitor);
+            }
+
+            to_add.commands.executed.connect(on_command_execute);
+            to_add.commands.undone.connect(on_command_undo);
+            to_add.commands.redone.connect(on_command_redo);
+
+            this.accounts.add(to_add);
+        }
+    }
+
     /**
      * Removes the given account from the main window.
      *
      * If `to_select` is not null, the given folder will be selected,
      * otherwise no folder will be.
      */
-    public async void remove_account(Geary.Account to_remove,
-                                     Geary.Folder? to_select) {
-        // Explicitly unset the selected folder if it belongs to the
-        // account so we block until it's gone. This also clears the
-        // previous search folder, so it won't try to re-load that
-        // that when the account is gone.
-        if (this.selected_folder != null &&
-            this.selected_folder.account == to_remove) {
-            Geary.SearchFolder? current_search = (
-                this.selected_folder as Geary.SearchFolder
-            );
+    private async void remove_account(AccountContext to_remove,
+                                      Geary.Folder? to_select) {
+        if (this.accounts.contains(to_remove)) {
+            // Explicitly unset the selected folder if it belongs to the
+            // account so we block until it's gone. This also clears the
+            // previous search folder, so it won't try to re-load that
+            // that when the account is gone.
+            if (this.selected_folder != null &&
+                this.selected_folder.account == to_remove.account) {
+                Geary.SearchFolder? current_search = (
+                    this.selected_folder as Geary.SearchFolder
+                );
+
+                yield select_folder(to_select, false);
+
+                // Clear the account's search folder if it existed
+                if (current_search != null) {
+                    this.search_bar.set_search_text("");
+                    this.search_bar.search_mode_enabled = false;
+                }
+            }
 
-            yield select_folder(to_select, false);
+            to_remove.commands.executed.disconnect(on_command_execute);
+            to_remove.commands.undone.disconnect(on_command_undo);
+            to_remove.commands.redone.disconnect(on_command_redo);
 
-            // Clear the account's search folder if it existed
-            if (current_search != null) {
-                this.search_bar.set_search_text("");
-                this.search_bar.search_mode_enabled = false;
+            this.progress_monitor.remove(to_remove.account.opening_monitor);
+            Geary.Smtp.ClientService? smtp = (
+                to_remove.account.outgoing as Geary.Smtp.ClientService
+            );
+            if (smtp != null) {
+                this.progress_monitor.remove(smtp.sending_monitor);
             }
+
+            // Finally, remove the account and its folders
+            this.folder_list.remove_account(to_remove.account);
+            this.accounts.remove(to_remove);
         }
+    }
 
-        // Finally, remove the account and its folders
-        this.folder_list.remove_account(to_remove);
+    private AccountContext? get_selected_account_context() {
+        AccountContext? context = null;
+        if (this.selected_account != null) {
+            context = this.application.controller.get_context_for_account(
+                this.selected_account.information
+            );
+        }
+        return context;
     }
 
     private void load_config(Configuration config) {
@@ -975,7 +1044,7 @@ public class Application.MainWindow :
 
     /** Un-does the last executed application command, if any. */
     private async void undo() {
-        AccountContext? selected = this.context;
+        AccountContext? selected = get_selected_account_context();
         if (selected != null) {
             selected.commands.undo.begin(
                 selected.cancellable,
@@ -992,7 +1061,7 @@ public class Application.MainWindow :
 
     /** Re-does the last undone application command, if any. */
     private async void redo() {
-        AccountContext? selected = this.context;
+        AccountContext? selected = get_selected_account_context();
         if (selected != null) {
             selected.commands.redo.begin(
                 selected.cancellable,
@@ -1008,7 +1077,7 @@ public class Application.MainWindow :
     }
 
     private void update_command_actions() {
-        AccountContext? selected = this.context;
+        AccountContext? selected = get_selected_account_context();
         get_edit_action(Action.Edit.UNDO).set_enabled(
             selected != null && selected.commands.can_undo
         );
@@ -1130,29 +1199,12 @@ public class Application.MainWindow :
             if (this.selected_account != null) {
                 this.main_toolbar.copy_folder_menu.clear();
                 this.main_toolbar.move_folder_menu.clear();
-
-                AccountContext? context = this.context;
-                if (context != null) {
-                    context.commands.executed.disconnect(on_command_execute);
-                    context.commands.undone.disconnect(on_command_undo);
-                    context.commands.redone.disconnect(on_command_redo);
-                }
-                this.context = null;
             }
 
             this.selected_account = account;
             this.search_bar.set_account(account);
 
             if (account != null) {
-                this.context = this.application.controller.get_context_for_account(
-                    account.information
-                );
-                if (this.context != null) {
-                    this.context.commands.executed.connect(on_command_execute);
-                    this.context.commands.undone.connect(on_command_undo);
-                    this.context.commands.redone.connect(on_command_redo);
-                }
-
                 foreach (Geary.Folder folder in account.list_folders()) {
                     this.main_toolbar.copy_folder_menu.add_folder(folder);
                     this.main_toolbar.move_folder_menu.add_folder(folder);
@@ -1194,7 +1246,7 @@ public class Application.MainWindow :
                 // last email was removed but the conversation monitor
                 // hasn't signalled its removal yet. In this case,
                 // just don't load it since it will soon disappear.
-                AccountContext? context = this.context;
+                AccountContext? context = get_selected_account_context();
                 if (context != null && convo.get_count() > 0) {
                     try {
                         yield this.conversation_viewer.load_conversation(
@@ -1324,40 +1376,6 @@ public class Application.MainWindow :
         }
     }
 
-    private void on_account_available(Geary.AccountInformation account) {
-        try {
-            Geary.Account? engine = this.application.engine.get_account_instance(account);
-            if (engine != null) {
-                this.progress_monitor.add(engine.opening_monitor);
-                Geary.Smtp.ClientService? smtp = (
-                    engine.outgoing as Geary.Smtp.ClientService
-                );
-                if (smtp != null) {
-                    this.progress_monitor.add(smtp.sending_monitor);
-                }
-            }
-        } catch (GLib.Error e) {
-            debug("Could not access account progress monitors: %s", e.message);
-        }
-    }
-
-    private void on_account_unavailable(Geary.AccountInformation account) {
-        try {
-            Geary.Account? engine = this.application.engine.get_account_instance(account);
-            if (engine != null) {
-                this.progress_monitor.remove(engine.opening_monitor);
-                Geary.Smtp.ClientService? smtp = (
-                    engine.outgoing as Geary.Smtp.ClientService
-                );
-                if (smtp != null) {
-                    this.progress_monitor.remove(smtp.sending_monitor);
-                }
-            }
-        } catch (GLib.Error e) {
-            debug("Could not access account progress monitors: %s", e.message);
-        }
-    }
-
     private void on_change_orientation() {
         bool horizontal = this.application.config.folder_list_pane_horizontal;
         bool initial = true;
@@ -1646,6 +1664,32 @@ public class Application.MainWindow :
         update_ui();
     }
 
+    private void on_account_available(AccountContext account) {
+        add_account(account);
+    }
+
+    private void on_account_unavailable(AccountContext account,
+                                        bool is_shutdown) {
+        // If we're not shutting down, select the inbox of the first
+        // account so that we show something other than empty
+        // conversation list/viewer.
+        Geary.Folder? to_select = null;
+        if (!is_shutdown) {
+            Geary.AccountInformation? first_account =
+                this.application.controller.get_first_account();
+            if (first_account != null) {
+                AccountContext? first_context =
+                    this.application.controller.get_context_for_account(
+                        first_account
+                    );
+                if (first_context != null) {
+                    to_select = first_context.inbox;
+                }
+            }
+        }
+
+        this.remove_account.begin(account, to_select);
+    }
     private void on_command_execute(Command command) {
         if (!(command is TrivialCommand)) {
             // Only show an execute notification for non-trivial
diff --git a/src/client/dialogs/upgrade-dialog.vala b/src/client/dialogs/upgrade-dialog.vala
index 55cf81ca..7a238b17 100644
--- a/src/client/dialogs/upgrade-dialog.vala
+++ b/src/client/dialogs/upgrade-dialog.vala
@@ -56,13 +56,25 @@ public class UpgradeDialog : Object {
     }
 
     /**
-     * Add accounts before opening them.
+     * Adds an account to be monitored for upgrades by the dialog.
+     *
+     * Accounts should be added before being opened.
      */
-    public void add_account(Geary.Account account, Cancellable? cancellable = null) {
+    public void add_account(Geary.Account account,
+                            GLib.Cancellable? cancellable = null) {
         monitor.add(account.db_upgrade_monitor);
         monitor.add(account.db_vacuum_monitor);
-        if (cancellable != null)
+        if (cancellable != null) {
             cancellables.add(cancellable);
+        }
     }
-}
 
+    /**
+     * Stops an account from being monitored.
+     */
+    public void remove_account(Geary.Account account) {
+        monitor.remove(account.db_upgrade_monitor);
+        monitor.remove(account.db_vacuum_monitor);
+    }
+
+}


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