From 5339a6cbfc70dc09f5f9696b03f63bce80f0628f Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Thu, 14 Mar 2019 21:41:57 +1100 Subject: [PATCH] Add client Contact and ContactStore classes These simply wrap the Engine equivalents for the moment, but will also incorporate Folks data as well. Construct a per-account store and add it to the controller's account context object. --- po/POTFILES.in | 2 + .../application-contact-store.vala | 40 +++++++++ .../application/application-contact.vala | 88 +++++++++++++++++++ src/client/application/geary-controller.vala | 37 +++++--- src/client/meson.build | 2 + 5 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 src/client/application/application-contact-store.vala create mode 100644 src/client/application/application-contact.vala diff --git a/po/POTFILES.in b/po/POTFILES.in index 9cc6ea67..74689c48 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -18,6 +18,8 @@ src/client/accounts/accounts-signature-web-view.vala src/client/application/application-avatar-store.vala src/client/application/application-certificate-manager.vala src/client/application/application-command.vala +src/client/application/application-contact-store.vala +src/client/application/application-contact.vala src/client/application/autostart-manager.vala src/client/application/geary-application.vala src/client/application/geary-args.vala diff --git a/src/client/application/application-contact-store.vala b/src/client/application/application-contact-store.vala new file mode 100644 index 00000000..c6df7b76 --- /dev/null +++ b/src/client/application/application-contact-store.vala @@ -0,0 +1,40 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A source of contacts for an account. + * + * This class aggregates data from for both the Engine and Folks, + * allowing contacts for a specific account to be queried. + */ +public class Application.ContactStore { + + + /** The account this store aggregates data for. */ + public Geary.Account account { get; private set; } + + + /** Constructs a new contact store for an account. */ + public ContactStore(Geary.Account account) { + this.account = account; + } + + /** + * Returns a contact for a specific mailbox. + * + * Returns a contact that has the given mailbox address listed as + * a primary or secondary email. A contact will always be + * returned, so if no matching contact already exists a new, + * non-persistent contact will be returned. + */ + public Contact get(Geary.RFC822.MailboxAddress mailbox) { + Geary.Contact? contact = + this.account.get_contact_store().get_by_rfc822(mailbox); + return new Contact(this, contact, mailbox); + } + +} diff --git a/src/client/application/application-contact.vala b/src/client/application/application-contact.vala new file mode 100644 index 00000000..c325a062 --- /dev/null +++ b/src/client/application/application-contact.vala @@ -0,0 +1,88 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * Contact information for an individual. + * + * This class aggregates data from for both the Engine and Folks, + * allowing contacts information for a specific mailbox to be + * queried. Contacts are obtained from the {@link ContactStore} for an + * account. + */ +public class Application.Contact { + + + /** The human-readable name of the contact. */ + public string display_name { get; private set; } + + /** Determines if {@link display_name} is trusted by the user. */ + public bool display_name_is_trusted { get; private set; default = false; } + + /** Determines if {@link display_name} the same as its email address. */ + public bool display_name_is_email { get; private set; default = false; } + + /** Determines if email from this contact should load remote resources. */ + public bool load_remote_resources { + get { + return ( + this.contact != null && + this.contact.contact_flags.always_load_remote_images() + ); + } + } + + private weak ContactStore store; + private Geary.Contact? contact; + + + internal Contact(ContactStore store, + Geary.Contact? contact, + Geary.RFC822.MailboxAddress source) { + this.store = store; + this.contact = contact; + + if (contact != null) { + this.display_name = contact.real_name; + } else { + this.display_name = source.name; + } + + // Use the email address as the display name if the existing + // display name looks in any way sketchy, regardless of where + // it came from + if (source.is_spoofed() || + Geary.String.is_empty_or_whitespace(this.display_name) || + Geary.RFC822.MailboxAddress.is_valid_address(this.display_name)) { + this.display_name = source.address; + this.display_name_is_email = true; + } + } + + /** Sets remote resource loading for this contact. */ + public async void set_remote_resource_loading(bool enabled, + GLib.Cancellable? cancellable) + throws GLib.Error { + ContactStore? store = this.store; + if (store != null && this.contact != null) { + Geary.ContactFlags flags = new Geary.ContactFlags(); + flags.add(Geary.ContactFlags.ALWAYS_LOAD_REMOTE_IMAGES); + + yield store.account.get_contact_store().mark_contacts_async( + Geary.Collection.single(this.contact), + enabled ? flags : null, + !enabled ? flags : null //, + // XXX cancellable + ); + } + } + + /** Returns a string representation for debugging */ + public string to_string() { + return "Contact(\"%s\")".printf(this.display_name); + } + +} diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index d2594d79..c24c940d 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -63,7 +63,8 @@ public class GearyController : Geary.BaseObject { public Geary.Account account { get; private set; } public Geary.Folder? inbox = null; - public Geary.App.EmailStore store { get; private set; } + public Geary.App.EmailStore emails { get; private set; } + public Application.ContactStore contacts { get; private set; } public bool authentication_failed = false; public bool authentication_prompting = false; @@ -74,9 +75,12 @@ public class GearyController : Geary.BaseObject { public Cancellable cancellable { get; private set; default = new Cancellable(); } - public AccountContext(Geary.Account account) { + public AccountContext(Geary.Account account, + Geary.App.EmailStore emails, + Application.ContactStore contacts) { this.account = account; - this.store = new Geary.App.EmailStore(account); + this.emails = emails; + this.contacts = contacts; } public Geary.Account.Status get_effective_status() { @@ -908,7 +912,11 @@ public class GearyController : Geary.BaseObject { } private async void connect_account_async(Geary.Account account, Cancellable? cancellable = null) { - AccountContext context = new AccountContext(account); + AccountContext context = new AccountContext( + account, + new Geary.App.EmailStore(account), + new Application.ContactStore(account) + ); // XXX Need to set this early since // on_folders_available_unavailable expects it to be there @@ -1314,8 +1322,9 @@ public class GearyController : Geary.BaseObject { Geary.App.Conversation convo = Geary.Collection.get_first( selected ); - Geary.App.EmailStore? store = get_store_for_folder( - convo.base_folder + + AccountContext? context = this.accounts.get( + convo.base_folder.account.information ); // It's possible for a conversation with zero email to @@ -1323,10 +1332,10 @@ public class GearyController : Geary.BaseObject { // last email was removed but the conversation monitor // hasn't signalled its removal yet. In this case, // just don't load it since it will soon disappear. - if (store != null && convo.get_count() > 0) { + if (context != null && convo.get_count() > 0) { viewer.load_conversation.begin( convo, - store, + context.emails, (obj, ret) => { try { viewer.load_conversation.end(ret); @@ -1615,7 +1624,7 @@ public class GearyController : Geary.BaseObject { private void mark_email(Gee.Collection ids, Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove) { if (ids.size > 0) { - Geary.App.EmailStore? store = get_store_for_folder(current_folder); + Geary.App.EmailStore? store = get_email_store_for_folder(current_folder); if (store != null) { store.mark_email_async.begin( ids, flags_to_add, flags_to_remove, cancellable_folder @@ -1787,7 +1796,7 @@ public class GearyController : Geary.BaseObject { private void copy_email(Gee.Collection ids, Geary.FolderPath destination) { if (ids.size > 0) { - Geary.App.EmailStore? store = get_store_for_folder(current_folder); + Geary.App.EmailStore? store = get_email_store_for_folder(current_folder); if (store != null) { store.copy_email_async.begin( ids, destination, cancellable_folder @@ -2163,7 +2172,7 @@ public class GearyController : Geary.BaseObject { // Load the widget's content Geary.Email? full = null; if (referred != null) { - Geary.App.EmailStore? store = get_store_for_folder(current_folder); + Geary.App.EmailStore? store = get_email_store_for_folder(current_folder); if (store != null) { try { full = yield store.fetch_email_async( @@ -2734,7 +2743,7 @@ public class GearyController : Geary.BaseObject { Gee.MultiMap? selected_operations = null; try { if (current_folder != null) { - Geary.App.EmailStore? store = get_store_for_folder(current_folder); + Geary.App.EmailStore? store = get_email_store_for_folder(current_folder); if (store != null) { selected_operations = yield store .get_supported_operations_async(get_selected_email_ids(false), cancellable); @@ -2814,9 +2823,9 @@ public class GearyController : Geary.BaseObject { return selected_conversations.read_only_view; } - private inline Geary.App.EmailStore? get_store_for_folder(Geary.Folder target) { + private inline Geary.App.EmailStore? get_email_store_for_folder(Geary.Folder target) { AccountContext? context = this.accounts.get(target.account.information); - return context != null ? context.store : null; + return context != null ? context.emails : null; } private bool should_add_folder(Gee.Collection? all, diff --git a/src/client/meson.build b/src/client/meson.build index 77db6b1a..1f3f15a7 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -3,6 +3,8 @@ geary_client_vala_sources = files( 'application/application-avatar-store.vala', 'application/application-certificate-manager.vala', 'application/application-command.vala', + 'application/application-contact-store.vala', + 'application/application-contact.vala', 'application/autostart-manager.vala', 'application/geary-application.vala', 'application/geary-args.vala',