Ensure AccountInformation objects always have valid IMAP/SMTP services.

* src/engine/api/geary-engine.vala (Engine): Require a ServiceInformation
  object for IMAP and SMTP when creating orphan info objects. Update call
  sites.

* src/client/accounts/account-manager.vala (AccountManager): provide
  factory methods for constructing local service info objects. Use these
  when re-constituting accounts. Use a singleton SecretMediator object
  instance as a bonus.

* src/client/accounts/add-edit-page.vala (AddEditPage): Get
  ServiceInformation objects from the account manager, pass an instance
  of GearyApplication through so it has access to the manager. Update
  call chains back to GearyController to pass the app instance through.
This commit is contained in:
Michael James Gratton 2018-05-24 16:51:16 +10:00
parent 91e85a6647
commit 4fdeb9db47
8 changed files with 117 additions and 69 deletions

View file

@ -6,7 +6,8 @@
// Add or edit an account. Used with AccountDialog.
public class AccountDialogAddEditPane : AccountDialogPane {
public AddEditPage add_edit_page { get; private set; default = new AddEditPage(); }
public AddEditPage add_edit_page { get; private set; }
private Gtk.ButtonBox button_box = new Gtk.ButtonBox(Gtk.Orientation.HORIZONTAL);
private Gtk.Button ok_button = new Gtk.Button.with_mnemonic(Stock._OK);
private Gtk.Button cancel_button = new Gtk.Button.with_mnemonic(Stock._CANCEL);
@ -19,9 +20,11 @@ public class AccountDialogAddEditPane : AccountDialogPane {
public signal void edit_alternate_emails(string id);
public AccountDialogAddEditPane(Gtk.Stack stack) {
public AccountDialogAddEditPane(GearyApplication application, Gtk.Stack stack) {
base(stack);
this.add_edit_page = new AddEditPage(application);
button_box.set_layout(Gtk.ButtonBoxStyle.END);
button_box.expand = false;
button_box.spacing = 6;

View file

@ -37,7 +37,7 @@ public class AccountDialog : Gtk.Dialog {
// Add pages to stack.
account_list_pane = new AccountDialogAccountListPane(application, stack);
add_edit_pane = new AccountDialogAddEditPane(stack);
add_edit_pane = new AccountDialogAddEditPane(application, stack);
spinner_pane = new AccountDialogSpinnerPane(stack);
remove_confirm_pane = new AccountDialogRemoveConfirmPane(stack);
remove_fail_pane = new AccountDialogRemoveFailPane(stack);

View file

@ -4,6 +4,10 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
errordomain AccountError {
INVALID;
}
public class AccountManager : GLib.Object {
@ -11,6 +15,8 @@ public class AccountManager : GLib.Object {
private GLib.File user_config_dir;
private GLib.File user_data_dir;
private Geary.CredentialsMediator libsecret;
public AccountManager(Geary.Engine engine,
GLib.File user_config_dir,
@ -18,6 +24,13 @@ public class AccountManager : GLib.Object {
this.engine = engine;
this.user_config_dir = user_config_dir;
this.user_data_dir = user_data_dir;
this.libsecret = new SecretMediator();
}
public Geary.ServiceInformation new_libsecret_service(Geary.Service service,
Geary.CredentialsMethod method) {
return new LocalServiceInformation(service, libsecret);
}
@ -101,20 +114,13 @@ public class AccountManager : GLib.Object {
provider = Geary.CredentialsProvider.from_string(Geary.Config.get_string_value(key_file, Geary.Config.GROUP, Geary.Config.CREDENTIALS_PROVIDER_KEY, Geary.CredentialsProvider.LIBSECRET.to_string()));
method = Geary.CredentialsMethod.from_string(Geary.Config.get_string_value(key_file, Geary.Config.GROUP, Geary.Config.CREDENTIALS_METHOD_KEY, Geary.CredentialsMethod.PASSWORD.to_string()));
switch (provider) {
case Geary.CredentialsProvider.LIBSECRET:
mediator = new SecretMediator();
imap_information = new LocalServiceInformation(
Geary.Service.IMAP, mediator
);
smtp_information = new LocalServiceInformation(
Geary.Service.SMTP, mediator
);
break;
default:
mediator = null;
imap_information = null;
smtp_information = null;
break;
case Geary.CredentialsProvider.LIBSECRET:
imap_information = new_libsecret_service(Geary.Service.IMAP, method);
smtp_information = new_libsecret_service(Geary.Service.SMTP, method);
break;
default:
throw new AccountError.INVALID("Unhandled credentials provider");
}
Geary.AccountInformation info = new Geary.AccountInformation(

View file

@ -154,7 +154,9 @@ public class AddEditPage : Gtk.Box {
SSL = 1,
STARTTLS = 2
}
private GearyApplication application;
private PageMode mode = PageMode.WELCOME;
private Gtk.Widget container_widget;
@ -219,12 +221,13 @@ public class AddEditPage : Gtk.Box {
public signal void size_changed();
public signal void edit_alternate_emails();
public AddEditPage() {
public AddEditPage(GearyApplication application) {
Object(orientation: Gtk.Orientation.VERTICAL, spacing: 4);
Gtk.Builder builder = GearyApplication.instance.create_builder("login.glade");
this.application = application;
Gtk.Builder builder = GioUtil.create_builder("login.glade");
// Primary container.
container_widget = (Gtk.Widget) builder.get_object("container");
pack_start(container_widget);
@ -272,7 +275,7 @@ public class AddEditPage : Gtk.Box {
textview_email_signature = new Gtk.TextView();
edit_window.add(textview_email_signature);
preview_webview = new ClientWebView(GearyApplication.instance.config);
preview_webview = new ClientWebView(application.config);
Gtk.ScrolledWindow preview_window = new Gtk.ScrolledWindow(null, null);
preview_window.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC);
@ -665,7 +668,7 @@ public class AddEditPage : Gtk.Box {
// encountered validation errors. So we need to deal with
// both cases.
try {
info = Geary.Engine.instance.get_account(this.id);
info = this.application.engine.get_account(this.id);
} catch (Error err) {
// id was for an orphan account
}
@ -673,8 +676,19 @@ public class AddEditPage : Gtk.Box {
if (info == null) {
// New account
Geary.ServiceInformation imap =
this.application.controller.account_manager.new_libsecret_service(
Geary.Service.IMAP,
Geary.CredentialsMethod.PASSWORD
);
Geary.ServiceInformation smtp =
this.application.controller.account_manager.new_libsecret_service(
Geary.Service.SMTP,
Geary.CredentialsMethod.PASSWORD
);
try {
info = Geary.Engine.instance.create_orphan_account();
info = this.application.engine.create_orphan_account(imap, smtp);
} catch (Error err) {
debug("Unable to create account %s for %s: %s",
this.id, this.email_address, err.message);

View file

@ -6,16 +6,20 @@
// Displays a dialog for collecting the user's login data.
public class LoginDialog : Gtk.Dialog {
private Gtk.Button ok_button;
private Gtk.Button cancel_button;
private AddEditPage page = new AddEditPage();
private AddEditPage page;
private AccountSpinnerPage spinner_page = new AccountSpinnerPage();
public LoginDialog() {
public LoginDialog(GearyApplication application) {
Object();
set_type_hint(Gdk.WindowTypeHint.DIALOG);
set_size_request(450, -1); // Sets min width.
this.page = new AddEditPage(application);
page.margin = 5;
spinner_page.margin = 5;
get_content_area().pack_start(page, true, true, 0);
@ -42,12 +46,13 @@ public class LoginDialog : Gtk.Dialog {
on_info_changed();
}
public LoginDialog.from_account_information(Geary.AccountInformation initial_account_information) {
this();
set_account_information(initial_account_information);
public LoginDialog.from_account_information(GearyApplication application,
Geary.AccountInformation initial) {
this(application);
set_account_information(initial);
}
public void set_account_information(Geary.AccountInformation info,
Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK) {
page.set_account_information(info, result);

View file

@ -900,7 +900,7 @@ public class GearyController : Geary.BaseObject {
Geary.AccountInformation? new_info = old_info;
if (login_dialog == null) {
// Create here so we know GTK is initialized.
login_dialog = new LoginDialog();
login_dialog = new LoginDialog(this.application);
} else if (!login_dialog.get_visible()) {
// If the dialog has been dismissed, exit here.
this.application.exit();

View file

@ -208,7 +208,9 @@ public class Geary.Engine : BaseObject {
* Throws an error if the engine has not been opened or if an
* invalid account id is generated.
*/
public AccountInformation create_orphan_account() throws Error {
public AccountInformation create_orphan_account(ServiceInformation imap,
ServiceInformation smtp)
throws GLib.Error {
check_opened();
// We might want to allow the client to specify the id, but
@ -233,7 +235,7 @@ public class Geary.Engine : BaseObject {
if (this.accounts.has_key(id))
throw new EngineError.ALREADY_EXISTS("Account %s already exists", id);
return new AccountInformation(id, null, null);
return new AccountInformation(id, imap, smtp);
}
/**

View file

@ -63,19 +63,31 @@ class Geary.EngineTest : TestCase {
public void create_orphan_account() throws Error {
try {
AccountInformation info = this.engine.create_orphan_account();
AccountInformation info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_01");
this.engine.add_account(info, true);
info = this.engine.create_orphan_account();
info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_02");
this.engine.add_account(info, true);
info = this.engine.create_orphan_account();
info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_03");
this.engine.add_account(info, true);
info = this.engine.create_orphan_account();
info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_04");
} catch (Error err) {
print("\nerr: %s\n", err.message);
@ -84,36 +96,42 @@ class Geary.EngineTest : TestCase {
}
public void create_orphan_account_with_legacy() throws Error {
try {
this.engine.add_account(
new AccountInformation(
"foo",
new MockServiceInformation(),
new MockServiceInformation()
),
true
);
this.engine.add_account(
new AccountInformation(
"foo",
new MockServiceInformation(),
new MockServiceInformation()
),
true
);
AccountInformation info = this.engine.create_orphan_account();
assert(info.id == "account_01");
this.engine.add_account(info, true);
AccountInformation info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_01");
this.engine.add_account(info, true);
assert(this.engine.create_orphan_account().id == "account_02");
info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_02");
this.engine.add_account(
new AccountInformation(
"bar",
new MockServiceInformation(),
new MockServiceInformation()
),
true
);
this.engine.add_account(
new AccountInformation(
"bar",
new MockServiceInformation(),
new MockServiceInformation()
),
true
);
assert(this.engine.create_orphan_account().id == "account_02");
} catch (Error err) {
print("\nerr: %s\n", err.message);
assert_not_reached();
}
info = this.engine.create_orphan_account(
new MockServiceInformation(),
new MockServiceInformation()
);
assert(info.id == "account_02");
}
private void delete(File parent) throws Error {