diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32b82b51..caca32b2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,7 +14,6 @@ engine/abstract/geary-abstract-folder.vala engine/api/geary-account.vala engine/api/geary-account-information.vala -engine/api/geary-account-settings.vala engine/api/geary-attachment.vala engine/api/geary-composed-email.vala engine/api/geary-contact.vala diff --git a/src/client/geary-application.vala b/src/client/geary-application.vala index 64a976ef..f0a7da5d 100644 --- a/src/client/geary-application.vala +++ b/src/client/geary-application.vala @@ -79,7 +79,6 @@ along with Geary; if not, write to the Free Software Foundation, Inc., private static GearyApplication _instance = null; private GearyController? controller = null; - private Geary.Account? account = null; private LoginDialog? login_dialog = null; @@ -108,8 +107,6 @@ along with Geary; if not, write to the Free Software Foundation, Inc., if (controller.main_window != null) controller.main_window.destroy(); - controller.disconnect_account_async.begin(null); - Date.terminate(); return true; @@ -130,17 +127,22 @@ along with Geary; if not, write to the Free Software Foundation, Inc., return; } + Geary.Engine.instance.account_available.connect(on_account_available); + Geary.Engine.instance.account_unavailable.connect(on_account_unavailable); + + config = new Configuration(); + controller = new GearyController(); + // Start Geary. try { yield Geary.Engine.instance.open_async(get_user_data_directory(), get_resource_directory(), new GnomeKeyringMediator()); + if (Geary.Engine.instance.get_accounts().size == 0) + create_account(); } catch (Error e) { error("Error opening Geary.Engine instance: %s", e.message); } - config = new Configuration(); - controller = new GearyController(); - initialize_account(); handle_args(args); } @@ -152,44 +154,34 @@ along with Geary; if not, write to the Free Software Foundation, Inc., return action; } - private void set_account(Geary.Account? account) { - if (this.account == account) - return; - - if (this.account != null) - this.account.report_problem.disconnect(on_report_problem); - - this.account = account; - - if (this.account != null) - this.account.report_problem.connect(on_report_problem); - - controller.connect_account_async.begin(this.account, null); + private void open_account(Geary.Account account) { + account.report_problem.connect(on_report_problem); + controller.connect_account_async.begin(account); } - private void initialize_account() { - Geary.AccountInformation? account_information = get_account(); - if (account_information == null) { - create_account(null); - } else { - open_account(account_information.email, - Geary.CredentialsMediator.ServiceFlag.IMAP, null); + private void close_account(Geary.Account account) { + account.report_problem.disconnect(on_report_problem); + controller.disconnect_account_async.begin(account); + } + + private Geary.Account get_account_instance(Geary.AccountInformation account_information) { + try { + return Geary.Engine.instance.get_account_instance(account_information); + } catch (Error e) { + error("Error creating account instance: %s", e.message); } } - private void create_account(string? email) { - Geary.AccountInformation? old_account_information = null; - if (email != null) { - try { - old_account_information = Geary.Engine.instance.get_accounts().get(email); - } catch (Error err) { - debug("Unable to open account information for %s, creating instead: %s", email, - err.message); - } - } - - Geary.AccountInformation? account_information = - request_account_information(old_account_information); + private void on_account_available(Geary.AccountInformation account_information) { + open_account(get_account_instance(account_information)); + } + + private void on_account_unavailable(Geary.AccountInformation account_information) { + close_account(get_account_instance(account_information)); + } + + private void create_account() { + Geary.AccountInformation? account_information = request_account_information(null); if (account_information != null) do_validate_until_successful_async.begin(account_information); } @@ -220,10 +212,8 @@ along with Geary; if not, write to the Free Software Foundation, Inc., // exit could be canceled is if there are unsaved composer windows open (which won't // happen before an account is created). However, best to include this check for the // future. - if (new_account_information == null) { - set_account(null); + if (new_account_information == null) return null; - } debug("User entered revised account information, retrying validation"); return new_account_information; @@ -243,62 +233,8 @@ along with Geary; if not, write to the Free Software Foundation, Inc., account_information.store_async.begin(cancellable); - try { - set_account(Geary.Engine.instance.get_account_instance(account_information)); - debug("Successfully validated account information"); - return true; - } catch (Error err) { - debug("Unable to retrieve email account: %s", err.message); - return false; - } - } - - private void open_account(string email, Geary.CredentialsMediator.ServiceFlag password_flags, - Cancellable? cancellable) { - Geary.AccountInformation account_information; - try { - account_information = Geary.Engine.instance.get_accounts().get(email); - if (account_information == null) - account_information = Geary.Engine.instance.create_orphan_account(email); - } catch (Error err) { - error("Unable to open account information for label %s: %s", email, err.message); - } - - account_information.fetch_passwords_async.begin(password_flags, - on_open_account_fetch_passwords_finished); - } - - private void on_open_account_fetch_passwords_finished(Object? object, AsyncResult result) { - Geary.AccountInformation? account_information = object as Geary.AccountInformation; - assert(account_information != null); - - try { - if (!account_information.fetch_passwords_async.end(result)) - exit(1); - } catch (Error e) { - error("Error fetching stored passwords: %s", e.message); - } - - try { - set_account(Geary.Engine.instance.get_account_instance(account_information)); - } catch (Error err) { - // Our service provider is wrong. But we can't change it, because we don't want to - // change the service provider for an existing account. - debug("Unable to retrieve email account: %s", err.message); - set_account(null); - } - } - - private Geary.AccountInformation? get_account() { - try { - Geary.AccountInformation[] accounts = Geary.Engine.instance.get_accounts().values.to_array(); - if (accounts.length > 0) - return accounts[0]; - } catch (Error e) { - debug("Unable to fetch account labels: %s", e.message); - } - - return null; + debug("Successfully validated account information"); + return true; } // Prompt the user for a service, real name, username, and password, and try to start Geary. @@ -350,17 +286,9 @@ along with Geary; if not, write to the Free Software Foundation, Inc., } } - private void on_report_problem(Geary.Account.Problem problem, Geary.AccountSettings settings, - Error? err) { + private void on_report_problem(Geary.Account account, Geary.Account.Problem problem, Error? err) { debug("Reported problem: %s Error: %s", problem.to_string(), err != null ? err.message : "(N/A)"); - Geary.AccountInformation account_information; - try { - account_information = Geary.Engine.instance.get_accounts().get(settings.email.address); - } catch (Error e) { - error("Couldn't find previously-opened account %s", settings.email.address); - } - switch (problem) { case Geary.Account.Problem.DATABASE_FAILURE: case Geary.Account.Problem.HOST_UNREACHABLE: @@ -368,18 +296,11 @@ along with Geary; if not, write to the Free Software Foundation, Inc., // TODO break; - // TODO: Different password dialog prompt for SMTP or IMAP login. case Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED: - account.report_problem.disconnect(on_report_problem); - do_password_failed_async.begin(Geary.CredentialsMediator.ServiceFlag.IMAP, - account_information); - break; - - // TODO: Different password dialog prompt for SMTP or IMAP login. case Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED: - account.report_problem.disconnect(on_report_problem); - do_password_failed_async.begin(Geary.CredentialsMediator.ServiceFlag.SMTP, - account_information); + // At this point, we've prompted them for the password and + // they've hit cancel, so there's not much for us to do here. + close_account(account); break; default: @@ -387,17 +308,6 @@ along with Geary; if not, write to the Free Software Foundation, Inc., } } - private async void do_password_failed_async(Geary.CredentialsMediator.ServiceFlag services, - Geary.AccountInformation account_information) { - try { - yield account_information.clear_stored_passwords_async(services); - } catch (Error e) { - debug("Error clearing stored passwords: %s", e.message); - } - - open_account(account_information.email, services, null); - } - public File get_user_data_directory() { return File.new_for_path(Environment.get_user_data_dir()).get_child("geary"); } diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala index fc63f3e1..eb46f3a6 100644 --- a/src/client/geary-controller.vala +++ b/src/client/geary-controller.vala @@ -40,14 +40,16 @@ public class GearyController { public MainWindow main_window { get; private set; } - private Geary.Account? account = null; + private Geary.Account? current_account = null; + private Gee.HashMap inboxes + = new Gee.HashMap(); + private Geary.Folder? current_folder = null; + private Geary.ConversationMonitor? current_conversations = null; + private Gee.HashMap inbox_conversations + = new Gee.HashMap(); private Cancellable cancellable_folder = new Cancellable(); private Cancellable cancellable_inbox = new Cancellable(); private Cancellable cancellable_message = new Cancellable(); - private Geary.Folder? current_folder = null; - private Geary.Folder? inbox_folder = null; - private Geary.ConversationMonitor? current_conversations = null; - private Geary.ConversationMonitor? inbox_conversations = null; private int busy_count = 0; private Gee.Set selected_conversations = new Gee.HashSet(); private Geary.Conversation? last_deleted_conversation = null; @@ -107,7 +109,7 @@ public class GearyController { } ~GearyController() { - assert(account == null); + assert(current_account == null); } private void add_accelerator(string accelerator, string action) { @@ -242,78 +244,59 @@ public class GearyController { GearyApplication.instance.get_action(ACTION_DELETE_MESSAGE).is_important = true; } - public async void connect_account_async(Geary.Account? new_account, Cancellable? cancellable) { - if (account == new_account) - return; - - // Disconnect the old account, if any. - if (account != null) { - cancel_folder(); - cancel_inbox(); - cancel_message(); - - account.folders_available_unavailable.disconnect(on_folders_available_unavailable); - - main_window.title = GearyApplication.NAME; - main_window.conversation_list_store.account_owner_email = null; - main_window.folder_list.remove_all_branches(); - - if (inbox_conversations != null) { - try { - yield inbox_conversations.stop_monitoring_async(true, cancellable); - } catch (Error close_conversations_err) { - debug("Unable to stop monitoring inbox: %s", close_conversations_err.message); - } - - inbox_conversations = null; - } - - if (inbox_folder != null) { - try { - yield inbox_folder.close_async(cancellable); - } catch (Error close_inbox_err) { - debug("Unable to close monitored inbox: %s", close_inbox_err.message); - } - - inbox_folder = null; - } - - try { - yield account.close_async(cancellable); - } catch (Error close_err) { - debug("Unable to close account %s: %s", account.to_string(), close_err.message); - } - } - - account = new_account; - - // Connect the new account, if any. - if (account != null) { - account.folders_available_unavailable.connect(on_folders_available_unavailable); + public async void connect_account_async(Geary.Account account, Cancellable? cancellable = null) { + account.folders_available_unavailable.connect(on_folders_available_unavailable); - try { - yield account.open_async(cancellable); - } catch (Error open_err) { - // TODO: Better error reporting to user - debug("Unable to open account %s: %s", account.to_string(), open_err.message); - - account = null; - - GearyApplication.instance.panic(); - } + try { + yield account.open_async(cancellable); + } catch (Error open_err) { + // TODO: Better error reporting to user + debug("Unable to open account %s: %s", account.to_string(), open_err.message); - account.email_sent.connect(on_sent); - - if (account.settings.service_provider == Geary.ServiceProvider.YAHOO) - main_window.title = GearyApplication.NAME + "!"; - main_window.conversation_list_store.account_owner_email = account.settings.email.address; - - main_window.folder_list.set_user_folders_root_name(_("Labels")); + GearyApplication.instance.panic(); } + + account.email_sent.connect(on_sent); + + main_window.folder_list.set_user_folders_root_name(account, _("Labels")); } - public async void disconnect_account_async(Cancellable? cancellable) throws Error { - yield connect_account_async(null, cancellable); + public async void disconnect_account_async(Geary.Account account, Cancellable? cancellable = null) { + cancel_folder(); + cancel_inbox(); + cancel_message(); + + account.folders_available_unavailable.disconnect(on_folders_available_unavailable); + + if (main_window.conversation_list_store.account_owner_email == account.information.email) + main_window.conversation_list_store.account_owner_email = null; + main_window.folder_list.remove_account(account); + + if (inbox_conversations.has_key(account)) { + try { + yield inbox_conversations.get(account).stop_monitoring_async(true, cancellable); + } catch (Error close_conversations_err) { + debug("Unable to stop monitoring inbox: %s", close_conversations_err.message); + } + + inbox_conversations.unset(account); + } + + if (inboxes.has_key(account)) { + try { + yield inboxes.get(account).close_async(cancellable); + } catch (Error close_inbox_err) { + debug("Unable to close monitored inbox: %s", close_inbox_err.message); + } + + inboxes.unset(account); + } + + try { + yield account.close_async(cancellable); + } catch (Error close_err) { + debug("Unable to close account %s: %s", account.to_string(), close_err.message); + } } private bool is_viewed_conversation(Geary.Conversation? conversation) { @@ -352,15 +335,16 @@ public class GearyController { private async void do_select_folder(Geary.Folder folder) throws Error { cancel_folder(); - Cancellable? conversation_cancellable = (current_folder != inbox_folder) - ? cancellable_folder : cancellable_inbox; + bool current_is_inbox = inboxes.values.contains(current_folder); - // stop monitoring for conversations and close the folder (but only if not the inbox_folder, + Cancellable? conversation_cancellable = (current_is_inbox ? cancellable_inbox : cancellable_folder); + + // stop monitoring for conversations and close the folder (but only if not an inbox, // which we leave open for notifications) if (current_conversations != null) { - yield current_conversations.stop_monitoring_async((current_folder != inbox_folder), null); + yield current_conversations.stop_monitoring_async(!current_is_inbox, null); current_conversations = null; - } else if (current_folder != null && current_folder != inbox_folder) { + } else if (current_folder != null && !current_is_inbox) { yield current_folder.close_async(); } @@ -368,7 +352,17 @@ public class GearyController { debug("switching to %s", folder.to_string()); current_folder = folder; + current_account = folder.account; + main_window.conversation_list_store.set_current_folder(current_folder, conversation_cancellable); + main_window.conversation_list_store.account_owner_email = current_account.information.email; + + main_window.main_toolbar.copy_folder_menu.clear(); + main_window.main_toolbar.move_folder_menu.clear(); + foreach(Geary.Folder f in current_folder.account.list_folders()) { + main_window.main_toolbar.copy_folder_menu.add_folder(f); + main_window.main_toolbar.move_folder_menu.add_folder(f); + } // The current folder may be null if the user rapidly switches between folders. If they have // done that then this folder selection is invalid anyways, so just return. @@ -379,16 +373,16 @@ public class GearyController { update_ui(); - if (current_folder != inbox_folder) { + if (!inboxes.values.contains(current_folder)) { current_conversations = new Geary.ConversationMonitor(current_folder, false, ConversationListStore.REQUIRED_FIELDS); } else { - if (inbox_conversations == null) { - inbox_conversations = new Geary.ConversationMonitor(inbox_folder, false, - ConversationListStore.REQUIRED_FIELDS); + if (!inbox_conversations.has_key(folder.account)) { + inbox_conversations.set(folder.account, new Geary.ConversationMonitor( + current_folder, false, ConversationListStore.REQUIRED_FIELDS)); } - current_conversations = inbox_conversations; + current_conversations = inbox_conversations.get(folder.account); // Inbox selected, clear new messages if visible clear_new_messages("do_select_folder (inbox)", null); @@ -420,11 +414,11 @@ public class GearyController { set_busy(false); } - private void on_notification_bubble_invoked(Geary.Email? email) { - if (email == null || inbox_folder == null) + private void on_notification_bubble_invoked(Geary.Folder folder, Geary.Email? email) { + if (email == null) return; - main_window.folder_list.select_path(inbox_folder.get_path()); + main_window.folder_list.select_folder(folder); Geary.Conversation? conversation = current_conversations.get_conversation_for_email(email.id); if (conversation != null) main_window.conversation_list_view.select_conversation(conversation); @@ -444,10 +438,6 @@ public class GearyController { // user activated notification, reset new messages no matter other conditions new_messages_monitor.clear_new_messages(); - - // attempt to select Inbox - if (inbox_folder != null) - main_window.folder_list.select_path(inbox_folder.get_path()); } private void on_conversation_appended(Geary.Conversation conversation, @@ -527,7 +517,7 @@ public class GearyController { // Clear view before we yield, to make sure it happens if (clear_view) { - main_window.conversation_viewer.clear(current_folder, account.settings); + main_window.conversation_viewer.clear(current_folder, current_account.information); main_window.conversation_viewer.scroll_reset(); main_window.conversation_viewer.external_images_info_bar.hide(); } @@ -586,18 +576,24 @@ public class GearyController { if (available != null && available.size > 0) { foreach (Geary.Folder folder in available) { main_window.folder_list.add_folder(folder); - main_window.main_toolbar.copy_folder_menu.add_folder(folder); - main_window.main_toolbar.move_folder_menu.add_folder(folder); + if (folder.account == current_account) { + if (!main_window.main_toolbar.copy_folder_menu.has_folder(folder)) + main_window.main_toolbar.copy_folder_menu.add_folder(folder); + if (!main_window.main_toolbar.move_folder_menu.has_folder(folder)) + main_window.main_toolbar.move_folder_menu.add_folder(folder); + } // monitor the Inbox for notifications - if (folder.get_special_folder_type() == Geary.SpecialFolderType.INBOX && inbox_folder == null) { - inbox_folder = folder; + if (folder.get_special_folder_type() == Geary.SpecialFolderType.INBOX && + !inboxes.has_key(folder.account)) { + inboxes.set(folder.account, folder); // select the inbox and get the show started - main_window.folder_list.select_path(folder.get_path()); - inbox_folder.open_async.begin(false, cancellable_inbox); + if (inboxes.size == 1) + main_window.folder_list.select_folder(folder); + folder.open_async.begin(false, cancellable_inbox); - new_messages_monitor = new NewMessagesMonitor(inbox_folder, should_notify_new_messages, + new_messages_monitor = new NewMessagesMonitor(folder, should_notify_new_messages, cancellable_inbox); // Unity launcher count (Ubuntuism) @@ -1037,7 +1033,8 @@ public class GearyController { } private void create_compose_window(Geary.ComposedEmail? prefill = null) { - Geary.ContactStore? contact_store = account == null ? null : account.get_contact_store(); + Geary.ContactStore? contact_store = (current_account == null ? null + : current_account.get_contact_store()); ComposerWindow window = new ComposerWindow(contact_store, prefill); window.set_position(Gtk.WindowPosition.CENTER); window.send.connect(on_send); @@ -1163,7 +1160,8 @@ public class GearyController { } private Geary.RFC822.MailboxAddress get_sender() { - return account.settings.email; + return new Geary.RFC822.MailboxAddress(current_account.information.real_name, + current_account.information.email); } private Geary.RFC822.MailboxAddresses get_from() { @@ -1171,7 +1169,7 @@ public class GearyController { } private void on_send(ComposerWindow composer_window) { - account.send_email_async.begin(composer_window.get_composed_email(get_from())); + current_account.send_email_async.begin(composer_window.get_composed_email(get_from())); composer_window.destroy(); } diff --git a/src/client/models/folder-list.vala b/src/client/models/folder-list.vala index 674c72ee..6416b6f6 100644 --- a/src/client/models/folder-list.vala +++ b/src/client/models/folder-list.vala @@ -10,11 +10,84 @@ public class FolderList : Sidebar.Tree { { "application/x-geary-mail", Gtk.TargetFlags.SAME_APP, 0 } }; - private class SpecialFolderBranch : Sidebar.RootOnlyBranch { - public SpecialFolderBranch(Geary.Folder folder) { - base(new FolderEntry(folder)); + private class AccountBranch : Sidebar.Branch { + public Geary.Account account { get; private set; } + public Sidebar.Grouping user_folder_group { get; private set; } + public Gee.HashMap folder_entries { get; private set; } + + public AccountBranch(Geary.Account account) { + base(new Sidebar.Grouping(account.information.email, new ThemedIcon("emblem-mail")), + Sidebar.Branch.Options.NONE, special_folder_comparator); - assert(folder.get_special_folder_type() != Geary.SpecialFolderType.NONE); + this.account = account; + user_folder_group = new Sidebar.Grouping("", + IconFactory.instance.get_custom_icon("tags", IconFactory.ICON_SIDEBAR)); + folder_entries = new Gee.HashMap(); + + graft(get_root(), user_folder_group, normal_folder_comparator); + } + + private static int special_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) { + // Our user folder grouping always comes dead last. + if (a is Sidebar.Grouping) + return 1; + if (b is Sidebar.Grouping) + return -1; + + assert(a is FolderEntry); + assert(b is FolderEntry); + + FolderEntry entry_a = (FolderEntry) a; + FolderEntry entry_b = (FolderEntry) b; + Geary.SpecialFolderType type_a = entry_a.folder.get_special_folder_type(); + Geary.SpecialFolderType type_b = entry_b.folder.get_special_folder_type(); + + assert(type_a != Geary.SpecialFolderType.NONE); + assert(type_b != Geary.SpecialFolderType.NONE); + + // Special folders are ordered by their enum value. + return (int) type_a - (int) type_b; + } + + private static int normal_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) { + // Non-special folders are compared based on name. + return a.get_sidebar_name().collate(b.get_sidebar_name()); + } + + public Sidebar.Entry? get_entry_for_path(Geary.FolderPath folder_path) { + return folder_entries.get(folder_path); + } + + public void add_folder(Geary.Folder folder) { + FolderEntry folder_entry = new FolderEntry(folder); + Geary.SpecialFolderType special_folder_type = folder.get_special_folder_type(); + if (special_folder_type != Geary.SpecialFolderType.NONE) { + graft(get_root(), folder_entry); + } else if (folder.get_path().get_parent() == null) { + // Top-level folders get put in our special user folders group. + graft(user_folder_group, folder_entry); + } else { + Sidebar.Entry? entry = folder_entries.get(folder.get_path().get_parent()); + if (entry == null) { + debug("Could not add folder %s of type %s to folder list", folder.to_string(), + special_folder_type.to_string()); + return; + } + graft(entry, folder_entry); + } + + folder_entries.set(folder.get_path(), folder_entry); + } + + public void remove_folder(Geary.Folder folder) { + Sidebar.Entry? entry = folder_entries.get(folder.get_path()); + if(entry == null) { + debug("Could not remove folder %s", folder.to_string()); + return; + } + + prune(entry); + folder_entries.unset(folder.get_path()); } } @@ -95,30 +168,19 @@ public class FolderList : Sidebar.Tree { public signal void copy_conversation(Geary.Folder folder); public signal void move_conversation(Geary.Folder folder); - private Sidebar.Grouping user_folder_group; - private Sidebar.Branch user_folder_branch; - internal Gee.HashMap entries = new Gee.HashMap< - Geary.FolderPath, Sidebar.Entry>(Geary.Hashable.hash_func, Geary.Equalable.equal_func); - internal Gee.HashMap branches = new Gee.HashMap< - Geary.FolderPath, Sidebar.Branch>(Geary.Hashable.hash_func, Geary.Equalable.equal_func); + private Gee.HashMap account_branches + = new Gee.HashMap(); + private int total_accounts = 0; public FolderList() { base(new Gtk.TargetEntry[0], Gdk.DragAction.ASK, drop_handler); entry_selected.connect(on_entry_selected); - reset_user_folder_group(); - // Set self as a drag destination. Gtk.drag_dest_set(this, Gtk.DestDefaults.MOTION | Gtk.DestDefaults.HIGHLIGHT, TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE); } - private static int user_folder_comparator(Sidebar.Entry a, Sidebar.Entry b) { - int result = a.get_sidebar_name().collate(b.get_sidebar_name()); - - return (result != 0) ? result : strcmp(a.get_sidebar_name(), b.get_sidebar_name()); - } - private void drop_handler(Gdk.DragContext context, Sidebar.Entry? entry, Gtk.SelectionData data, uint info, uint time) { } @@ -129,88 +191,49 @@ public class FolderList : Sidebar.Tree { } } - public void set_user_folders_root_name(string name) { - user_folder_group.rename(name); - } - - private void reset_user_folder_group() { - user_folder_group = new Sidebar.Grouping("", - IconFactory.instance.get_custom_icon("tags", IconFactory.ICON_SIDEBAR)); - user_folder_branch = new Sidebar.Branch(user_folder_group, - Sidebar.Branch.Options.STARTUP_OPEN_GROUPING, user_folder_comparator); + public void set_user_folders_root_name(Geary.Account account, string name) { + if (account_branches.has_key(account)) + account_branches.get(account).user_folder_group.rename(name); } public void add_folder(Geary.Folder folder) { - bool added = false; + if (!account_branches.has_key(folder.account)) + account_branches.set(folder.account, new AccountBranch(folder.account)); - if (!has_branch(user_folder_branch)) - graft(user_folder_branch, int.MAX); + AccountBranch account_branch = account_branches.get(folder.account); + if (!has_branch(account_branch)) + graft(account_branch, total_accounts++); - Geary.SpecialFolderType special_folder_type = folder.get_special_folder_type(); - if (special_folder_type != Geary.SpecialFolderType.NONE) { - SpecialFolderBranch branch = new SpecialFolderBranch(folder); - graft(branch, (int) special_folder_type); - entries.set(folder.get_path(), branch.get_root()); - branches.set(folder.get_path(), branch); - added = true; - } else if (folder.get_path().get_parent() == null) { - // Top-level folder. - FolderEntry folder_entry = new FolderEntry(folder); - user_folder_branch.graft(user_folder_group, folder_entry); - entries.set(folder.get_path(), folder_entry); - branches.set(folder.get_path(), user_folder_branch); - added = true; - } else { - FolderEntry folder_entry = new FolderEntry(folder); - Sidebar.Entry? entry = get_entry_for_folder_path(folder.get_path().get_parent()); - if (entry != null) { - user_folder_branch.graft(entry, folder_entry); - entries.set(folder.get_path(), folder_entry); - branches.set(folder.get_path(), user_folder_branch); - added = true; - } - } - - if (!added) { - debug("Could not add folder %s of type %s to folder list", folder.to_string(), - special_folder_type.to_string()); - } + account_branch.add_folder(folder); } public void remove_folder(Geary.Folder folder) { - Sidebar.Entry? entry = get_entry_for_folder_path(folder.get_path()); - Sidebar.Branch? branch = get_branch_for_folder_path(folder.get_path()); - if(entry != null && branch != null) { - if (branch is SpecialFolderBranch) { - this.prune(branch); - } else { - branch.prune(entry); - } - } else { - debug(@"Could not remove folder $(folder.get_path())"); + AccountBranch? account_branch = account_branches.get(folder.account); + assert(account_branch != null); + assert(has_branch(account_branch)); + + account_branch.remove_folder(folder); + } + + public void remove_account(Geary.Account account) { + AccountBranch? account_branch = account_branches.get(account); + if (account_branch != null) { + if (has_branch(account_branch)) + prune(account_branch); + account_branches.unset(account); } } - public void remove_all_branches() { - prune_all(); - entries.clear(); - reset_user_folder_group(); - } - - public void select_path(Geary.FolderPath path) { - Sidebar.Entry? entry = get_entry_for_folder_path(path); + public void select_folder(Geary.Folder folder) { + AccountBranch? account_branch = account_branches.get(folder.account); + if (account_branch == null) + return; + + Sidebar.Entry? entry = account_branch.get_entry_for_path(folder.get_path()); if (entry != null) place_cursor(entry, false); } - private Sidebar.Entry? get_entry_for_folder_path(Geary.FolderPath path) { - return entries.get(path); - } - - private Sidebar.Branch? get_branch_for_folder_path(Geary.FolderPath path) { - return branches.get(path); - } - public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) { // Run the base version first. bool ret = base.drag_motion(context, x, y, time); diff --git a/src/client/notification/notification-bubble.vala b/src/client/notification/notification-bubble.vala index d4a95889..6d51f827 100644 --- a/src/client/notification/notification-bubble.vala +++ b/src/client/notification/notification-bubble.vala @@ -16,7 +16,7 @@ public class NotificationBubble : GLib.Object { private Geary.Email? email = null; private unowned List caps; - public signal void invoked(Geary.Email? email); + public signal void invoked(Geary.Folder folder, Geary.Email? email); public NotificationBubble(NewMessagesMonitor monitor) { this.monitor = monitor; @@ -64,7 +64,7 @@ public class NotificationBubble : GLib.Object { } private void on_default_action(Notify.Notification notification, string action) { - invoked(email); + invoked(monitor.folder, email); GearyApplication.instance.activate(new string[0]); } diff --git a/src/client/ui/folder-menu.vala b/src/client/ui/folder-menu.vala index 986e8e3d..f4e99265 100644 --- a/src/client/ui/folder-menu.vala +++ b/src/client/ui/folder-menu.vala @@ -18,6 +18,10 @@ public class FolderMenu : GtkUtil.ToggleToolbarDropdown { // TODO Merge the move/copy menus and just have a move/copy buttons at bottom of this menu. } + public bool has_folder(Geary.Folder folder) { + return folder_list.contains(folder); + } + public void add_folder(Geary.Folder folder) { folder_list.add(folder); folder_list.sort((CompareFunc) folder_sort); @@ -43,6 +47,14 @@ public class FolderMenu : GtkUtil.ToggleToolbarDropdown { proxy_menu.show_all(); } + public void clear() { + folder_list.clear(); + menu.foreach((w) => menu.remove(w)); + proxy_menu.foreach((w) => proxy_menu.remove(w)); + menu.show_all(); + proxy_menu.show_all(); + } + private Gtk.MenuItem build_menu_item(Geary.Folder folder) { Gtk.MenuItem menu_item = new Gtk.MenuItem.with_label(folder.get_path().to_string()); menu_item.activate.connect(() => { diff --git a/src/client/views/conversation-viewer.vala b/src/client/views/conversation-viewer.vala index 9df20037..d6161978 100644 --- a/src/client/views/conversation-viewer.vala +++ b/src/client/views/conversation-viewer.vala @@ -63,7 +63,7 @@ public class ConversationViewer : Gtk.Box { private Gtk.Menu? message_menu = null; private Gtk.Menu? attachment_menu = null; private weak Geary.Folder? current_folder = null; - private Geary.AccountSettings? current_settings = null; + private Geary.AccountInformation? current_account_information = null; public ConversationViewer() { Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0); @@ -122,7 +122,7 @@ public class ConversationViewer : Gtk.Box { } // Removes all displayed e-mails from the view. - public void clear(Geary.Folder? new_folder, Geary.AccountSettings? settings) { + public void clear(Geary.Folder? new_folder, Geary.AccountInformation? account_information) { // Remove all messages from DOM. try { foreach (WebKit.DOM.HTMLElement element in email_to_element.values) { @@ -136,7 +136,7 @@ public class ConversationViewer : Gtk.Box { messages.clear(); current_folder = new_folder; - current_settings = settings; + current_account_information = account_information; } // Converts an email ID into HTML ID used by the
for the email. @@ -146,7 +146,7 @@ public class ConversationViewer : Gtk.Box { public void show_multiple_selected(uint selected_count) { // Remove any messages and hide the message container, then show the counter. - clear(current_folder, current_settings); + clear(current_folder, current_account_information); try { web_view.hide_element_by_id(MESSAGE_CONTAINER_ID); web_view.show_element_by_id(SELECTION_COUNTER_ID); @@ -234,8 +234,8 @@ public class ConversationViewer : Gtk.Box { // Only include to string if it's not just this account. // TODO: multiple accounts. - if (email.to != null && current_settings != null) { - if (!(email.to.get_all().size == 1 && email.to.get_all().get(0).address == current_settings.email.address)) + if (email.to != null && current_account_information != null) { + if (!(email.to.get_all().size == 1 && email.to.get_all().get(0).address == current_account_information.email)) insert_header_address(ref header, _("To:"), email.to); } diff --git a/src/dbusservice/controller.vala b/src/dbusservice/controller.vala index 5dc7829e..d1eaf480 100644 --- a/src/dbusservice/controller.vala +++ b/src/dbusservice/controller.vala @@ -50,7 +50,7 @@ public class Geary.DBus.Controller { // Open the Inbox folder. Geary.Folder? folder = null; - Gee.Collection folders = yield account.list_folders_async(null, null); + Gee.Collection folders = account.list_matching_folders(null); foreach(Geary.Folder folder_to_check in folders) { if(folder_to_check.get_special_folder_type() == Geary.SpecialFolderType.INBOX) { folder = folder_to_check; @@ -85,8 +85,7 @@ public class Geary.DBus.Controller { return File.new_for_path(Environment.get_current_dir()); } - private void on_report_problem(Geary.Account.Problem problem, Geary.AccountSettings settings, - Error? err) { + private void on_report_problem(Geary.Account.Problem problem, Error? err) { debug("Reported problem: %s Error: %s", problem.to_string(), err != null ? err.message : "(N/A)"); } diff --git a/src/engine/abstract/geary-abstract-account.vala b/src/engine/abstract/geary-abstract-account.vala index e1967e74..1ac8b962 100644 --- a/src/engine/abstract/geary-abstract-account.vala +++ b/src/engine/abstract/geary-abstract-account.vala @@ -5,13 +5,13 @@ */ public abstract class Geary.AbstractAccount : Object, Geary.Account { - public Geary.AccountSettings settings { get; protected set; } + public Geary.AccountInformation information { get; protected set; } private string name; - public AbstractAccount(string name, AccountSettings settings) { + public AbstractAccount(string name, AccountInformation information) { this.name = name; - this.settings = settings; + this.information = information; } protected virtual void notify_folders_available_unavailable(Gee.Collection? available, @@ -36,17 +36,18 @@ public abstract class Geary.AbstractAccount : Object, Geary.Account { email_sent(message); } - protected virtual void notify_report_problem(Geary.Account.Problem problem, - Geary.AccountSettings? settings, Error? err) { - report_problem(problem, settings, err); + protected virtual void notify_report_problem(Geary.Account.Problem problem, Error? err) { + report_problem(problem, err); } public abstract async void open_async(Cancellable? cancellable = null) throws Error; public abstract async void close_async(Cancellable? cancellable = null) throws Error; - public abstract async Gee.Collection list_folders_async(Geary.FolderPath? parent, - Cancellable? cancellable = null) throws Error; + public abstract Gee.Collection list_matching_folders( + Geary.FolderPath? parent) throws Error; + + public abstract Gee.Collection list_folders() throws Error; public abstract Geary.ContactStore get_contact_store(); diff --git a/src/engine/abstract/geary-abstract-folder.vala b/src/engine/abstract/geary-abstract-folder.vala index fc8220ec..5e3e3940 100644 --- a/src/engine/abstract/geary-abstract-folder.vala +++ b/src/engine/abstract/geary-abstract-folder.vala @@ -47,6 +47,8 @@ public abstract class Geary.AbstractFolder : Object, Geary.Folder { Geary.SpecialFolderType new_type) { special_folder_type_changed(old_type, new_type); } + + public abstract Geary.Account account { get; } public abstract Geary.FolderPath get_path(); diff --git a/src/engine/api/geary-account-information.vala b/src/engine/api/geary-account-information.vala index 775fc968..65c6e463 100644 --- a/src/engine/api/geary-account-information.vala +++ b/src/engine/api/geary-account-information.vala @@ -96,14 +96,25 @@ public class Geary.AccountInformation : Object { } /** - * Juggles get_passwords_async() and prompt_passwords_async() to fetch the - * passwords for the given services. Return true if all passwords were in - * the key store or the user proceeded normally, false if the user tried to - * cancel. + * Fetch the passwords for the given services. For each service, if the + * password is unset, use get_passwords_async() first; if the password is + * set or it's not in the key store, use prompt_passwords_async(). Return + * true if all passwords were retrieved from the key store or the user + * proceeded normally if/when prompted, false if the user tried to cancel + * the prompt. */ public async bool fetch_passwords_async(CredentialsMediator.ServiceFlag services) throws Error { - CredentialsMediator.ServiceFlag unset_services = - yield get_passwords_async(services); + // Only call get_passwords on anything that hasn't been set + // (incorrectly) previously. + CredentialsMediator.ServiceFlag get_services = 0; + if (services.has_imap() && !imap_credentials.is_complete()) + get_services |= CredentialsMediator.ServiceFlag.IMAP; + if (services.has_smtp() && !smtp_credentials.is_complete()) + get_services |= CredentialsMediator.ServiceFlag.SMTP; + + CredentialsMediator.ServiceFlag unset_services = services; + if (get_services != 0) + unset_services = yield get_passwords_async(get_services); if (unset_services == 0) return true; @@ -117,6 +128,17 @@ public class Geary.AccountInformation : Object { "Geary.Engine instance needs to be open with a valid Geary.CredentialsMediator"); } + private void set_imap_password(string imap_password) { + // Don't just update the pass field, because we need imap_credentials + // itself to change so callers can bind to its changed signal. + imap_credentials = new Credentials(imap_credentials.user, imap_password); + } + + private void set_smtp_password(string smtp_password) { + // See above. Same argument. + smtp_credentials = new Credentials(smtp_credentials.user, smtp_password); + } + /** * Use Engine's authentication mediator to retrieve the passwords for the * given services. The passwords will be stored in the appropriate @@ -137,7 +159,7 @@ public class Geary.AccountInformation : Object { CredentialsMediator.Service.IMAP, imap_credentials.user); if (imap_password != null) - imap_credentials.pass = imap_password; + set_imap_password(imap_password); else failed_services |= CredentialsMediator.ServiceFlag.IMAP; } @@ -147,7 +169,7 @@ public class Geary.AccountInformation : Object { CredentialsMediator.Service.SMTP, smtp_credentials.user); if (smtp_password != null) - smtp_credentials.pass = smtp_password; + set_smtp_password(smtp_password); else failed_services |= CredentialsMediator.ServiceFlag.SMTP; } @@ -176,12 +198,12 @@ public class Geary.AccountInformation : Object { return false; if (services.has_imap()) { - imap_credentials.pass = imap_password; + set_imap_password(imap_password); this.imap_remember_password = imap_remember_password; } if (services.has_smtp()) { - smtp_credentials.pass = smtp_password; + set_smtp_password(smtp_password); this.smtp_remember_password = smtp_remember_password; } @@ -221,27 +243,6 @@ public class Geary.AccountInformation : Object { } } - /** - * Use the Engine's authentication mediator to clear the passwords for the - * given services in the key store. - */ - public async void clear_stored_passwords_async( - CredentialsMediator.ServiceFlag services) throws Error { - check_mediator_instance(); - - CredentialsMediator mediator = Geary.Engine.instance.authentication_mediator; - - if (services.has_imap()) { - yield mediator.clear_password_async( - CredentialsMediator.Service.IMAP, imap_credentials.user); - } - - if (services.has_smtp()) { - yield mediator.clear_password_async( - CredentialsMediator.Service.SMTP, smtp_credentials.user); - } - } - public Endpoint get_imap_endpoint() { switch (service_provider) { case ServiceProvider.GMAIL: diff --git a/src/engine/api/geary-account-settings.vala b/src/engine/api/geary-account-settings.vala deleted file mode 100644 index 6080d2be..00000000 --- a/src/engine/api/geary-account-settings.vala +++ /dev/null @@ -1,40 +0,0 @@ -/* Copyright 2012 Yorba Foundation - * - * This software is licensed under the GNU Lesser General Public License - * (version 2.1 or later). See the COPYING file in this distribution. - */ - -/** - * AccountSettings is a complement to AccountInformation. AccountInformation stores these settings - * as well as defaults and provides validation and persistence functionality. Settings is simply - * the values loaded from a backing store, perhaps chosen from defaults, and validated, then filtered - * down to a set of working settings for the Account to use. Changes made to AccountInformation - * are not reflected here; a new AccountSettings object must be created. - */ - -public class Geary.AccountSettings { - public RFC822.MailboxAddress email { get; private set; } - public string real_name { get; private set; } - public Geary.Credentials imap_credentials { get; private set; } - public Geary.Credentials smtp_credentials { get; private set; } - public Geary.ServiceProvider service_provider { get; private set; } - public bool imap_server_pipeline { get; private set; } - public Endpoint imap_endpoint { get; private set; } - public Endpoint smtp_endpoint { get; private set; } - - internal File settings_dir { get; private set; } - - internal AccountSettings(AccountInformation info) throws EngineError { - email = new RFC822.MailboxAddress(info.real_name, info.email); - real_name = info.real_name; - imap_credentials = info.imap_credentials; - smtp_credentials = info.smtp_credentials; - service_provider = info.service_provider; - imap_server_pipeline = info.imap_server_pipeline; - imap_endpoint = info.get_imap_endpoint(); - smtp_endpoint = info.get_smtp_endpoint(); - - settings_dir = info.settings_dir; - } -} - diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala index 67cad88f..e2ffa2ec 100644 --- a/src/engine/api/geary-account.vala +++ b/src/engine/api/geary-account.vala @@ -13,7 +13,7 @@ public interface Geary.Account : Object { DATABASE_FAILURE } - public abstract Geary.AccountSettings settings { get; protected set; } + public abstract Geary.AccountInformation information { get; protected set; } public signal void opened(); @@ -21,8 +21,7 @@ public interface Geary.Account : Object { public signal void email_sent(Geary.RFC822.Message rfc822); - public signal void report_problem(Geary.Account.Problem problem, Geary.AccountSettings settings, - Error? err); + public signal void report_problem(Geary.Account.Problem problem, Error? err); /** * Fired when folders become available or unavailable in the account. @@ -57,8 +56,7 @@ public interface Geary.Account : Object { /** * Signal notification method for subclasses to use. */ - protected abstract void notify_report_problem(Geary.Account.Problem problem, - Geary.AccountSettings? settings, Error? err); + protected abstract void notify_report_problem(Geary.Account.Problem problem, Error? err); /** * Signal notification method for subclasses to use. @@ -83,18 +81,26 @@ public interface Geary.Account : Object { public abstract async void close_async(Cancellable? cancellable = null) throws Error; /** - * Lists all the folders found under the parent path unless it's null, in which case it lists - * all the root folders. If the parent path cannot be found, EngineError.NOT_FOUND is thrown. - * If no folders exist in the root, EngineError.NOT_FOUND may be thrown as well. However, - * the caller should be prepared to deal with an empty list being returned instead. + * Lists all the currently-available folders found under the parent path + * unless it's null, in which case it lists all the root folders. If the + * parent path cannot be found, EngineError.NOT_FOUND is thrown. If no + * folders exist in the root, EngineError.NOT_FOUND may be thrown as well. + * However, the caller should be prepared to deal with an empty list being + * returned instead. * * The same Geary.Folder objects (instances) will be returned if the same path is submitted * multiple times. This means that multiple callers may be holding references to the same * Folders. This is important when thinking of opening and closing folders and signal * notifications. */ - public abstract async Gee.Collection list_folders_async(Geary.FolderPath? parent, - Cancellable? cancellable = null) throws Error; + public abstract Gee.Collection list_matching_folders( + Geary.FolderPath? parent) throws Error; + + /** + * Lists all currently-available folders. See caveats under + * list_matching_folders(). + */ + public abstract Gee.Collection list_folders() throws Error; /** * Gets a perpetually update-to-date collection of autocompletion contacts. diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala index fe94e252..1476bcc1 100644 --- a/src/engine/api/geary-engine.vala +++ b/src/engine/api/geary-engine.vala @@ -241,27 +241,26 @@ public class Geary.Engine { if (account_instances.has_key(account_information.email)) return account_instances.get(account_information.email); - AccountSettings settings = new AccountSettings(account_information); - ImapDB.Account local_account = new ImapDB.Account(settings); - Imap.Account remote_account = new Imap.Account(settings); + ImapDB.Account local_account = new ImapDB.Account(account_information); + Imap.Account remote_account = new Imap.Account(account_information); Geary.Account account; switch (account_information.service_provider) { case ServiceProvider.GMAIL: account = new ImapEngine.GmailAccount("Gmail account %s".printf(account_information.email), - settings, remote_account, local_account); + account_information, remote_account, local_account); break; case ServiceProvider.YAHOO: account = new ImapEngine.YahooAccount("Yahoo account %s".printf(account_information.email), - settings, remote_account, local_account); + account_information, remote_account, local_account); break; case ServiceProvider.OTHER: account = new ImapEngine.OtherAccount("Other account %s".printf(account_information.email), - settings, remote_account, local_account); + account_information, remote_account, local_account); break; - + default: assert_not_reached(); } diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala index a52d3008..7d20b518 100644 --- a/src/engine/api/geary-folder.vala +++ b/src/engine/api/geary-folder.vala @@ -71,6 +71,8 @@ public interface Geary.Folder : Object { } } + public abstract Geary.Account account { get; } + /** * This is fired when the Folder is successfully opened by a caller. It will only fire once * until the Folder is closed, with the OpenState indicating what has been opened and the count diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala index 2e98b827..bef1b9be 100644 --- a/src/engine/imap-db/imap-db-account.vala +++ b/src/engine/imap-db/imap-db-account.vala @@ -19,17 +19,17 @@ private class Geary.ImapDB.Account : Object { public SmtpOutboxFolder? outbox { get; private set; default = null; } private string name; - private AccountSettings settings; + private AccountInformation account_information; private ImapDB.Database? db = null; private Gee.HashMap folder_refs = new Gee.HashMap(Hashable.hash_func, Equalable.equal_func); public ContactStore contact_store { get; private set; } - public Account(Geary.AccountSettings settings) { - this.settings = settings; + public Account(Geary.AccountInformation account_information) { + this.account_information = account_information; contact_store = new ContactStore(); - name = "IMAP database account for %s".printf(settings.imap_credentials.user); + name = "IMAP database account for %s".printf(account_information.imap_credentials.user); } private void check_open() throws Error { @@ -42,7 +42,7 @@ private class Geary.ImapDB.Account : Object { if (db != null) throw new EngineError.ALREADY_OPEN("IMAP database already open"); - db = new ImapDB.Database(user_data_dir, schema_dir, settings.email.address); + db = new ImapDB.Database(user_data_dir, schema_dir, account_information.email); try { db.open(Db.DatabaseFlags.CREATE_DIRECTORY | Db.DatabaseFlags.CREATE_FILE, null, @@ -56,10 +56,21 @@ private class Geary.ImapDB.Account : Object { throw err; } + Geary.Account account; + try { + account = Geary.Engine.instance.get_account_instance(account_information); + } catch (Error e) { + // If they're opening an account, the engine should already be + // open, and there should be no reason for this to fail. Thus, if + // we get here, it's a programmer error. + + error("Error finding account from its information: %s", e.message); + } + initialize_contacts(cancellable); // ImapDB.Account holds the Outbox, which is tied to the database it maintains - outbox = new SmtpOutboxFolder(db, settings); + outbox = new SmtpOutboxFolder(db, account); // Need to clear duplicate folders due to old bug that caused multiple folders to be // created in the database ... benign due to other logic, but want to prevent this from @@ -370,7 +381,7 @@ private class Geary.ImapDB.Account : Object { } // create folder - folder = new Geary.ImapDB.Folder(db, path, contact_store, settings.email.address, folder_id, + folder = new Geary.ImapDB.Folder(db, path, contact_store, account_information.email, folder_id, properties); // build a reference to it diff --git a/src/engine/imap-db/outbox/smtp-outbox-folder.vala b/src/engine/imap-db/outbox/smtp-outbox-folder.vala index 348f9b63..a8f497a9 100644 --- a/src/engine/imap-db/outbox/smtp-outbox-folder.vala +++ b/src/engine/imap-db/outbox/smtp-outbox-folder.vala @@ -32,9 +32,8 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport } } - public signal void report_problem(Geary.Account.Problem problem, Geary.AccountSettings settings, - Error? err); - + public signal void report_problem(Geary.Account.Problem problem, Error? err); + // Min and max times between attempting to re-send after a connection failure. private const uint MIN_SEND_RETRY_INTERVAL_SEC = 4; private const uint MAX_SEND_RETRY_INTERVAL_SEC = 64; @@ -42,18 +41,20 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport private static FolderRoot? path = null; private ImapDB.Database db; - private AccountSettings settings; + private weak Account _account; private Geary.Smtp.ClientSession smtp; private bool opened = false; private NonblockingMailbox outbox_queue = new NonblockingMailbox(); + public override Account account { get { return _account; } } + // Requires the Database from the get-go because it runs a background task that access it // whether open or not - public SmtpOutboxFolder(ImapDB.Database db, AccountSettings settings) { + public SmtpOutboxFolder(ImapDB.Database db, Account account) { this.db = db; - this.settings = settings; + _account = account; - smtp = new Geary.Smtp.ClientSession(settings.smtp_endpoint); + smtp = new Geary.Smtp.ClientSession(_account.information.get_smtp_endpoint()); do_postman_async.begin(); } @@ -127,15 +128,26 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport } catch (Error send_err) { debug("Outbox postman send error, retrying: %s", send_err.message); - if (send_err is SmtpError.AUTHENTICATION_FAILED) - report_problem(Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED, settings, send_err); - try { outbox_queue.send(row); } catch (Error send_err) { debug("Outbox postman: Unable to re-enqueue message, dropping on floor: %s", send_err.message); } - + + if (send_err is SmtpError.AUTHENTICATION_FAILED) { + bool report = true; + try { + if (yield _account.information.fetch_passwords_async( + CredentialsMediator.ServiceFlag.SMTP)) + report = false; + } catch (Error e) { + debug("Error prompting for IMAP password: %s", e.message); + } + + if (report) + report_problem(Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED, send_err); + } + // Take a brief nap before continuing to allow connection problems to resolve. yield Geary.Scheduler.sleep_async(send_retry_seconds); send_retry_seconds *= 2; @@ -499,7 +511,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport Error? smtp_err = null; try { - yield smtp.login_async(settings.smtp_credentials, cancellable); + yield smtp.login_async(_account.information.smtp_credentials, cancellable); } catch (Error login_err) { debug("SMTP login error: %s", login_err.message); smtp_err = login_err; diff --git a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala index e384fed5..0ae3d92d 100644 --- a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala +++ b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala @@ -36,9 +36,9 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount { private static Gee.HashMap? path_type_map = null; - public GmailAccount(string name, Geary.AccountSettings settings, Imap.Account remote, - ImapDB.Account local) { - base (name, settings, remote, local); + public GmailAccount(string name, Geary.AccountInformation account_information, + Imap.Account remote, ImapDB.Account local) { + base (name, account_information, remote, local); if (path_type_map == null) { path_type_map = new Gee.HashMap( diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala b/src/engine/imap-engine/imap-engine-generic-account.vala index 7fa081d6..02fc8bb6 100644 --- a/src/engine/imap-engine/imap-engine-generic-account.vala +++ b/src/engine/imap-engine/imap-engine-generic-account.vala @@ -18,9 +18,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { private Gee.HashMap local_only = new Gee.HashMap( Hashable.hash_func, Equalable.equal_func); - public GenericAccount(string name, Geary.AccountSettings settings, Imap.Account remote, + public GenericAccount(string name, Geary.AccountInformation information, Imap.Account remote, ImapDB.Account local) { - base (name, settings); + base (name, information); this.remote = remote; this.local = local; @@ -71,7 +71,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { if (open) throw new EngineError.ALREADY_OPEN("Account %s already opened", to_string()); - yield local.open_async(settings.settings_dir, Engine.instance.resource_dir.get_child("sql"), cancellable); + yield local.open_async(information.settings_dir, Engine.instance.resource_dir.get_child("sql"), cancellable); // outbox is now available local.outbox.report_problem.connect(notify_report_problem); @@ -170,8 +170,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { return return_folders; } - public override async Gee.Collection list_folders_async(Geary.FolderPath? parent, - Cancellable? cancellable = null) throws Error { + public override Gee.Collection list_matching_folders( + Geary.FolderPath? parent) throws Error { + check_open(); + Gee.ArrayList matches = new Gee.ArrayList(); foreach(FolderPath path in existing_folders.keys) { @@ -184,6 +186,11 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { return matches; } + public override Gee.Collection list_folders() throws Error { + check_open(); + return existing_folders.values; + } + private async Gee.Collection enumerate_folders_async(Geary.FolderPath? parent, Cancellable? cancellable = null) throws Error { check_open(); @@ -384,7 +391,18 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { } private void on_login_failed(Geary.Credentials? credentials) { - notify_report_problem(Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED, settings, null); + do_login_failed_async.begin(credentials); + } + + private async void do_login_failed_async(Geary.Credentials? credentials) { + try { + if (yield information.fetch_passwords_async(CredentialsMediator.ServiceFlag.IMAP)) + return; + } catch (Error e) { + debug("Error prompting for IMAP password: %s", e.message); + } + + notify_report_problem(Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED, null); } } diff --git a/src/engine/imap-engine/imap-engine-generic-folder.vala b/src/engine/imap-engine/imap-engine-generic-folder.vala index 670cea4d..b180fa0c 100644 --- a/src/engine/imap-engine/imap-engine-generic-folder.vala +++ b/src/engine/imap-engine/imap-engine-generic-folder.vala @@ -11,10 +11,11 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde private const Geary.Email.Field NORMALIZATION_FIELDS = Geary.Email.Field.PROPERTIES | Geary.Email.Field.FLAGS | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION; + private weak GenericAccount _account; + public override Account account { get { return _account; } } internal ImapDB.Folder local_folder { get; protected set; } internal Imap.Folder? remote_folder { get; protected set; default = null; } - private weak GenericAccount account; private Imap.Account remote; private ImapDB.Account local; private EmailFlagWatcher email_flag_watcher; @@ -28,7 +29,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde public GenericFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local, ImapDB.Folder local_folder, SpecialFolderType special_folder_type) { - this.account = account; + _account = account; this.remote = remote; this.local = local; this.local_folder = local_folder; @@ -74,7 +75,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde properties = remote_folder.get_properties(); if (properties == null) - properties = account.get_properties_for_folder(local_folder.get_path()); + properties = _account.get_properties_for_folder(local_folder.get_path()); if (properties == null) properties = local_folder.get_properties(); diff --git a/src/engine/imap-engine/other/imap-engine-other-account.vala b/src/engine/imap-engine/other/imap-engine-other-account.vala index c51fa7eb..c0b3b50f 100644 --- a/src/engine/imap-engine/other/imap-engine-other-account.vala +++ b/src/engine/imap-engine/other/imap-engine-other-account.vala @@ -5,9 +5,9 @@ */ private class Geary.ImapEngine.OtherAccount : Geary.ImapEngine.GenericAccount { - public OtherAccount(string name, AccountSettings settings, Imap.Account remote, - ImapDB.Account local) { - base (name, settings, remote, local); + public OtherAccount(string name, AccountInformation account_information, + Imap.Account remote, ImapDB.Account local) { + base (name, account_information, remote, local); } protected override GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account, diff --git a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala index e8524fa3..528fde43 100644 --- a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala +++ b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala @@ -33,9 +33,9 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount { private static Gee.HashMap? special_map = null; - public YahooAccount(string name, AccountSettings settings, Imap.Account remote, - ImapDB.Account local) { - base (name, settings, remote, local); + public YahooAccount(string name, AccountInformation account_information, + Imap.Account remote, ImapDB.Account local) { + base (name, account_information, remote, local); if (special_map == null) { special_map = new Gee.HashMap( diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala index cdf6be12..56b23504 100644 --- a/src/engine/imap/api/imap-account.vala +++ b/src/engine/imap/api/imap-account.vala @@ -30,17 +30,17 @@ private class Geary.Imap.Account : Object { } private string name; - private AccountSettings settings; + private AccountInformation account_information; private ClientSessionManager session_mgr; private Gee.HashMap delims = new Gee.HashMap(); public signal void login_failed(Geary.Credentials cred); - public Account(Geary.AccountSettings settings) { - name = "IMAP Account for %s".printf(settings.imap_credentials.to_string()); - this.settings = settings; + public Account(Geary.AccountInformation account_information) { + name = "IMAP Account for %s".printf(account_information.imap_credentials.to_string()); + this.account_information = account_information; - session_mgr = new ClientSessionManager(settings); + session_mgr = new ClientSessionManager(account_information); session_mgr.login_failed.connect(on_login_failed); } @@ -185,7 +185,7 @@ private class Geary.Imap.Account : Object { } private void on_login_failed() { - login_failed(settings.imap_credentials); + login_failed(account_information.imap_credentials); } public string to_string() { diff --git a/src/engine/imap/transport/imap-client-session-manager.vala b/src/engine/imap/transport/imap-client-session-manager.vala index 9f24656e..88eb9725 100644 --- a/src/engine/imap/transport/imap-client-session-manager.vala +++ b/src/engine/imap/transport/imap-client-session-manager.vala @@ -7,7 +7,7 @@ public class Geary.Imap.ClientSessionManager { public const int DEFAULT_MIN_POOL_SIZE = 2; - private AccountSettings settings; + private AccountInformation account_information; private int min_pool_size; private Gee.HashSet sessions = new Gee.HashSet(); private Geary.NonblockingMutex sessions_mutex = new Geary.NonblockingMutex(); @@ -20,10 +20,18 @@ public class Geary.Imap.ClientSessionManager { public signal void login_failed(); - public ClientSessionManager(AccountSettings settings, int min_pool_size = DEFAULT_MIN_POOL_SIZE) { - this.settings = settings; + public ClientSessionManager(AccountInformation account_information, + int min_pool_size = DEFAULT_MIN_POOL_SIZE) { + this.account_information = account_information; this.min_pool_size = min_pool_size; + account_information.notify["imap-credentials"].connect(on_imap_credentials_notified); + + adjust_session_pool.begin(); + } + + private void on_imap_credentials_notified() { + authentication_failed = false; adjust_session_pool.begin(); } @@ -43,7 +51,8 @@ public class Geary.Imap.ClientSessionManager { try { yield create_new_authorized_session(null); } catch (Error err) { - debug("Unable to create authorized session to %s: %s", settings.imap_endpoint.to_string(), err.message); + debug("Unable to create authorized session to %s: %s", + account_information.get_imap_endpoint().to_string(), err.message); break; } @@ -222,7 +231,8 @@ public class Geary.Imap.ClientSessionManager { if (authentication_failed) throw new ImapError.UNAUTHENTICATED("Invalid ClientSessionManager credentials"); - ClientSession new_session = new ClientSession(settings.imap_endpoint, settings.imap_server_pipeline); + ClientSession new_session = new ClientSession(account_information.get_imap_endpoint(), + account_information.imap_server_pipeline); // add session to pool before launching all the connect activity so error cases can properly // back it out @@ -240,7 +250,7 @@ public class Geary.Imap.ClientSessionManager { } try { - yield new_session.initiate_session_async(settings.imap_credentials, cancellable); + yield new_session.initiate_session_async(account_information.imap_credentials, cancellable); } catch (Error err) { debug("[%s] Initiate session failure: %s", new_session.to_string(), err.message); @@ -365,7 +375,7 @@ public class Geary.Imap.ClientSessionManager { * Use only for debugging and logging. */ public string to_string() { - return settings.imap_endpoint.to_string(); + return account_information.get_imap_endpoint().to_string(); } }