Bug 844274 - Move contacts API getAll cache to child process. r=sicking

This commit is contained in:
Reuben Morais 2013-02-24 19:53:55 -08:00
parent 4f80dbdbcf
commit c4d09269f1
4 changed files with 95 additions and 131 deletions

View File

@ -390,6 +390,14 @@ ContactManager.prototype = {
return contacts;
},
_fireSuccessOrDone: function(aCursor, aResult) {
if (aResult == null) {
Services.DOMRequest.fireDone(aCursor);
} else {
Services.DOMRequest.fireSuccess(aCursor, aResult);
}
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: " + aMessage.name);
let msg = aMessage.json;
@ -407,12 +415,15 @@ ContactManager.prototype = {
}
break;
case "Contacts:GetAll:Next":
let cursor = this._cursorData[msg.cursorId];
let data = this._cursorData[msg.cursorId];
let contact = msg.contact ? this._convertContact(msg.contact) : null;
if (contact == null) {
Services.DOMRequest.fireDone(cursor);
if (data.waitingForNext) {
if (DEBUG) debug("cursor waiting for contact, sending");
data.waitingForNext = false;
this._fireSuccessOrDone(data.cursor, contact);
} else {
Services.DOMRequest.fireSuccess(cursor, contact);
if (DEBUG) debug("cursor not waiting, saving");
data.cachedContacts.push(contact);
}
break;
case "Contacts:GetSimContacts:Return:OK":
@ -591,12 +602,16 @@ ContactManager.prototype = {
createCursor: function CM_createCursor(aRequest) {
let id = this._getRandomId();
let cursor = Services.DOMRequest.createCursor(this._window, function() {
this.handleContinue(id);
}.bind(this));
let data = {
cursor: Services.DOMRequest.createCursor(this._window, function() {
this.handleContinue(id);
}.bind(this)),
cachedContacts: [],
waitingForNext: true,
};
if (DEBUG) debug("saved cursor id: " + id);
this._cursorData[id] = cursor;
return [id, cursor];
this._cursorData[id] = data;
return [id, data.cursor];
},
getAll: function CM_getAll(aOptions) {
@ -612,9 +627,14 @@ ContactManager.prototype = {
handleContinue: function CM_handleContinue(aCursorId) {
if (DEBUG) debug("handleContinue: " + aCursorId);
cpmm.sendAsyncMessage("Contacts:GetAll:Continue", {
cursorId: aCursorId
});
let data = this._cursorData[aCursorId];
if (data.cachedContacts.length > 0) {
if (DEBUG) debug("contact in cache");
this._fireSuccessOrDone(data.cursor, data.cachedContacts.shift());
} else {
if (DEBUG) debug("waiting for contact");
data.waitingForNext = true;
}
},
remove: function removeContact(aRecord) {

View File

@ -30,8 +30,6 @@ this.ContactDB = function ContactDB(aGlobal) {
ContactDB.prototype = {
__proto__: IndexedDBHelper.prototype,
cursorData: {},
upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
if (DEBUG) debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
let db = aDb;
@ -472,82 +470,81 @@ ContactDB.prototype = {
}, aSuccessCb, aErrorCb);
},
getObjectById: function CDB_getObjectById(aStore, aObjectId, aCallback) {
if (DEBUG) debug("getObjectById: " + aStore + ":" + aObjectId);
this.newTxn("readonly", aStore, function (txn, store) {
let req = store.get(aObjectId);
req.onsuccess = function (event) {
aCallback(event.target.result);
};
req.onerror = function (event) {
aCallback(null);
};
});
},
getCacheForQuery: function CDB_getCacheForQuery(aQuery, aCursorId, aSuccessCb) {
if (DEBUG) debug("getCacheForQuery");
// Here we try to get the cached results for query `aQuery'. If they don't
// exist, it means the cache was invalidated and needs to be recreated, so
// we do that. Otherwise, we just return the existing cache.
this.getObjectById(SAVED_GETALL_STORE_NAME, aQuery, function (aCache) {
if (!aCache) {
if (DEBUG) debug("creating cache for query " + aQuery);
this.createCacheForQuery(aQuery, aCursorId, aSuccessCb);
} else {
if (DEBUG) debug("cache exists");
if (!this.cursorData[aCursorId]) {
this.cursorData[aCursorId] = aCache;
}
aSuccessCb(aCache);
}
}.bind(this));
},
setCacheForQuery: function CDB_setCacheForQuery(aQuery, aCache, aCallback) {
this.newTxn("readwrite", SAVED_GETALL_STORE_NAME, function (txn, store) {
let req = store.put(aCache, aQuery);
if (!aCallback) {
return;
}
req.onsuccess = function () {
aCallback(true);
};
req.onerror = function () {
aCallback(false);
};
});
},
createCacheForQuery: function CDB_createCacheForQuery(aQuery, aCursorId, aSuccessCb, aFailureCb) {
createCacheForQuery: function CDB_createCacheForQuery(aQuery, aSuccessCb, aFailureCb) {
this.find(function (aContacts) {
if (aContacts) {
let contactsArray = [];
for (let i in aContacts) {
contactsArray.push(aContacts[i].id);
contactsArray.push(aContacts[i]);
}
this.setCacheForQuery(aQuery, contactsArray);
this.cursorData[aCursorId] = contactsArray;
aSuccessCb(contactsArray);
// save contact ids in cache
this.newTxn("readwrite", SAVED_GETALL_STORE_NAME, function(txn, store) {
store.put(contactsArray.map(function(el) el.id), aQuery);
});
// send full contacts
aSuccessCb(contactsArray, true);
} else {
aSuccessCb(null);
aSuccessCb([], true);
}
}.bind(this),
function (aErrorMsg) { aFailureCb(aErrorMsg); },
JSON.parse(aQuery));
},
getAll: function CDB_getAll(aSuccessCb, aFailureCb, aOptions, aCursorId) {
// Recreate the cache for this query if needed
getCacheForQuery: function CDB_getCacheForQuery(aQuery, aSuccessCb) {
if (DEBUG) debug("getCacheForQuery");
// Here we try to get the cached results for query `aQuery'. If they don't
// exist, it means the cache was invalidated and needs to be recreated, so
// we do that. Otherwise, we just return the existing cache.
this.newTxn("readonly", SAVED_GETALL_STORE_NAME, function(txn, store) {
let req = store.get(aQuery);
req.onsuccess = function(e) {
if (e.target.result) {
if (DEBUG) debug("cache exists");
debug("sending: " + JSON.stringify(e.target.result));
aSuccessCb(e.target.result, false);
} else {
if (DEBUG) debug("creating cache for query " + aQuery);
this.createCacheForQuery(aQuery, aSuccessCb);
}
}.bind(this);
req.onerror = function() {
};
}.bind(this));
},
getAll: function CDB_getAll(aSuccessCb, aFailureCb, aOptions) {
let optionStr = JSON.stringify(aOptions);
this.getCacheForQuery(optionStr, aCursorId, function (aCachedResults) {
this.getCacheForQuery(optionStr, function(aCachedResults, aFullContacts) {
// aFullContacts is true if the cache didn't exist and had to be created.
// In that case, we receive the full contacts since we already have them
// in memory to create the cache anyway. This allows us to avoid accessing
// the main object store again.
if (aCachedResults && aCachedResults.length > 0) {
if (DEBUG) debug("query returned at least one contact");
this.getObjectById(STORE_NAME, aCachedResults[0], function (aContact) {
this.cursorData[aCursorId].shift();
aSuccessCb(aContact);
}.bind(this));
if (aFullContacts) {
if (DEBUG) debug("full contacts: " + aCachedResults.length);
for (let i = 0; i < aCachedResults.length; ++i) {
aSuccessCb(aCachedResults[i]);
}
aSuccessCb(null);
} else {
let count = 0;
this.newTxn("readonly", STORE_NAME, function(txn, store) {
for (let i = 0; i < aCachedResults.length; ++i) {
store.get(aCachedResults[i]).onsuccess = function(e) {
aSuccessCb(e.target.result);
count++;
if (count == aCachedResults.length) {
aSuccessCb(null);
}
};
}
});
}
} else { // no contacts
if (DEBUG) debug("query returned no contacts");
aSuccessCb(null);
@ -555,34 +552,6 @@ ContactDB.prototype = {
}.bind(this));
},
getNext: function CDB_getNext(aSuccessCb, aFailureCb, aCursorId) {
if (DEBUG) debug("ContactDB:getNext: " + aCursorId);
let aCachedResults = this.cursorData[aCursorId];
if (DEBUG) debug("got transient cache");
if (aCachedResults.length > 0) {
this.getObjectById(STORE_NAME, aCachedResults[0], function(aContact) {
this.cursorData[aCursorId].shift();
if (aContact) {
aSuccessCb(aContact);
} else {
// If the contact ID in cache is invalid, it was removed recently and
// the cache hasn't been updated to reflect the change, so we skip it.
if (DEBUG) debug("invalid contact in cache: " + aCachedResults[0]);
return this.getNext(aSuccessCb, aFailureCb, aCursorId);
}
}.bind(this));
} else { // last contact
delete this.cursorData[aCursorId];
aSuccessCb(null);
}
},
releaseCursors: function CDB_releaseCursors(aCursors) {
for (let i of aCursors) {
delete this.cursorData[i];
}
},
/*
* Sorting the contacts by sortBy field. aSortBy can either be familyName or givenName.
* If 2 entries have the same sortyBy field or no sortBy field is present, we continue

View File

@ -37,12 +37,9 @@ XPCOMUtils.defineLazyGetter(this, "mRIL", function () {
let myGlobal = this;
this.DOMContactManager = {
// maps children to their live cursors so we can cleanup on shutdown/crash
_liveCursors: {},
init: function() {
if (DEBUG) debug("Init");
this._messages = ["Contacts:Find", "Contacts:GetAll", "Contacts:GetAll:Continue", "Contacts:Clear", "Contact:Save",
this._messages = ["Contacts:Find", "Contacts:GetAll", "Contacts:Clear", "Contact:Save",
"Contact:Remove", "Contacts:GetSimContacts",
"Contacts:RegisterForMessages", "child-process-shutdown"];
this._children = [];
@ -119,23 +116,6 @@ this.DOMContactManager = {
},
function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Find:Return:KO", { errorMsg: aErrorMsg }); },
msg.findOptions, msg.cursorId);
if (Array.isArray(this._liveCursors[mm])) {
this._liveCursors[mm].push(msg.cursorId);
} else {
this._liveCursors[mm] = [msg.cursorId];
}
break;
case "Contacts:GetAll:Continue":
this._db.getNext(
function(aContact) {
if (aContact == null) { // last contact, release the cursor
let cursors = this._liveCursors[mm];
cursors.splice(cursors.indexOf(msg.cursorId), 1);
}
mm.sendAsyncMessage("Contacts:GetAll:Next", {cursorId: msg.cursorId, contact: aContact});
}.bind(this),
function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Find:Return:KO", { errorMsg: aErrorMsg }); },
msg.cursorId);
break;
case "Contact:Save":
if (msg.options.reason === "create") {
@ -210,10 +190,6 @@ this.DOMContactManager = {
break;
case "child-process-shutdown":
if (DEBUG) debug("Unregister");
if (this._liveCursors[mm]) {
this._db.releaseCursors(this._liveCursors[mm]);
delete this._liveCursors[mm];
}
let index = this._children.indexOf(mm);
if (index != -1) {
if (DEBUG) debug("Unregister index: " + index);

View File

@ -266,7 +266,7 @@ let steps = [
add20Contacts,
function() {
ok(true, "Test cache invalidation between getAll and getNext");
ok(true, "Test cache consistency when deleting contact during getAll");
req = mozContacts.find({});
req.onsuccess = function(e) {
let lastContact = e.target.result[e.target.result.length-1];
@ -290,8 +290,7 @@ let steps = [
count++;
req.continue();
} else {
is(count, 19, "19 contacts returned");
ok(true, "last contact");
is(count, 20, "last contact - 20 contacts returned");
next();
}
}
@ -303,7 +302,7 @@ let steps = [
add20Contacts,
function() {
ok(true, "Delete the currect contact while iterating");
ok(true, "Delete the current contact while iterating");
req = mozContacts.getAll({});
let count = 0;
let previousId = null;