[geary/wip/135-contact-popovers: 50/56] Use LRU cache ContactStore for caching Contact objects and lookups



commit ef62d95d674e1d92f500b5ed5cc148bedf04e59f
Author: Michael Gratton <mike vee net>
Date:   Sun Mar 17 13:49:06 2019 +1100

    Use LRU cache ContactStore for caching Contact objects and lookups
    
    Cache by both email and Folks id. Listen to the Folks individual
    aggregator and evict entries when Folks indicates they have changed.

 .../application/application-contact-store.vala     | 78 +++++++++++++++++++---
 1 file changed, 69 insertions(+), 9 deletions(-)
---
diff --git a/src/client/application/application-contact-store.vala 
b/src/client/application/application-contact-store.vala
index 59ae9108..c4fc75b0 100644
--- a/src/client/application/application-contact-store.vala
+++ b/src/client/application/application-contact-store.vala
@@ -6,25 +6,58 @@
  */
 
 /**
- * A source of contacts for an account.
+ * Contacts loader and cache for an account.
  *
- * This class aggregates data from for both the Engine and Folks,
- * allowing contacts for a specific account to be queried.
+ * This class aggregates contact information from Folks, the Engine,
+ * and the source mailbox. It allows contacts for a specific account
+ * to be obtained, and uses caches to minimise performance impact of
+ * re-using recently used contacts.
  */
 public class Application.ContactStore : Geary.BaseObject {
 
 
+    // Keep caches small to keep lookups fast and memory overhead
+    // low. Conversations are rarely more than in the 100's anyway.
+    private const uint LRU_CACHE_MAX = 128;
+
+
     /** The account this store aggregates data for. */
     public Geary.Account account { get; private set; }
 
     internal Folks.IndividualAggregator individuals;
 
+    // Address for storing contacts for a specific email address.
+    // This primary cache is used to fast-path common-case lookups.
+    private Util.Cache.Lru<Contact> address_cache =
+        new Util.Cache.Lru<Contact>(LRU_CACHE_MAX);
+
+    // Folks cache used for storing contacts backed by a Folk
+    // individual. This secondary cache is used in case an individual
+    // has more than one email address.
+    private Util.Cache.Lru<Contact> folks_cache =
+        new Util.Cache.Lru<Contact>(LRU_CACHE_MAX);
+
 
     /** Constructs a new contact store for an account. */
     public ContactStore(Geary.Account account,
                         Folks.IndividualAggregator individuals) {
         this.account = account;
         this.individuals = individuals;
+        this.individuals.individuals_changed_detailed.connect(
+            on_individuals_changed
+        );
+    }
+
+    ~ContactStore() {
+        this.individuals.individuals_changed_detailed.disconnect(
+            on_individuals_changed
+        );
+    }
+
+    /** Closes the store, flushing all caches. */
+    public void close() {
+        this.address_cache.clear();
+        this.folks_cache.clear();
     }
 
     /**
@@ -38,12 +71,26 @@ public class Application.ContactStore : Geary.BaseObject {
     public async Contact load(Geary.RFC822.MailboxAddress mailbox,
                               GLib.Cancellable? cancellable)
         throws GLib.Error {
-        Folks.Individual? individual = yield search_match(
-            mailbox.address, cancellable
-        );
-        Geary.Contact? contact =
-            this.account.get_contact_store().get_by_rfc822(mailbox);
-        return new Contact(this, individual, contact, mailbox);
+        Contact? contact = this.address_cache.get_entry(mailbox.address);
+        if (contact == null) {
+            Folks.Individual? individual = yield search_match(
+                mailbox.address, cancellable
+            );
+            if (individual != null) {
+                contact = this.folks_cache.get_entry(individual.id);
+            }
+
+            if (contact == null) {
+                Geary.Contact? engine =
+                    this.account.get_contact_store().get_by_rfc822(mailbox);
+                contact = new Contact(this, individual, engine, mailbox);
+                if (individual != null) {
+                    this.folks_cache.set_entry(individual.id, contact);
+                }
+                this.address_cache.set_entry(mailbox.address, contact);
+            }
+        }
+        return contact;
     }
 
     private async Folks.Individual? search_match(string address,
@@ -80,4 +127,17 @@ public class Application.ContactStore : Geary.BaseObject {
         return match;
     }
 
+    private void on_individuals_changed(
+        Gee.MultiMap<Folks.Individual?,Folks.Individual?> changes) {
+        foreach (Folks.Individual? individual in changes.get_keys()) {
+            if (individual != null) {
+                this.folks_cache.remove_entry(individual.id);
+                foreach (Folks.EmailFieldDetails email in
+                         individual.email_addresses) {
+                    this.address_cache.remove_entry(email.value);
+                }
+            }
+        }
+    }
+
 }


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