Load and display multiple accounts; fix #6230

Lots has been changed here.  See the bug report
<http://redmine.yorba.org/issues/6230> for some of the discussion about
it.

This also fixes #6274.  I'll cut the list of revisions off after a
while, because this branch has been outstanding for a couple weeks.

Squashed commit of the following:

commit ae505d89e87e63e0d8949bfd901913706a9d3b73
Merge: 81ef002 e18bef9
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 15:16:17 2013 -0800

    Merge branch 'master' into multiple-accounts

commit 81ef002f5ff486b9c28f5663a0ba1e7392b8489c
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 15:15:51 2013 -0800

    Fix final round of code review

commit 6935b1e7892b9b356bf5006b89e0b2a4e6a8ad16
Merge: c9ed434 a9dc52b
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 14:45:46 2013 -0800

    Merge branch 'master' into multiple-accounts

commit c9ed434fe936e6aed735baef222ae615364c2513
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 14:31:02 2013 -0800

    Simplify folder comparator

commit 28ac020cfd9135c6eb4ed05574c82b92f99c4a40
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 14:06:55 2013 -0800

    Bump up declaration to conform to guidelines

commit 0a8167bdaebd5fac1c3ca791de5f2cc233c13cb9
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 13:54:03 2013 -0800

    Rename back to list_*folders

commit 31457f60298052bdddba8e426db27f93d7c72529
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 31 12:36:15 2013 -0800

    Fix spacing, brevity issues

commit ecd30c203d80c21c1ca1234b8911b57efcb68294
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 17:56:29 2013 -0800

    Fix nits before review

commit 85b51d71e83115991cd9a54d491b4d45b71f2f9b
Merge: b29abce d538bf0
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 17:46:19 2013 -0800

    Merge branch 'master' into multiple-accounts

commit b29abceeaea84f226ab9bcd22266a511691d8005
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 17:22:28 2013 -0800

    Fix notifications

commit c26d975fb0859d807ddb7f7c10632605c3b6fb1c
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 13:01:03 2013 -0800

    Only show current acct. folders in copy/move menus

commit 9a5b57db1229a079f11f518c53f5762a3670b83f
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 11:48:05 2013 -0800

    Fix issue where wrong mail would show in folders

commit 3b05d18843584c2aff7472708eea30ce24068043
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 11:18:38 2013 -0800

    Fix ordering of folders in side bar

commit b228967b6a74c16698d593e53e65d66c69ffb974
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 30 11:07:32 2013 -0800

    Add icon to accounts in sidebar

commit dd05d2c987a46f0b6699d743c339297e06829e4f
Author: Charles Lindsay <chaz@yorba.org>
Date:   Tue Jan 29 19:04:52 2013 -0800

    Fix Labels icon

commit b5254fe8f6ef37df48d41167d0f52f3cd88d1966
Author: Charles Lindsay <chaz@yorba.org>
Date:   Tue Jan 29 18:43:45 2013 -0800

    Initial stab at new FolderList; fix compile errors

commit ff591810ee4312acce208dfa36d993069bc4c7d2
Merge: 2b9dbb9 ff5f9fa
Author: Charles Lindsay <chaz@yorba.org>
Date:   Mon Jan 28 17:42:34 2013 -0800

    Merge branch 'master' into multiple-accounts

commit 2b9dbb9b6963b1d52b2b90300bcea277b01d2094
Merge: 7583241 fcfb460
Author: Charles Lindsay <chaz@yorba.org>
Date:   Mon Jan 28 17:21:49 2013 -0800

    Merge branch 'master' into multiple-accounts

    Conflicts:
    	src/client/geary-controller.vala

commit 75832412cc806c956848e32ef20052af36d4f64d
Author: Charles Lindsay <chaz@yorba.org>
Date:   Mon Jan 28 16:37:15 2013 -0800

    Fix IMAP sess. mgr. to recover from bad passwords

commit 8868b4be5c3f5c97246d35c6170531c6f543abe1
Author: Charles Lindsay <chaz@yorba.org>
Date:   Mon Jan 28 12:06:21 2013 -0800

    Typo

commit 3f909054502d31ca48e11f7948fd22118afe7514
Author: Charles Lindsay <chaz@yorba.org>
Date:   Mon Jan 28 10:54:51 2013 -0800

    Clean up interface a little

commit 3bfb526fe8801f8234127944be8594a960ccf7e7
Merge: 5e84e93 e971275
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 16:23:52 2013 -0800

    Merge branch 'master' into multiple-accounts

    Conflicts:
    	src/client/geary-application.vala
    	src/engine/api/geary-engine.vala

commit 5e84e9375a655567a3bc4eb7ebaacab2d218be40
Merge: 35cc46b 9167aeb
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 16:17:00 2013 -0800

    Merge branch 'pluggable-auth' into multiple-accounts

    Conflicts:
    	src/engine/api/geary-engine.vala

commit 9167aeb56be6789d49a3e7cdba2a21d2b015e40d
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 16:11:20 2013 -0800

    Fix for code guidelines

commit 35cc46bc99f44f1597c609bfeaa72dd503333a17
Merge: 9675f47 7612a7d
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 15:44:18 2013 -0800

    Merge branch 'pluggable-auth' into multiple-accounts

    Conflicts:
    	src/client/geary-application.vala

commit 7612a7ddc3df14ef207b9e74ee32fa23710e1ce9
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 15:26:48 2013 -0800

    Fix code review issues

