Bug 1597163 - Cache address book lists and cards; r=mkmelin
authorGeoff Lankow <geoff@darktrojan.net>
Mon, 11 Nov 2019 10:34:12 +1300
changeset 37549 d5118af449e7fbf0f750b3ae045324470cfcd284
parent 37548 f5bd174b047b170cec34c4a4b465efb182fec971
child 37550 97369b9ba582b35700843284b583cb8da5a92845
push id396
push userclokep@gmail.com
push dateMon, 06 Jan 2020 23:11:57 +0000
reviewersmkmelin
bugs1597163
Bug 1597163 - Cache address book lists and cards; r=mkmelin
mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
--- a/mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
+++ b/mailnews/addrbook/jsaddrbook/AddrBookDirectory.jsm
@@ -213,54 +213,79 @@ AddrBookDirectoryInner.prototype = {
       throw Cr.NS_ERROR_NOT_AVAILABLE;
     }
     return Services.prefs.getBranch(`${this.dirPrefId}.`);
   },
   get _dbConnection() {
     let file = FileUtils.getFile("ProfD", [this.fileName]);
     let connection = openConnectionTo(file);
 
-    delete this._inner._dbConnection;
     Object.defineProperty(this._inner, "_dbConnection", {
       enumerable: true,
       value: connection,
       writable: false,
     });
     return connection;
   },
   get _lists() {
+    let listCache = new Map();
     let selectStatement = this._dbConnection.createStatement(
       "SELECT uid, localId, name, nickName, description FROM lists"
     );
-    let results = new Map();
     while (selectStatement.executeStep()) {
-      results.set(selectStatement.row.uid, {
+      listCache.set(selectStatement.row.uid, {
         uid: selectStatement.row.uid,
         localId: selectStatement.row.localId,
         name: selectStatement.row.name,
         nickName: selectStatement.row.nickName,
         description: selectStatement.row.description,
       });
     }
     selectStatement.finalize();
-    return results;
+
+    Object.defineProperty(this._inner, "_lists", {
+      enumerable: true,
+      value: listCache,
+      writable: false,
+    });
+    return listCache;
   },
   get _cards() {
+    let cardCache = new Map();
     let cardStatement = this._dbConnection.createStatement(
       "SELECT uid, localId FROM cards"
     );
-    let results = new Map();
     while (cardStatement.executeStep()) {
-      results.set(cardStatement.row.uid, {
+      cardCache.set(cardStatement.row.uid, {
         uid: cardStatement.row.uid,
         localId: cardStatement.row.localId,
+        properties: new Map(),
       });
     }
     cardStatement.finalize();
-    return results;
+    let propertiesStatement = this._dbConnection.createStatement(
+      "SELECT card, name, value FROM properties"
+    );
+    while (propertiesStatement.executeStep()) {
+      let card = cardCache.get(propertiesStatement.row.card);
+      if (card) {
+        card.properties.set(
+          propertiesStatement.row.name,
+          propertiesStatement.row.value
+        );
+      }
+    }
+    propertiesStatement.finalize();
+
+    Object.defineProperty(this._inner, "_cards", {
+      enumerable: true,
+      value: cardCache,
+      writable: false,
+    });
+    return cardCache;
   },
 
   _getNextCardId() {
     if (this._inner._nextCardId === null) {
       let value = 0;
       let selectStatement = this._dbConnection.createStatement(
         "SELECT MAX(localId) AS localId FROM cards"
       );
@@ -292,79 +317,117 @@ AddrBookDirectoryInner.prototype = {
     let card = new AddrBookCard();
     card.directoryId = this.uuid;
     card._uid = uid;
     card.localId = localId;
     card._properties = this._loadCardProperties(uid);
     return card.QueryInterface(Ci.nsIAbCard);
   },
   _loadCardProperties(uid) {
+    if (this._inner.hasOwnProperty("_cards")) {
+      let cachedCard = this._inner._cards.get(uid);
+      if (cachedCard) {
+        return new Map(cachedCard.properties);
+      }
+    }
     let properties = new Map();
     let propertyStatement = this._dbConnection.createStatement(
       "SELECT name, value FROM properties WHERE card = :card"
     );
     propertyStatement.params.card = uid;
     while (propertyStatement.executeStep()) {
       properties.set(propertyStatement.row.name, propertyStatement.row.value);
     }
     propertyStatement.finalize();
     return properties;
   },
   _saveCardProperties(card) {
+    let cachedCard;
+    if (this._inner.hasOwnProperty("_cards")) {
+      cachedCard = this._inner._cards.get(card.UID);
+      cachedCard.properties.clear();
+    }
+
     this._dbConnection.beginTransaction();
     let deleteStatement = this._dbConnection.createStatement(
       "DELETE FROM properties WHERE card = :card"
     );
     deleteStatement.params.card = card.UID;
     deleteStatement.execute();
     let insertStatement = this._dbConnection.createStatement(
       "INSERT INTO properties VALUES (:card, :name, :value)"
     );
     for (let { name, value } of fixIterator(card.properties, Ci.nsIProperty)) {
       if (value !== null && value !== undefined && value !== "") {
         insertStatement.params.card = card.UID;
         insertStatement.params.name = name;
         insertStatement.params.value = value;
         insertStatement.execute();
         insertStatement.reset();
+
+        if (cachedCard) {
+          cachedCard.properties.set(name, value);
+        }
       }
     }
     this._dbConnection.commitTransaction();
     deleteStatement.finalize();
     insertStatement.finalize();
   },
   _saveList(list) {
+    // Ensure list cache exists.
+    this._lists;
+
     let replaceStatement = this._dbConnection.createStatement(
       "REPLACE INTO lists (uid, localId, name, nickName, description) " +
         "VALUES (:uid, :localId, :name, :nickName, :description)"
     );
     replaceStatement.params.uid = list._uid;
     replaceStatement.params.localId = list._localId;
     replaceStatement.params.name = list._name;
     replaceStatement.params.nickName = list._nickName;
     replaceStatement.params.description = list._description;
     replaceStatement.execute();
     replaceStatement.finalize();
+
+    this._lists.set(list._uid, {
+      uid: list._uid,
+      localId: list._localId,
+      name: list._name,
+      nickName: list._nickName,
+      description: list._description,
+    });
   },
   async _bulkAddCards(cards) {
     let cardStatement = this._dbConnection.createStatement(
       "INSERT INTO cards (uid, localId) VALUES (:uid, :localId)"
     );
     let propertiesStatement = this._dbConnection.createStatement(
       "INSERT INTO properties VALUES (:card, :name, :value)"
     );
     let cardArray = cardStatement.newBindingParamsArray();
     let propertiesArray = propertiesStatement.newBindingParamsArray();
     for (let card of cards) {
       let uid = card.UID || newUID();
+      let localId = this._getNextCardId();
       let cardParams = cardArray.newBindingParams();
       cardParams.bindByName("uid", uid);
-      cardParams.bindByName("localId", this._getNextCardId());
+      cardParams.bindByName("localId", localId);
       cardArray.addParams(cardParams);
 
+      let cachedCard;
+      if (this._inner.hasOwnProperty("_cards")) {
+        cachedCard = {
+          uid,
+          localId,
+          properties: new Map(),
+        };
+        this._inner._cards.set(uid, cachedCard);
+      }
+
       for (let { name, value } of fixIterator(
         card.properties,
         Ci.nsIProperty
       )) {
         if (
           [
             "DbRowID",
             "LowercasePrimaryEmail",
@@ -375,16 +438,20 @@ AddrBookDirectoryInner.prototype = {
         ) {
           continue;
         }
         let propertiesParams = propertiesArray.newBindingParams();
         propertiesParams.bindByName("card", uid);
         propertiesParams.bindByName("name", name);
         propertiesParams.bindByName("value", value);
         propertiesArray.addParams(propertiesParams);
+
+        if (cachedCard) {
+          cachedCard.properties.set(name, value);
+        }
       }
     }
     cardStatement.bindParameters(cardArray);
     await new Promise(resolve => {
       cardStatement.executeAsync({
         handleError(error) {
           Cu.reportError(error);
         },
@@ -663,16 +730,20 @@ AddrBookDirectoryInner.prototype = {
 
     let deleteListStatement = this._dbConnection.createStatement(
       "DELETE FROM lists WHERE uid = :uid"
     );
     deleteListStatement.params.uid = directory.UID;
     deleteListStatement.execute();
     deleteListStatement.finalize();
 
+    if (this._inner.hasOwnProperty("_lists")) {
+      this._inner._lists.delete(directory.UID);
+    }
+
     this._dbConnection.executeSimpleSQL(
       "DELETE FROM list_cards WHERE list NOT IN (SELECT DISTINCT uid FROM lists)"
     );
     MailServices.ab.notifyDirectoryItemDeleted(this, list.asCard);
     MailServices.ab.notifyDirectoryItemDeleted(list.asDirectory, list.asCard);
     MailServices.ab.notifyDirectoryDeleted(this, directory);
   },
   hasCard(card) {
@@ -720,16 +791,20 @@ AddrBookDirectoryInner.prototype = {
 
     let deleteCardStatement = this._dbConnection.createStatement(
       "DELETE FROM cards WHERE uid = :uid"
     );
     for (let card of cards.enumerate(Ci.nsIAbCard)) {
       deleteCardStatement.params.uid = card.UID;
       deleteCardStatement.execute();
       deleteCardStatement.reset();
+
+      if (this._inner.hasOwnProperty("_cards")) {
+        this._inner._cards.delete(card.UID);
+      }
     }
     this._dbConnection.executeSimpleSQL(
       "DELETE FROM properties WHERE card NOT IN (SELECT DISTINCT uid FROM cards)"
     );
     for (let card of cards.enumerate(Ci.nsIAbCard)) {
       MailServices.ab.notifyDirectoryItemDeleted(this, card);
     }
 
@@ -750,16 +825,24 @@ AddrBookDirectoryInner.prototype = {
     let insertStatement = this._dbConnection.createStatement(
       "INSERT INTO cards (uid, localId) VALUES (:uid, :localId)"
     );
     insertStatement.params.uid = newCard.UID;
     insertStatement.params.localId = newCard.localId;
     insertStatement.execute();
     insertStatement.finalize();
 
+    if (this._inner.hasOwnProperty("_cards")) {
+      this._inner._cards.set(newCard._uid, {
+        uid: newCard._uid,
+        localId: newCard.localId,
+        properties: new Map(),
+      });
+    }
+
     for (let { name, value } of fixIterator(card.properties, Ci.nsIProperty)) {
       if (
         [
           "DbRowID",
           "LowercasePrimaryEmail",
           "LowercaseSecondEmail",
           "RecordKey",
           "UID",