[geary] Fix delay showing composer for accounts with large numbers of messages.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary] Fix delay showing composer for accounts with large numbers of messages.
- Date: Fri, 23 Sep 2016 13:59:48 +0000 (UTC)
commit 6b58da6b99a2acd1f12c7ee42b4a841374e4c447
Author: Michael James Gratton <mike vee net>
Date: Fri Sep 23 23:31:01 2016 +1000
Fix delay showing composer for accounts with large numbers of messages.
* src/client/application/geary-controller.vala
(GearyController::create_compose_widget_async): Load draft manager and
email entry completion model asynchronously after the UI has been made
visible.
* src/client/composer/composer-widget.vala (ComposerWidget): Rename
set_entry_completions to load_entry_completions, make loading
async. Make open_draft_manager_async and load_entry_completions public
so they can be invoked by the controller.
* src/client/composer/contact-list-store.vala (ContactListStore): Load
contacts asynchronously in smaller batches, so the UI
remains responsive.
src/client/application/geary-controller.vala | 13 +++
src/client/composer/composer-widget.vala | 138 +++++++++++++-------------
src/client/composer/contact-list-store.vala | 67 ++++++++-----
3 files changed, 123 insertions(+), 95 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 34e6045..a88d1bb 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -2258,6 +2258,19 @@ public class GearyController : Geary.BaseObject {
new ComposerWindow(widget);
widget.state = ComposerWidget.ComposerState.DETACHED;
}
+
+ if (is_draft) {
+ try {
+ yield widget.open_draft_manager_async(referred.id);
+ } catch (Error e) {
+ message("Could not open draft manager: %s", e.message);
+ }
+ }
+
+ // For accounts with large numbers of contacts, loading the
+ // entry completions can some time, so do it after the UI has
+ // been shown
+ yield widget.load_entry_completions();
}
private bool should_create_new_composer(ComposerWidget.ComposeType? compose_type,
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index ecc2981..4471cdc 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -429,9 +429,6 @@ public class ComposerWidget : Gtk.EventBox {
this.plain_menu = (Menu) builder.get_object("plain_menu_model");
this.context_menu_model = (Menu) builder.get_object("context_menu_model");
- // TODO: It would be nicer to set the completions inside the EmailEntry constructor. But in
- // testing, this can cause non-deterministic segfaults. Investigate why, and fix if possible.
- set_entry_completions();
this.subject_entry.bind_property("text", this, "window-title", BindingFlags.SYNC_CREATE,
(binding, source_value, ref target_value) => {
target_value = Geary.String.is_empty_or_whitespace(this.subject_entry.text)
@@ -457,9 +454,8 @@ public class ComposerWidget : Gtk.EventBox {
this.from = new Geary.RFC822.MailboxAddresses.single(account.information.primary_mailbox);
- Geary.EmailIdentifier? editing_draft_id = null;
if (referred != null)
- editing_draft_id = fill_in_from_referred(referred, quote, is_referred_draft);
+ fill_in_from_referred(referred, quote);
update_from_field();
@@ -500,8 +496,6 @@ public class ComposerWidget : Gtk.EventBox {
this.editor.navigation_policy_decision_requested.connect(on_navigation_policy_decision_requested);
this.editor.new_window_policy_decision_requested.connect(on_navigation_policy_decision_requested);
- open_draft_manager_async.begin(editing_draft_id);
-
GearyApplication.instance.config.settings.changed[Configuration.SPELL_CHECK_KEY].connect(
on_spell_check_changed);
@@ -599,10 +593,37 @@ public class ComposerWidget : Gtk.EventBox {
update_actions();
}
+ /**
+ * Loads and sets contact auto-complete data for the current account.
+ */
+ public async void load_entry_completions() {
+ // XXX Since ContactListStore hooks into ContactStore to
+ // listen for contacts being added and removed,
+ // GearyController or some composer-related controller should
+ // construct an instance per account and keep it around for
+ // the lifetime of the app, since there can be tens of
+ // thousands of contacts for large accounts.
+ Geary.ContactStore contacts = this.account.get_contact_store();
+ if (this.contact_list_store == null ||
+ this.contact_list_store.contact_store != contacts) {
+ ContactListStore store = new ContactListStore(contacts);
+ this.contact_list_store = store;
+ yield store.load();
+
+ this.to_entry.completion = new ContactEntryCompletion(store);
+ this.cc_entry.completion = new ContactEntryCompletion(store);
+ this.bcc_entry.completion = new ContactEntryCompletion(store);
+ this.reply_to_entry.completion = new ContactEntryCompletion(store);
+ }
+ }
+
+ /**
+ * Restores the composer's widget state from its draft.
+ */
public async void restore_draft_state_async(Geary.Account account) {
bool first_email = true;
- foreach (Geary.RFC822.MessageID mid in in_reply_to) {
+ foreach (Geary.RFC822.MessageID mid in this.in_reply_to) {
Gee.MultiMap<Geary.Email, Geary.FolderPath?>? email_map;
try {
email_map =
@@ -664,12 +685,46 @@ public class ComposerWidget : Gtk.EventBox {
}
}
- // Copies the addresses (e.g. From/To/CC) and content from referred into this one
- private Geary.EmailIdentifier? fill_in_from_referred(Geary.Email referred, string? quote, bool
is_referred_draft) {
- if (referred == null)
- return null;
+ /**
+ * Creates and opens the composer's draft manager.
+ */
+ public async void open_draft_manager_async(
+ Geary.EmailIdentifier? editing_draft_id = null,
+ Cancellable? cancellable = null)
+ throws Error {
+ this.draft_save_text = "";
+ yield close_draft_manager_async(cancellable);
+
+ SimpleAction close_and_save = get_action(ACTION_CLOSE_AND_SAVE);
+ if (!this.account.information.save_drafts) {
+ this.header.save_and_close_button.hide();
+ return;
+ }
+
+ this.draft_manager = new Geary.App.DraftManager(account);
+ try {
+ yield this.draft_manager.open_async(editing_draft_id, cancellable);
+ } catch (Error err) {
+ debug("Unable to open draft manager %s: %s",
+ this.draft_manager.to_string(), err.message);
+
+ this.draft_manager = null;
+
+ throw err;
+ }
+
+ close_and_save.set_enabled(true);
+ this.header.save_and_close_button.show();
+
+ this.draft_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE]
+ .connect(on_draft_state_changed);
+ this.draft_manager.notify[Geary.App.DraftManager.PROP_CURRENT_DRAFT_ID]
+ .connect(on_draft_id_changed);
+ this.draft_manager.fatal.connect(on_draft_manager_fatal);
+ }
- Geary.EmailIdentifier? editing_draft_id = null;
+ // Copies the addresses (e.g. From/To/CC) and content from referred into this one
+ private void fill_in_from_referred(Geary.Email referred, string? quote) {
if (this.compose_type != ComposeType.NEW_MESSAGE) {
add_recipients_and_ids(this.compose_type, referred);
this.reply_subject = Geary.RFC822.Utils.create_subject_for_reply(referred);
@@ -703,9 +758,6 @@ public class ComposerWidget : Gtk.EventBox {
debug("Error getting message body: %s", error.message);
}
- if (is_referred_draft)
- editing_draft_id = referred.id;
-
add_attachments(referred.attachments);
break;
@@ -730,7 +782,6 @@ public class ComposerWidget : Gtk.EventBox {
this.pending_attachments = referred.attachments;
break;
}
- return editing_draft_id;
}
public void set_focus() {
@@ -1332,42 +1383,6 @@ public class ComposerWidget : Gtk.EventBox {
this.draft_save_text = DRAFT_ERROR_TEXT;
}
- // Returns the drafts folder for the current From account.
- private async void open_draft_manager_async(
- Geary.EmailIdentifier? editing_draft_id = null,
- Cancellable? cancellable = null)
- throws Error {
- this.draft_save_text = "";
- yield close_draft_manager_async(cancellable);
-
- SimpleAction close_and_save = get_action(ACTION_CLOSE_AND_SAVE);
- if (!this.account.information.save_drafts) {
- this.header.save_and_close_button.hide();
- return;
- }
-
- this.draft_manager = new Geary.App.DraftManager(account);
- try {
- yield this.draft_manager.open_async(editing_draft_id, cancellable);
- } catch (Error err) {
- debug("Unable to open draft manager %s: %s",
- this.draft_manager.to_string(), err.message);
-
- this.draft_manager = null;
-
- throw err;
- }
-
- close_and_save.set_enabled(true);
- this.header.save_and_close_button.show();
-
- this.draft_manager.notify[Geary.App.DraftManager.PROP_DRAFT_STATE]
- .connect(on_draft_state_changed);
- this.draft_manager.notify[Geary.App.DraftManager.PROP_CURRENT_DRAFT_ID]
- .connect(on_draft_id_changed);
- this.draft_manager.fatal.connect(on_draft_manager_fatal);
- }
-
private async void close_draft_manager_async(Cancellable? cancellable) throws Error {
get_action(ACTION_CLOSE_AND_SAVE).set_enabled(false);
if (this.draft_manager == null)
@@ -2335,22 +2350,9 @@ public class ComposerWidget : Gtk.EventBox {
return false;
this.account = new_account;
- set_entry_completions();
-
- return true;
- }
-
- private void set_entry_completions() {
- if (this.contact_list_store != null
- && this.contact_list_store.contact_store == account.get_contact_store())
- return;
+ load_entry_completions.begin();
- this.contact_list_store = new ContactListStore(account.get_contact_store());
-
- this.to_entry.completion = new ContactEntryCompletion(this.contact_list_store);
- this.cc_entry.completion = new ContactEntryCompletion(this.contact_list_store);
- this.bcc_entry.completion = new ContactEntryCompletion(this.contact_list_store);
- this.reply_to_entry.completion = new ContactEntryCompletion(this.contact_list_store);
+ return true;
}
}
diff --git a/src/client/composer/contact-list-store.vala b/src/client/composer/contact-list-store.vala
index 50b5f42..1bc30ea 100644
--- a/src/client/composer/contact-list-store.vala
+++ b/src/client/composer/contact-list-store.vala
@@ -5,9 +5,13 @@
*/
public class ContactListStore : Gtk.ListStore {
+
// Minimum visibility for the contact to appear in autocompletion.
private const Geary.ContactImportance CONTACT_VISIBILITY_THRESHOLD = Geary.ContactImportance.TO_TO;
-
+
+ // Batch size for loading contacts asynchronously
+ private uint LOAD_BATCH_SIZE = 4096;
+
public enum Column {
CONTACT_OBJECT,
CONTACT_MARKUP_NAME,
@@ -26,25 +30,35 @@ public class ContactListStore : Gtk.ListStore {
public ContactListStore(Geary.ContactStore contact_store) {
set_column_types(Column.get_types());
-
this.contact_store = contact_store;
-
- foreach (Geary.Contact contact in contact_store.contacts)
- add_contact(contact);
-
- // set sort function *after* adding all the contacts
- set_sort_func(Column.CONTACT_OBJECT, sort_func);
- set_sort_column_id(Column.CONTACT_OBJECT, Gtk.SortType.ASCENDING);
-
contact_store.contact_added.connect(on_contact_added);
contact_store.contact_updated.connect(on_contact_updated);
}
-
+
~ContactListStore() {
- contact_store.contact_added.disconnect(on_contact_added);
- contact_store.contact_updated.disconnect(on_contact_updated);
+ this.contact_store.contact_added.disconnect(on_contact_added);
+ this.contact_store.contact_updated.disconnect(on_contact_updated);
}
-
+
+ /**
+ * Loads contacts from the model's contact store.
+ */
+ public async void load() {
+ uint count = 0;
+ foreach (Geary.Contact contact in this.contact_store.contacts) {
+ add_contact(contact);
+ count++;
+ if (count % LOAD_BATCH_SIZE == 0) {
+ Idle.add(load.callback);
+ yield;
+ }
+ }
+
+ // set sort function *after* adding all the contacts
+ set_sort_func(Column.CONTACT_OBJECT, sort_func);
+ set_sort_column_id(Column.CONTACT_OBJECT, Gtk.SortType.ASCENDING);
+ }
+
public Geary.Contact get_contact(Gtk.TreeIter iter) {
GLib.Value contact_value;
get_value(iter, Column.CONTACT_OBJECT, out contact_value);
@@ -73,20 +87,19 @@ public class ContactListStore : Gtk.ListStore {
set(iter, Column.CONTACT_MARKUP_NAME, highlighted_result, -1);
}
}
-
- private void add_contact(Geary.Contact contact) {
- if (contact.highest_importance < CONTACT_VISIBILITY_THRESHOLD)
- return;
-
- string full_address = contact.get_rfc822_address().to_rfc822_string();
- Gtk.TreeIter iter;
- append(out iter);
- set(iter,
- Column.CONTACT_OBJECT, contact,
- Column.CONTACT_MARKUP_NAME, Markup.escape_text(full_address),
- Column.PRIOR_KEYS, new Gee.HashSet<string>());
+
+ private inline void add_contact(Geary.Contact contact) {
+ if (contact.highest_importance >= CONTACT_VISIBILITY_THRESHOLD) {
+ string full_address = contact.get_rfc822_address().to_rfc822_string();
+ Gtk.TreeIter iter;
+ append(out iter);
+ set(iter,
+ Column.CONTACT_OBJECT, contact,
+ Column.CONTACT_MARKUP_NAME, Markup.escape_text(full_address),
+ Column.PRIOR_KEYS, new Gee.HashSet<string>());
+ }
}
-
+
private void update_contact(Geary.Contact updated_contact) {
Gtk.TreeIter iter;
if (!get_iter_first(out iter))
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]