From c3340e91fbe1dcf297508f6a60f993788ac5ad55 Mon Sep 17 00:00:00 2001 From: Matthew Pirocchi Date: Wed, 6 Jun 2012 10:39:49 -0700 Subject: [PATCH] Don't force user to save password: Closes #5317, #5291, #5218, #5083. - Give the user a "remember password" option that they can uncheck if they don't want Geary to remember their password. You've seen most of this part of the patch before, aside from a few bugfixes. - Display a nicer dialog when re-prompting the user for their password. This only shows (editable) fields for "password" and "remember password" by default, with (non-editable) fields for "Service" and "Real name" available in an expander. - Fix a crash that occurred whenever an account is connected when there was previously another account (or the same account) connected. --- src/CMakeLists.txt | 1 + src/client/geary-application.vala | 118 ++++++---- src/client/geary-controller.vala | 41 ++-- src/client/ui/folder-list.vala | 16 +- src/client/ui/geary-login.vala | 30 ++- src/client/ui/password-dialog.vala | 80 +++++++ src/client/ui/sidebar/sidebar-tree.vala | 18 +- src/client/util/util-keyring.vala | 15 +- src/engine/api/geary-account-information.vala | 24 +- .../api/geary-conversation-monitor.vala | 26 ++- src/engine/api/geary-credentials.vala | 2 +- src/engine/util/util-string.vala | 4 + ui/login.glade | 24 +- ui/password-dialog.glade | 216 ++++++++++++++++++ 14 files changed, 504 insertions(+), 111 deletions(-) create mode 100644 src/client/ui/password-dialog.vala create mode 100644 ui/password-dialog.glade diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e617721d..31bf8413 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -185,6 +185,7 @@ client/ui/message-list-cell-renderer.vala client/ui/message-list-store.vala client/ui/message-list-view.vala client/ui/message-viewer.vala +client/ui/password-dialog.vala client/ui/preferences-dialog.vala client/ui/sidebar/sidebar-branch.vala diff --git a/src/client/geary-application.vala b/src/client/geary-application.vala index a9de9040..ef067440 100644 --- a/src/client/geary-application.vala +++ b/src/client/geary-application.vala @@ -165,9 +165,24 @@ along with Geary; if not, write to the Free Software Foundation, Inc., return; } - private void initialize_account(bool replace_existing_data = false) { + private void set_account(Geary.EngineAccount? 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(this.account); + } + + private void initialize_account() { string? username = get_username(); - if (username == null || replace_existing_data) + if (username == null) create_account(username); else open_account(username); @@ -213,16 +228,14 @@ along with Geary; if not, write to the Free Software Foundation, Inc., if (success) { account_information.store_async.begin(cancellable); - account = account_information.get_account(); - account.report_problem.connect(on_report_problem); - controller.connect_account(account); + set_account(account_information.get_account()); } else { Geary.AccountInformation new_account_information = request_account_information(account_information); // If the user refused to enter account information. if (new_account_information == null) { - account = null; + set_account(null); return; } @@ -230,30 +243,29 @@ along with Geary; if not, write to the Free Software Foundation, Inc., } } - private void open_account(string username) { - string? password = get_password(username); - if (password == null) { - password = request_password(username); - - // If the user refused to enter a password. - if (password == null) { - account = null; - return; - } - } - - // Now we know password is non-null. - Geary.Credentials credentials = new Geary.Credentials(username, password); + private void open_account(string username, string? old_password = null, Cancellable? cancellable = null) { + Geary.Credentials credentials = new Geary.Credentials(username, null); Geary.AccountInformation account_information = new Geary.AccountInformation(credentials); try { account_information.load_info_from_file(); } catch (Error err) { - error("Problem loading account information: %s", err.message); + // TODO: Handle this more gracefully? + error("Problem loading account information from file: %s", err.message); } - account = account_information.get_account(); - account.report_problem.connect(on_report_problem); - controller.connect_account(account); + bool remember_password = account_information.remember_password; + string? password = get_password(account_information.credentials.user, old_password, ref remember_password); + // If there was no saved password and the user refused to enter a password. + if (password == null) { + set_account(null); + return; + } + + account_information.remember_password = remember_password; + account_information.store_async.begin(cancellable); + + account_information.credentials.pass = password; + set_account(account_information.get_account()); } private string? get_username() { @@ -270,34 +282,51 @@ along with Geary; if not, write to the Free Software Foundation, Inc., return null; } - private string? get_password(string username) { - // TODO: For now we always get the password from the keyring. This will change when we - // allow users to not save their password. - string? password = keyring_get_password(username); - return Geary.String.is_empty(password) ? null : password; - } + private string? get_password(string username, string? old_password, ref bool remember_password) { + string? password = null; + if (old_password == null && remember_password) + password = keyring_get_password(username); + + if (Geary.String.is_null_or_whitespace(password)) + password = request_password(username, old_password, out remember_password); + + return password; + } private string get_default_real_name() { string real_name = Environment.get_real_name(); return real_name == "Unknown" ? "" : real_name; } - private string? request_password(string username) { - // TODO: For now we use the full LoginDialog. This should be changed to a dialog that only - // allows editting the password. - - Geary.Credentials credentials = new Geary.Credentials(username, null); + private string? request_password(string username, string? old_password, out bool remember_password) { + Geary.Credentials credentials = new Geary.Credentials(username, old_password); Geary.AccountInformation old_account_information = new Geary.AccountInformation(credentials); try { old_account_information.load_info_from_file(); } catch (Error err) { - debug("Problem loading account information: %s", err.message); - old_account_information = null; + // TODO: Handle this more gracefully? + error("Error loading account information: %s", err.message); } - Geary.AccountInformation account_information = request_account_information(old_account_information); - return account_information == null ? null : account_information.credentials.pass; + PasswordDialog password_dialog = new PasswordDialog(old_account_information); + if (!password_dialog.run()) { + exit(1); + remember_password = false; + return null; + } + + // password_dialog.password should never be null at this point. It will only be null when + // password_dialog.run() returns false, in which case we have already exited/returned. + string? password = password_dialog.password; + remember_password = password_dialog.remember_password; + + if (remember_password) + keyring_save_password(new Geary.Credentials(username, password)); + else + keyring_delete_password(username); + + return password; } // Prompt the user for a service, real name, username, and password, and try to start Geary. @@ -312,8 +341,10 @@ along with Geary; if not, write to the Free Software Foundation, Inc., return null; } - // TODO: This should be optional. - keyring_save_password(login_dialog.account_information.credentials); + if (login_dialog.account_information.remember_password) + keyring_save_password(login_dialog.account_information.credentials); + else + keyring_delete_password(login_dialog.account_information.credentials.user); return login_dialog.account_information; } @@ -330,10 +361,9 @@ along with Geary; if not, write to the Free Software Foundation, Inc., case Geary.Account.Problem.LOGIN_FAILED: debug("Login failed."); - if (controller != null) - controller.stop(); + Geary.Credentials old_credentials = account.get_account_information().credentials; account.report_problem.disconnect(on_report_problem); - initialize_account(true); + open_account(old_credentials.user, old_credentials.pass); break; default: diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala index 015779a9..3a068efb 100644 --- a/src/client/geary-controller.vala +++ b/src/client/geary-controller.vala @@ -77,10 +77,7 @@ public class GearyController { private int conversations_added_counter = 0; private Gee.LinkedList composer_windows = new Gee.LinkedList(); - private Geary.EngineAccount? _account = null; - private Geary.EngineAccount? account { - get { return _account; } - } + private Geary.EngineAccount? account { get; private set; } public GearyController() { // Setup actions. @@ -245,13 +242,16 @@ public class GearyController { return entries; } - public void connect_account(Geary.EngineAccount? account) { - if (_account == account) + public void connect_account(Geary.EngineAccount? new_account) { + if (account == new_account) return; - + // Disconnect the old account, if any. - if (_account != null) { - _account.folders_added_removed.disconnect(on_folders_added_removed); + if (account != null) { + cancel_folder(); + cancel_message(); + + account.folders_added_removed.disconnect(on_folders_added_removed); Gtk.Action delete_message = GearyApplication.instance.actions.get_action(ACTION_DELETE_MESSAGE); delete_message.label = DEFAULT_DELETE_MESSAGE_LABEL; @@ -260,30 +260,29 @@ public class GearyController { main_window.title = GearyApplication.NAME; - main_window.folder_list.set_user_folders_root_name(""); + main_window.folder_list.remove_all_branches(); } - _account = account; + account = new_account; // Connect the new account, if any. - if (_account != null) { - _account.folders_added_removed.connect(on_folders_added_removed); + if (account != null) { + account.folders_added_removed.connect(on_folders_added_removed); // Personality-specific setup. - if (_account.delete_is_archive()) { + if (account.delete_is_archive()) { Gtk.Action delete_message = GearyApplication.instance.actions.get_action(ACTION_DELETE_MESSAGE); delete_message.label = _("Archive Message"); delete_message.tooltip = _("Archive the selected conversation"); delete_message.icon_name = "archive-insert"; } - if (_account.get_account_information().service_provider == Geary.ServiceProvider.YAHOO) + if (account.get_account_information().service_provider == Geary.ServiceProvider.YAHOO) main_window.title = GearyApplication.NAME + "!"; - main_window.folder_list.set_user_folders_root_name(_account.get_user_folders_label()); + main_window.folder_list.set_user_folders_root_name(account.get_user_folders_label()); + load_folders.begin(cancellable_folder); } - - load_folders.begin(cancellable_folder); } private bool is_viewed_conversation(Geary.Conversation? conversation) { @@ -336,12 +335,6 @@ public class GearyController { } } - public void stop() { - cancel_folder(); - cancel_message(); - connect_account(null); - } - private void on_folder_selected(Geary.Folder? folder) { if (folder == null) { debug("no folder selected"); diff --git a/src/client/ui/folder-list.vala b/src/client/ui/folder-list.vala index acaf584f..f1e2c698 100644 --- a/src/client/ui/folder-list.vala +++ b/src/client/ui/folder-list.vala @@ -114,9 +114,7 @@ public class FolderList : Sidebar.Tree { base(new Gtk.TargetEntry[0], Gdk.DragAction.ASK, drop_handler); entry_selected.connect(on_entry_selected); - user_folder_group = new Sidebar.Grouping("", IconFactory.instance.label_folder_icon); - user_folder_branch = new Sidebar.Branch(user_folder_group, - Sidebar.Branch.Options.STARTUP_OPEN_GROUPING, user_folder_comparator); + reset_user_folder_group(); graft(user_folder_branch, int.MAX); // Set self as a drag destination. @@ -146,6 +144,12 @@ public class FolderList : Sidebar.Tree { user_folder_group.rename(name); } + private void reset_user_folder_group() { + user_folder_group = new Sidebar.Grouping("", IconFactory.instance.label_folder_icon); + user_folder_branch = new Sidebar.Branch(user_folder_group, + Sidebar.Branch.Options.STARTUP_OPEN_GROUPING, user_folder_comparator); + } + public void add_folder(Geary.Folder folder) { FolderEntry folder_entry = new FolderEntry(folder); @@ -174,6 +178,12 @@ public class FolderList : Sidebar.Tree { entries.set(folder.get_path(), branch.get_root()); } + 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); if (entry != null) diff --git a/src/client/ui/geary-login.vala b/src/client/ui/geary-login.vala index e5ffd664..f9b910d0 100644 --- a/src/client/ui/geary-login.vala +++ b/src/client/ui/geary-login.vala @@ -11,6 +11,7 @@ public class LoginDialog { private Gtk.Entry entry_password; private Gtk.Entry entry_real_name; private Gtk.ComboBoxText combo_service; + private Gtk.CheckButton check_remember_password; private Gtk.Alignment other_info; private Gtk.Entry entry_imap_host; @@ -29,17 +30,18 @@ public class LoginDialog { public LoginDialog.from_account_information(Geary.AccountInformation default_account_information) { this(default_account_information.real_name, default_account_information.credentials.user, - default_account_information.credentials.pass, default_account_information.service_provider, - default_account_information.imap_server_host, default_account_information.imap_server_port, - default_account_information.imap_server_ssl, default_account_information.smtp_server_host, - default_account_information.smtp_server_port, default_account_information.smtp_server_ssl); + default_account_information.credentials.pass, default_account_information.remember_password, + default_account_information.service_provider, default_account_information.imap_server_host, + default_account_information.imap_server_port, default_account_information.imap_server_ssl, + default_account_information.smtp_server_host, default_account_information.smtp_server_port, + default_account_information.smtp_server_ssl); } - public LoginDialog(string default_real_name, string? default_username = null, - string? default_password = null, int default_service_provider = -1,string? default_imap_host = null, - uint16 default_imap_port = Geary.Imap.ClientConnection.DEFAULT_PORT_SSL, bool default_imap_ssl = true, - string? default_smtp_host = null, uint16 default_smtp_port = Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL, - bool default_smtp_ssl = true) { + public LoginDialog(string? default_real_name = null, string? default_username = null, + string? default_password = null, bool default_remember_password = true, int default_service_provider = -1, + string? default_imap_host = null, uint16 default_imap_port = Geary.Imap.ClientConnection.DEFAULT_PORT_SSL, + bool default_imap_ssl = true, string? default_smtp_host = null, + uint16 default_smtp_port = Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL, bool default_smtp_ssl = true) { Gtk.Builder builder = GearyApplication.instance.create_builder("login.glade"); dialog = builder.get_object("LoginDialog") as Gtk.Dialog; @@ -50,6 +52,7 @@ public class LoginDialog { combo_service = builder.get_object("service") as Gtk.ComboBoxText; entry_username = builder.get_object("username") as Gtk.Entry; entry_password = builder.get_object("password") as Gtk.Entry; + check_remember_password = builder.get_object("remember_password") as Gtk.CheckButton; other_info = builder.get_object("other_info") as Gtk.Alignment; entry_imap_host = builder.get_object("imap host") as Gtk.Entry; @@ -74,6 +77,7 @@ public class LoginDialog { entry_real_name.set_text(default_real_name ?? ""); entry_username.set_text(default_username ?? ""); entry_password.set_text(default_password ?? ""); + check_remember_password.active = default_remember_password; entry_imap_host.set_text(default_imap_host ?? ""); entry_imap_port.set_text(default_imap_port.to_string()); check_imap_ssl.active = default_imap_ssl; @@ -89,6 +93,7 @@ public class LoginDialog { entry_username.changed.connect(on_changed); entry_password.changed.connect(on_changed); entry_real_name.changed.connect(on_changed); + check_remember_password.toggled.connect(on_changed); combo_service.changed.connect(on_changed); entry_imap_host.changed.connect(on_changed); entry_imap_port.changed.connect(on_changed); @@ -124,6 +129,7 @@ public class LoginDialog { account_information = new Geary.AccountInformation(credentials); account_information.real_name = entry_real_name.text.strip(); + account_information.remember_password = check_remember_password.active; account_information.service_provider = get_service_provider(); account_information.imap_server_host = entry_imap_host.text.strip(); account_information.imap_server_port = (uint16) int.parse(entry_imap_port.text.strip()); @@ -132,6 +138,8 @@ public class LoginDialog { account_information.smtp_server_port = (uint16) int.parse(entry_smtp_port.text.strip()); account_information.smtp_server_ssl = check_smtp_ssl.active; + on_changed(); + dialog.destroy(); return true; } @@ -184,8 +192,8 @@ public class LoginDialog { } private bool is_complete() { - if (Geary.String.is_empty(entry_username.text.strip()) || - Geary.String.is_empty(entry_password.text.strip())) + if (Geary.String.is_null_or_whitespace(entry_username.text.strip()) || + Geary.String.is_null_or_whitespace(entry_password.text.strip())) return false; // For "other" providers, check server settings. diff --git a/src/client/ui/password-dialog.vala b/src/client/ui/password-dialog.vala new file mode 100644 index 00000000..d6475799 --- /dev/null +++ b/src/client/ui/password-dialog.vala @@ -0,0 +1,80 @@ +/* Copyright 2011-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. + */ + +/** + * Displays a dialog for collecting the user's password, without allowing them to change their + * other data. + */ +public class PasswordDialog { + private Gtk.Dialog dialog; + private Gtk.Entry password_entry; + private Gtk.CheckButton remember_password_checkbutton; + private Gtk.Button ok_button; + + public string password { get; private set; default = ""; } + + public bool remember_password { get; private set; } + + public PasswordDialog(Geary.AccountInformation account_information) { + Gtk.Builder builder = GearyApplication.instance.create_builder("password-dialog.glade"); + + // Load dialog + dialog = (Gtk.Dialog)builder.get_object("PasswordDialog"); + dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG); + dialog.set_default_response(Gtk.ResponseType.OK); + + // Load editable widgets + password_entry = (Gtk.Entry)builder.get_object("password_entry"); + remember_password_checkbutton = (Gtk.CheckButton)builder.get_object("remember_password_checkbutton"); + + // Load non-editable widgets + Gtk.Label email_label = (Gtk.Label)builder.get_object("email_label"); + Gtk.Label real_name_label = (Gtk.Label)builder.get_object("real_name_label"); + Gtk.Label service_label = (Gtk.Label)builder.get_object("service_label"); + + // Load default values + email_label.set_text(account_information.credentials.user ?? ""); + password_entry.set_text(account_information.credentials.pass ?? ""); + remember_password_checkbutton.active = account_information.remember_password; + real_name_label.set_text(account_information.real_name ?? ""); + service_label.set_text(account_information.service_provider.display_name() ?? ""); + + // Add action buttons + Gtk.Button cancel_button = new Gtk.Button.from_stock(Gtk.Stock.CANCEL); + ok_button = new Gtk.Button.from_stock(Gtk.Stock.OK); + ok_button.can_default = true; + dialog.add_action_widget(cancel_button, Gtk.ResponseType.CANCEL); + dialog.add_action_widget(ok_button, Gtk.ResponseType.OK); + dialog.set_default_response(Gtk.ResponseType.OK); + + // Setup listeners + refresh_ok_button_sensitivity(); + password_entry.changed.connect(refresh_ok_button_sensitivity); + } + + private void refresh_ok_button_sensitivity() { + ok_button.sensitive = !Geary.String.is_null_or_whitespace(password_entry.get_text()); + + } + + public bool run() { + dialog.show(); + dialog.get_action_area().show_all(); + + Gtk.ResponseType response = (Gtk.ResponseType)dialog.run(); + if (response != Gtk.ResponseType.OK) { + dialog.destroy(); + return false; + } + + password = password_entry.get_text(); + remember_password = remember_password_checkbutton.active; + + dialog.destroy(); + return true; + } +} + diff --git a/src/client/ui/sidebar/sidebar-tree.vala b/src/client/ui/sidebar/sidebar-tree.vala index b28b8039..69605891 100644 --- a/src/client/ui/sidebar/sidebar-tree.vala +++ b/src/client/ui/sidebar/sidebar-tree.vala @@ -237,7 +237,13 @@ public class Sidebar.Tree : Gtk.TreeView { public bool is_selected(Sidebar.Entry entry) { EntryWrapper? wrapper = get_wrapper(entry); - return (wrapper != null) ? get_selection().path_is_selected(wrapper.get_path()) : false; + // Even though get_selection() does not report its return type as nullable, it can be null + // if the window has been destroyed. + Gtk.TreeSelection selection = get_selection(); + if (selection == null) + return false; + + return (wrapper != null) ? selection.path_is_selected(wrapper.get_path()) : false; } public bool is_any_selected() { @@ -456,6 +462,16 @@ public class Sidebar.Tree : Gtk.TreeView { return new_wrapper; } + protected void prune_all() { + while (branches.keys.size > 0) { + Gee.Iterator iterator = branches.keys.iterator(); + if (!iterator.next()) + break; + + prune(iterator.get()); + } + } + public void prune(Sidebar.Branch branch) { assert(branches.has_key(branch)); diff --git a/src/client/util/util-keyring.vala b/src/client/util/util-keyring.vala index 3bbe55d1..cfb48d51 100644 --- a/src/client/util/util-keyring.vala +++ b/src/client/util/util-keyring.vala @@ -9,17 +9,22 @@ const string GEARY_USERNAME_PREFIX = "org.yorba.geary username:"; public static bool keyring_save_password(Geary.Credentials credentials) { string name = GEARY_USERNAME_PREFIX + credentials.user; - GnomeKeyring.Result res = GnomeKeyring.store_password_sync(GnomeKeyring.NETWORK_PASSWORD, null, - name, credentials.pass, "user", name); + GnomeKeyring.Result result = GnomeKeyring.store_password_sync(GnomeKeyring.NETWORK_PASSWORD, + null, name, credentials.pass, "user", name); + + return result == GnomeKeyring.Result.OK; +} - return res == GnomeKeyring.Result.OK; +public static void keyring_delete_password(string username) { + GnomeKeyring.delete_password_sync(GnomeKeyring.NETWORK_PASSWORD, "user", + GEARY_USERNAME_PREFIX + username); } // Returns the password for the given username, or null if not set. public static string? keyring_get_password(string username) { string password; - GnomeKeyring.Result res = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD, out password, - "user", GEARY_USERNAME_PREFIX + username); + GnomeKeyring.Result res = GnomeKeyring.find_password_sync(GnomeKeyring.NETWORK_PASSWORD, + out password, "user", GEARY_USERNAME_PREFIX + username); return res == GnomeKeyring.Result.OK ? password : null; } diff --git a/src/engine/api/geary-account-information.vala b/src/engine/api/geary-account-information.vala index 8efe5dfa..a8f77b2a 100644 --- a/src/engine/api/geary-account-information.vala +++ b/src/engine/api/geary-account-information.vala @@ -8,6 +8,7 @@ public class Geary.AccountInformation : Object { private const string GROUP = "AccountInformation"; private const string REAL_NAME_KEY = "real_name"; private const string SERVICE_PROVIDER_KEY = "service_provider"; + private const string REMEMBER_PASSWORD_KEY = "remember_password"; private const string IMAP_HOST = "imap_host"; private const string IMAP_PORT = "imap_port"; private const string IMAP_SSL = "imap_ssl"; @@ -18,6 +19,7 @@ public class Geary.AccountInformation : Object { public const string SETTINGS_FILENAME = "geary.ini"; + internal File? settings_dir; internal File? file = null; public string real_name { get; set; } public Geary.ServiceProvider service_provider { get; set; } @@ -32,10 +34,13 @@ public class Geary.AccountInformation : Object { public bool smtp_server_ssl { get; set; default = true; } public Geary.Credentials credentials { get; private set; } + public bool remember_password { get; set; default = true; } public AccountInformation(Geary.Credentials credentials) { this.credentials = credentials; - this.file = get_settings_file(); + + this.settings_dir = Geary.Engine.user_data_dir.get_child(credentials.user); + this.file = settings_dir.get_child(SETTINGS_FILENAME); } public void load_info_from_file() throws Error { @@ -46,6 +51,7 @@ public class Geary.AccountInformation : Object { // The file didn't exist. No big deal -- just means we give you the defaults. } finally { real_name = get_string_value(key_file, GROUP, REAL_NAME_KEY); + remember_password = get_bool_value(key_file, GROUP, REMEMBER_PASSWORD_KEY, true); service_provider = Geary.ServiceProvider.from_string(get_string_value(key_file, GROUP, SERVICE_PROVIDER_KEY)); @@ -177,6 +183,15 @@ public class Geary.AccountInformation : Object { public async void store_async(Cancellable? cancellable = null) { assert(file != null); + if (!settings_dir.query_exists(cancellable)) { + try { + settings_dir.make_directory_with_parents(); + } catch (Error err) { + error("Error creating settings directory for user '%s': %s", credentials.user, + err.message); + } + } + if (!file.query_exists(cancellable)) { try { yield file.create_async(FileCreateFlags.REPLACE_DESTINATION); @@ -189,6 +204,7 @@ public class Geary.AccountInformation : Object { key_file.set_value(GROUP, REAL_NAME_KEY, real_name); key_file.set_value(GROUP, SERVICE_PROVIDER_KEY, service_provider.to_string()); + key_file.set_boolean(GROUP, REMEMBER_PASSWORD_KEY, remember_password); key_file.set_value(GROUP, IMAP_HOST, imap_server_host); key_file.set_integer(GROUP, IMAP_PORT, imap_server_port); @@ -206,11 +222,7 @@ public class Geary.AccountInformation : Object { yield file.replace_contents_async(data.data, null, false, FileCreateFlags.NONE, cancellable, out new_etag); } catch (Error err) { - debug("Error writign to account info file: %s", err.message); + debug("Error writing to account info file: %s", err.message); } } - - private File get_settings_file() { - return Geary.Engine.user_data_dir.get_child(credentials.user).get_child(SETTINGS_FILENAME); - } } diff --git a/src/engine/api/geary-conversation-monitor.vala b/src/engine/api/geary-conversation-monitor.vala index edd3b212..0e72d7a4 100644 --- a/src/engine/api/geary-conversation-monitor.vala +++ b/src/engine/api/geary-conversation-monitor.vala @@ -92,7 +92,9 @@ public class Geary.ConversationMonitor : Object { id_ascending.add(email); id_descending.add(email); - message_ids.add_all(email.get_ancestors()); + Gee.Set? ancestors = email.get_ancestors(); + if (ancestors != null) + message_ids.add_all(ancestors); } public void remove(Email email) { @@ -502,21 +504,23 @@ public class Geary.ConversationMonitor : Object { // Right now, all threading is done with Message-IDs (no parsing of subject lines, etc.) // If a message doesn't have a Message-ID, it's treated as its own conversation - Gee.Set ancestors = email.get_ancestors(); + Gee.Set? ancestors = email.get_ancestors(); // see if any of these ancestor IDs maps to an existing conversation ImplConversation? conversation = null; - foreach (ImplConversation known in conversations) { - foreach (RFC822.MessageID ancestor in ancestors) { - if (known.tracks_message_id(ancestor)) { - conversation = known; - - break; + if (ancestors != null) { + foreach (ImplConversation known in conversations) { + foreach (RFC822.MessageID ancestor in ancestors) { + if (known.tracks_message_id(ancestor)) { + conversation = known; + + break; + } } + + if (conversation != null) + break; } - - if (conversation != null) - break; } // create new conversation if not seen before diff --git a/src/engine/api/geary-credentials.vala b/src/engine/api/geary-credentials.vala index f9296792..dba956e6 100644 --- a/src/engine/api/geary-credentials.vala +++ b/src/engine/api/geary-credentials.vala @@ -6,7 +6,7 @@ public class Geary.Credentials { public string user { get; private set; } - public string pass { get; private set; } + public string pass { get; set; } public Credentials(string? user, string? pass) { this.user = user ?? ""; diff --git a/src/engine/util/util-string.vala b/src/engine/util/util-string.vala index f4b4e340..c0397ac8 100644 --- a/src/engine/util/util-string.vala +++ b/src/engine/util/util-string.vala @@ -6,6 +6,10 @@ namespace Geary.String { +public inline bool is_null_or_whitespace(string? str) { + return str == null || str.strip()[0] == 0; +} + public inline bool is_empty(string? str) { return (str == null || str[0] == 0); } diff --git a/ui/login.glade b/ui/login.glade index cb4e1285..827c5504 100644 --- a/ui/login.glade +++ b/ui/login.glade @@ -1,6 +1,6 @@ - + False True @@ -187,10 +187,24 @@ - - - - + + _Remember password + False + True + True + False + False + True + 0 + True + True + + + 1 + 4 + 1 + 1 + diff --git a/ui/password-dialog.glade b/ui/password-dialog.glade new file mode 100644 index 00000000..75a0236e --- /dev/null +++ b/ui/password-dialog.glade @@ -0,0 +1,216 @@ + + + + + False + 5 + Login + dialog + + + False + vertical + 2 + + + True + False + 2 + 4 + + + True + False + 0 + Email address: + + + 0 + 0 + 1 + 1 + + + + + True + False + 0 + _Password: + True + password_entry + + + 0 + 1 + 1 + 1 + + + + + True + False + True + 0 + 2 + example@example.com + + + 1 + 0 + 1 + 1 + + + + + True + True + True + False + + True + True + + + 1 + 1 + 1 + 1 + + + + + _Remember password + False + True + True + False + False + True + 0 + True + + + 1 + 2 + 1 + 1 + + + + + + + + False + True + 1 + + + + + False + end + + + + + + + + + False + True + end + 2 + + + + + True + True + + + True + False + 2 + 4 + + + True + False + 0 + Service: + + + 0 + 0 + 1 + 1 + + + + + True + False + 0 + Real name: + + + 0 + 1 + 1 + 1 + + + + + True + False + 0 + Service + + + 1 + 0 + 1 + 1 + + + + + True + False + 0 + Real name + + + 1 + 1 + 1 + 1 + + + + + + + True + False + _Details + True + + + + + False + True + 3 + + + + + +