commit 46635544c98df7a8b6c76f028715814907274389
Merge: 30b611e 6de36ae
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 12:54:03 2013 -0800

    Merge branch 'master' into pluggable-auth

commit 9675f473e77d0d581cf73a33012981e6a4f44943
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 12:53:18 2013 -0800

    Try to make Folder know about its Account

commit 5d9af43e53199a616490cf6ff98bd3c613b4e5f2
Merge: 335480e 6de36ae
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 12:12:21 2013 -0800

    Merge branch 'master' into multiple-accounts

commit 335480e2dd0261c8fb82f1c6296f5b8c76f3ac02
Author: Charles Lindsay <chaz@yorba.org>
Date:   Fri Jan 25 12:11:18 2013 -0800

    Work on multiple accounts; fix compilation errors

commit 808e65d55798e3f08b4c70992718cc11befbb45c
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 24 19:28:12 2013 -0800

    Fix application logic for multiple accounts

You get the idea.
This commit is contained in:
Charles Lindsay 2013-01-31 15:17:44 -08:00
parent e18bef9638
commit 3308189ece
24 changed files with 437 additions and 473 deletions

View file

@ -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

View file

@ -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");
}

View file

@ -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<Geary.Account, Geary.Folder> inboxes
= new Gee.HashMap<Geary.Account, Geary.Folder>();
private Geary.Folder? current_folder = null;
private Geary.ConversationMonitor? current_conversations = null;
private Gee.HashMap<Geary.Account, Geary.ConversationMonitor> inbox_conversations
= new Gee.HashMap<Geary.Account, Geary.ConversationMonitor>();
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<Geary.Conversation> selected_conversations = new Gee.HashSet<Geary.Conversation>();
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();
}

View file

@ -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<Geary.FolderPath, FolderEntry> 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<Geary.FolderPath, FolderEntry>();
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<Geary.FolderPath, Sidebar.Entry> entries = new Gee.HashMap<
Geary.FolderPath, Sidebar.Entry>(Geary.Hashable.hash_func, Geary.Equalable.equal_func);
internal Gee.HashMap<Geary.FolderPath, Sidebar.Branch> branches = new Gee.HashMap<
Geary.FolderPath, Sidebar.Branch>(Geary.Hashable.hash_func, Geary.Equalable.equal_func);
private Gee.HashMap<Geary.Account, AccountBranch> account_branches
= new Gee.HashMap<Geary.Account, AccountBranch>();
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);

View file

@ -16,7 +16,7 @@ public class NotificationBubble : GLib.Object {
private Geary.Email? email = null;
private unowned List<string> 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]);
}

View file

@ -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(() => {

View file

@ -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 <div> 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);
}

View file

@ -50,7 +50,7 @@ public class Geary.DBus.Controller {
// Open the Inbox folder.
Geary.Folder? folder = null;
Gee.Collection<Geary.Folder> folders = yield account.list_folders_async(null, null);
Gee.Collection<Geary.Folder> 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)");
}

View file

@ -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<Geary.Folder>? 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<Geary.Folder> list_folders_async(Geary.FolderPath? parent,
Cancellable? cancellable = null) throws Error;
public abstract Gee.Collection<Geary.Folder> list_matching_folders(
Geary.FolderPath? parent) throws Error;
public abstract Gee.Collection<Geary.Folder> list_folders() throws Error;
public abstract Geary.ContactStore get_contact_store();

View file

@ -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();

View file

@ -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:

View file

@ -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;
}
}

View file

@ -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<Geary.Folder> list_folders_async(Geary.FolderPath? parent,
Cancellable? cancellable = null) throws Error;
public abstract Gee.Collection<Geary.Folder> list_matching_folders(
Geary.FolderPath? parent) throws Error;
/**
* Lists all currently-available folders. See caveats under
* list_matching_folders().
*/
public abstract Gee.Collection<Geary.Folder> list_folders() throws Error;
/**
* Gets a perpetually update-to-date collection of autocompletion contacts.

View file

@ -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();
}

View file

@ -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

View file

@ -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<Geary.FolderPath, FolderReference> folder_refs =
new Gee.HashMap<Geary.FolderPath, FolderReference>(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

View file

@ -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<OutboxRow> outbox_queue = new NonblockingMailbox<OutboxRow>();
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;

View file

@ -36,9 +36,9 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
private static Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>? 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<Geary.FolderPath, Geary.SpecialFolderType>(

View file

@ -18,9 +18,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
private Gee.HashMap<FolderPath, Folder> local_only = new Gee.HashMap<FolderPath, Folder>(
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<Geary.Folder> list_folders_async(Geary.FolderPath? parent,
Cancellable? cancellable = null) throws Error {
public override Gee.Collection<Geary.Folder> list_matching_folders(
Geary.FolderPath? parent) throws Error {
check_open();
Gee.ArrayList<Geary.Folder> matches = new Gee.ArrayList<Geary.Folder>();
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<Geary.Folder> list_folders() throws Error {
check_open();
return existing_folders.values;
}
private async Gee.Collection<Geary.Folder> 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);
}
}

View file

@ -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();

View file

@ -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,

View file

@ -33,9 +33,9 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
private static Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>? 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<Geary.FolderPath, Geary.SpecialFolderType>(

View file

@ -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<string, string?> delims = new Gee.HashMap<string, string?>();
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() {

View file

@ -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<ClientSession> sessions = new Gee.HashSet<ClientSession>();
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();
}
}