[geary/wip/composer-folks: 18/22] Decouple Application.Contact from Geary.Contact



commit 7c4d71131ecd9deb85c11d43ea9f75989a138e2b
Author: Michael Gratton <mike vee net>
Date:   Sat Jun 15 16:42:23 2019 +1000

    Decouple Application.Contact from Geary.Contact
    
    Update Application.Contact to have specific constructors for both
    Folks-based and engine-based construction, remove the requirement for
    the class to have a Geary.Contact field, and add a set of email
    addresses as a property so that things like setting remote resource
    loading prefs still works. This allows application contact to be
    constructed from a Folks individual directly, without having to also
    load an engine contact.
    
    Update Application.ContactStore to cache Geary.Contact instances now
    that they are always loaded from the database, rework loading code to
    take advantage of the changes above.

 .../application/application-contact-store.vala     |  89 ++++++++++++---
 src/client/application/application-contact.vala    | 126 ++++++++++++++-------
 2 files changed, 155 insertions(+), 60 deletions(-)
---
diff --git a/src/client/application/application-contact-store.vala 
b/src/client/application/application-contact-store.vala
index 7d029610..9e9a97c8 100644
--- a/src/client/application/application-contact-store.vala
+++ b/src/client/application/application-contact-store.vala
@@ -21,6 +21,11 @@ public class Application.ContactStore : Geary.BaseObject {
     private const uint LRU_CACHE_MAX = 128;
 
 
+    private static inline string to_cache_key(string value) {
+        return value.normalize().casefold();
+    }
+
+
     /** The account this store aggregates data for. */
     public Geary.Account account { get; private set; }
 
@@ -32,8 +37,12 @@ public class Application.ContactStore : Geary.BaseObject {
         new Util.Cache.Lru<Folks.Individual?>(LRU_CACHE_MAX);
 
     // Cache for contacts backed by Folks individual's id.
-    private Util.Cache.Lru<Contact?> contact_id_cache =
-        new Util.Cache.Lru<Contact?>(LRU_CACHE_MAX);
+    private Util.Cache.Lru<Contact> contact_id_cache =
+        new Util.Cache.Lru<Contact>(LRU_CACHE_MAX);
+
+    // Cache for engine contacts by email address.
+    private Util.Cache.Lru<Geary.Contact> engine_address_cache =
+        new Util.Cache.Lru<Geary.Contact>(LRU_CACHE_MAX);
 
 
     /** Constructs a new contact store for an account. */
@@ -56,6 +65,7 @@ public class Application.ContactStore : Geary.BaseObject {
     public void close() {
         this.folks_address_cache.clear();
         this.contact_id_cache.clear();
+        this.engine_address_cache.clear();
     }
 
     /**
@@ -73,32 +83,77 @@ public class Application.ContactStore : Geary.BaseObject {
         // Do a double lookup here in case of cache hit since null
         // values are used to be able to cache Folks lookup failures
         // (as well as successes).
-        if (this.folks_address_cache.has_key(mailbox.address)) {
-            individual = this.folks_address_cache.get_entry(mailbox.address);
+        string email_key = to_cache_key(mailbox.address);
+        if (this.folks_address_cache.has_key(email_key)) {
+            individual = this.folks_address_cache.get_entry(email_key);
         } else {
-            individual = yield search_match(mailbox.address, cancellable);
-            this.folks_address_cache.set_entry(mailbox.address, individual);
+            individual = yield search_folks_by_email(
+                mailbox.address, cancellable
+            );
+            this.folks_address_cache.set_entry(email_key, individual);
         }
 
-        Contact? contact = null;
-        if (individual != null) {
-            this.contact_id_cache.get_entry(individual.id);
-        }
+        return yield get_contact(individual, mailbox, cancellable);
+    }
+
+    internal async Geary.Contact
+        lookup_engine_contact(Geary.RFC822.MailboxAddress mailbox,
+                              GLib.Cancellable cancellable)
+        throws GLib.Error {
+        string email_key = to_cache_key(mailbox.address);
+        Geary.Contact contact = this.engine_address_cache.get_entry(email_key);
         if (contact == null) {
-            Geary.Contact? engine =
-                yield this.account.contact_store.get_by_rfc822(
-                    mailbox, cancellable
+            contact = yield this.account.contact_store.get_by_rfc822(
+                mailbox, cancellable
+            );
+            if (contact == null) {
+                contact = new Geary.Contact.from_rfc822_address(mailbox, 0);
+                yield this.account.contact_store.update_contacts(
+                    Geary.Collection.single(contact), cancellable
                 );
-            contact = new Contact(this, individual, engine, mailbox);
-            if (individual != null) {
+            }
+            this.engine_address_cache.set_entry(email_key, contact);
+        }
+        return contact;
+    }
+
+    private async Contact get_contact(Folks.Individual? individual,
+                                      Geary.RFC822.MailboxAddress? mailbox,
+                                      GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        Contact? contact = null;
+        if (individual != null) {
+            contact = this.contact_id_cache.get_entry(individual.id);
+            if (contact == null) {
+                contact = new Contact.for_folks(this, individual);
                 this.contact_id_cache.set_entry(individual.id, contact);
             }
+        } else if (mailbox != null) {
+            Geary.Contact engine = yield lookup_engine_contact(
+                mailbox, cancellable
+            );
+            // Despite the engine's contact having a display name, use
+            // the one from the source mailbox: Online services like
+            // Gitlab will use the sender's name but the service's
+            // email address, so the name from the engine probably
+            // won't match the name on the email
+            string name = (!Geary.String.is_empty_or_whitespace(mailbox.name) &&
+                           !mailbox.is_spoofed())
+               ? mailbox.name
+               : mailbox.mailbox;
+            contact = new Contact.for_engine(
+                this, name, engine
+            );
+        } else {
+            throw new Geary.EngineError.BAD_PARAMETERS(
+                "Requires either an individual or a mailbox"
+            );
         }
         return contact;
     }
 
-    private async Folks.Individual? search_match(string address,
-                                                 GLib.Cancellable cancellable)
+    private async Folks.Individual? search_folks_by_email(string address,
+                                                          GLib.Cancellable cancellable)
         throws GLib.Error {
         Folks.SearchView view = new Folks.SearchView(
             this.individuals,
diff --git a/src/client/application/application-contact.vala b/src/client/application/application-contact.vala
index ef597209..fa2389d5 100644
--- a/src/client/application/application-contact.vala
+++ b/src/client/application/application-contact.vala
@@ -44,15 +44,27 @@ public class Application.Contact : Geary.BaseObject {
      * Will automatically load resources from contacts in the desktop
      * database, or if the Engine's contact has been flagged to do so.
      */
-    public bool load_remote_resources {
+    public bool load_remote_resources { get; private set; }
+
+    /** The set of email addresses associated with this contact. */
+    public Gee.Collection<Geary.RFC822.MailboxAddress> email_addresses {
         get {
-            return (
-                this.individual != null ||
-                (this.contact != null &&
-                 this.contact.flags.always_load_remote_images())
-            );
+            Gee.Collection<Geary.RFC822.MailboxAddress>? addrs =
+                this._email_addresses;
+            if (addrs == null) {
+                addrs = new Gee.LinkedList<Geary.RFC822.MailboxAddress>();
+                foreach (Folks.EmailFieldDetails email in
+                         this.individual.email_addresses) {
+                    addrs.add(new Geary.RFC822.MailboxAddress(
+                                  this.display_name, email.value
+                              ));
+                }
+                this._email_addresses = addrs;
+            }
+            return this._email_addresses;
         }
     }
+    private Gee.Collection<Geary.RFC822.MailboxAddress>? _email_addresses = null;
 
 
     /** Fired when the contact has changed in some way. */
@@ -63,31 +75,27 @@ public class Application.Contact : Geary.BaseObject {
     internal Folks.Individual? individual { get; private set; }
 
     private weak ContactStore store;
-    private Geary.Contact? contact;
 
 
-    internal Contact(ContactStore store,
-                     Folks.Individual? individual,
-                     Geary.Contact? contact,
-                     Geary.RFC822.MailboxAddress source) {
+    private Contact(ContactStore store, Folks.Individual? source) {
         this.store = store;
-        this.contact = contact;
-        update_individual(individual);
-
+        update_individual(source);
         update();
-        if (Geary.String.is_empty_or_whitespace(this.display_name)) {
-            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;
-        }
+    internal Contact.for_folks(ContactStore store,
+                               Folks.Individual? source) {
+        this(store, source);
+    }
+
+    internal Contact.for_engine(ContactStore store,
+                                string display_name,
+                                Geary.Contact source) {
+        this(store, null);
+        Geary.RFC822.MailboxAddress mailbox = source.get_rfc822_address();
+        update_name(display_name);
+        this._email_addresses = Geary.Collection.single(mailbox);
+        this.load_remote_resources = source.flags.always_load_remote_images();
     }
 
     ~Contact() {
@@ -115,14 +123,28 @@ public class Application.Contact : Geary.BaseObject {
                 other.individual != null &&
                 this.individual.id == other.individual.id
             );
-        } else if (this.contact != null) {
-            return (
-                other.contact != null &&
-                this.contact.email == other.contact.email
-            );
         }
 
-        return (this.display_name == other.display_name);
+        if (this.display_name != other.display_name ||
+            this.email_addresses.size != other.email_addresses.size) {
+            return false;
+        }
+
+        foreach (Geary.RFC822.MailboxAddress this_addr in this.email_addresses) {
+            bool found = false;
+            foreach (Geary.RFC822.MailboxAddress other_addr
+                     in other.email_addresses) {
+                if (this_addr.equal_to(other_addr)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+
+        return true;
     }
 
     /** Invokes the desktop contacts application to save this contact. */
@@ -151,7 +173,7 @@ public class Application.Contact : Geary.BaseObject {
                         Folks.PersonaStore.detail_key(
                             Folks.PersonaDetail.EMAIL_ADDRESSES
                         ),
-                        this.contact.email
+                        Geary.Collection.get_first(this.email_addresses).address
                     }
                 )
             }
@@ -181,20 +203,30 @@ public class Application.Contact : Geary.BaseObject {
                                                   GLib.Cancellable? cancellable)
         throws GLib.Error {
         ContactStore? store = this.store;
-        if (store != null && this.contact != null) {
-            if (enabled) {
-                this.contact.flags.add(
-                    Geary.Contact.Flags.ALWAYS_LOAD_REMOTE_IMAGES
-                );
-            } else {
-                this.contact.flags.remove(
-                    Geary.Contact.Flags.ALWAYS_LOAD_REMOTE_IMAGES
+        if (store != null) {
+            Gee.Collection<Geary.Contact> contacts =
+                new Gee.LinkedList<Geary.Contact>();
+            foreach (Geary.RFC822.MailboxAddress mailbox in this.email_addresses) {
+                Geary.Contact? contact = yield store.lookup_engine_contact(
+                    mailbox, cancellable
                 );
+                if (enabled) {
+                    contact.flags.add(
+                        Geary.Contact.Flags.ALWAYS_LOAD_REMOTE_IMAGES
+                    );
+                } else {
+                    contact.flags.remove(
+                        Geary.Contact.Flags.ALWAYS_LOAD_REMOTE_IMAGES
+                    );
+                }
+                contacts.add(contact);
             }
 
             yield store.account.contact_store.update_contacts(
-                Geary.Collection.single(this.contact), cancellable
+                contacts, cancellable
             );
+
+            this.load_remote_resources = enabled;
         }
 
         changed();
@@ -212,6 +244,12 @@ public class Application.Contact : Geary.BaseObject {
         return "Contact(\"%s\")".printf(this.display_name);
     }
 
+    private void update_name(string name) {
+        this.display_name = name;
+        this.display_name_is_email =
+            Geary.RFC822.MailboxAddress.is_valid_address(name);
+    }
+
     private void update_individual(Folks.Individual? replacement) {
         if (this.individual != null) {
             this.individual.notify.disconnect(this.on_individual_notify);
@@ -228,14 +266,16 @@ public class Application.Contact : Geary.BaseObject {
 
     private void update() {
         if (this.individual != null) {
-            this.display_name = this.individual.display_name;
+            update_name(this.individual.display_name);
             this.is_favourite = this.individual.is_favourite;
             this.is_trusted = (this.individual.trust_level == PERSONAS);
             this.is_desktop_contact = true;
+            this.load_remote_resources = true;
         } else {
             this.is_favourite = false;
             this.is_trusted = false;
             this.is_desktop_contact = false;
+            this.load_remote_resources = false;
         }
     }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]