From ada657fcb81bd5f9a151c9cdfc087df7ad86e502 Mon Sep 17 00:00:00 2001 From: Dan Stillman Date: Thu, 21 May 2015 21:56:04 -0400 Subject: [PATCH] Functions to modify 'version'/'synced' efficiently, plus some other fixes --- .../content/zotero/xpcom/data/dataObject.js | 68 +++++++++++++++++ .../content/zotero/xpcom/data/dataObjects.js | 76 ++++++++++++++++--- chrome/content/zotero/xpcom/data/item.js | 11 --- .../zotero/xpcom/utilities_internal.js | 2 +- test/content/support.js | 5 +- test/tests/dataObjectTest.js | 47 +++++++++++- 6 files changed, 184 insertions(+), 25 deletions(-) diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js index 3c0b88980..fbea2806e 100644 --- a/chrome/content/zotero/xpcom/data/dataObject.js +++ b/chrome/content/zotero/xpcom/data/dataObject.js @@ -771,6 +771,74 @@ Zotero.DataObject.prototype._recoverFromSaveError = Zotero.Promise.coroutine(fun }); +/** + * Update object version, efficiently + * + * Used by sync code + * + * @param {Integer} version + * @param {Boolean} [skipDB=false] + */ +Zotero.DataObject.prototype.updateVersion = Zotero.Promise.coroutine(function* (version, skipDB) { + if (!this.id) { + throw new Error("Cannot update version of unsaved " + this._objectType); + } + if (version != parseInt(version)) { + throw new Error("'version' must be an integer"); + } + + this._version = parseInt(version); + + if (!skipDB) { + var cl = this.ObjectsClass; + var sql = "UPDATE " + cl.table + " SET version=? WHERE " + cl.idColumn + "=?"; + yield Zotero.DB.queryAsync(sql, [parseInt(version), this.id]); + } + + if (this._changed.primaryData && this._changed.primaryData.version) { + if (Objects.keys(this._changed.primaryData).length == 1) { + delete this._changed.primaryData; + } + else { + delete this._changed.primaryData.version; + } + } +}); + +/** + * Update object sync status, efficiently + * + * Used by sync code + * + * @param {Boolean} synced + * @param {Boolean} [skipDB=false] + */ +Zotero.DataObject.prototype.updateSynced = Zotero.Promise.coroutine(function* (synced, skipDB) { + if (!this.id) { + throw new Error("Cannot update sync status of unsaved " + this._objectType); + } + if (typeof synced != 'boolean') { + throw new Error("'synced' must be a boolean"); + } + + this._synced = synced; + + if (!skipDB) { + var cl = this.ObjectsClass; + var sql = "UPDATE " + cl.table + " SET synced=? WHERE " + cl.idColumn + "=?"; + yield Zotero.DB.queryAsync(sql, [synced ? 1 : 0, this.id]); + } + + if (this._changed.primaryData && this._changed.primaryData.synced) { + if (Objects.keys(this._changed.primaryData).length == 1) { + delete this._changed.primaryData; + } + else { + delete this._changed.primaryData.synced; + } + } +}); + /** * Delete object from database */ diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js index 1a04bb407..2dd1eae12 100644 --- a/chrome/content/zotero/xpcom/data/dataObjects.js +++ b/chrome/content/zotero/xpcom/data/dataObjects.js @@ -127,9 +127,10 @@ Zotero.DataObjects.prototype.get = function (ids) { * Retrieves (and loads, if necessary) one or more items * * @param {Array|Integer} ids An individual object id or an array of object ids - * @param {Object} options 'noCache': Don't cache loaded objects - * @return {Zotero.[Object]|Array} A Zotero.[Object], if a scalar id was passed; - * otherwise, an array of Zotero.[Object] + * @param {Object} [options] + * @param {Boolean} [options.noCache=false] - Don't add object to cache after loading + * @return {Zotero.DataObject|Zotero.DataObject[]} - A data object, if a scalar id was passed; + * otherwise, an array of data objects */ Zotero.DataObjects.prototype.getAsync = Zotero.Promise.coroutine(function* (ids, options) { var toLoad = []; @@ -253,6 +254,8 @@ Zotero.DataObjects.prototype.getByLibraryAndKey = function (libraryID, key, opti * * @param {Integer} - libraryID * @param {String} - key + * @param {Object} [options] + * @param {Boolean} [options.noCache=false] - Don't add object to cache after loading * @return {Promise} - Promise for a data object, or FALSE if not found */ Zotero.DataObjects.prototype.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) { @@ -287,7 +290,7 @@ Zotero.DataObjects.prototype.getIDFromLibraryAndKey = function (libraryID, key) } -Zotero.DataObjects.prototype.getOlder = function (libraryID, date) { +Zotero.DataObjects.prototype.getOlder = Zotero.Promise.method(function (libraryID, date) { if (!date || date.constructor.name != 'Date') { throw ("date must be a JS Date in " + "Zotero." + this._ZDO_Objects + ".getOlder()") @@ -295,11 +298,11 @@ Zotero.DataObjects.prototype.getOlder = function (libraryID, date) { var sql = "SELECT ROWID FROM " + this._ZDO_table + " WHERE libraryID=? AND clientDateModified '?').join(', ') + ')', chunk); + // Update the internal 'version' property of any loaded objects + for (let i = 0; i < chunk.length; i++) { + let id = chunk[i]; + let obj = this._objectCache[id]; + if (obj) { + obj.updateVersion(version, true); + } + } + }.bind(this)) + ); +}); + + +/** + * Set the sync state of objects, efficiently + * + * @param {Integer[]} ids - Ids of objects to update + * @param {Boolean} synced + */ +Zotero.DataObjects.prototype.updateSynced = Zotero.Promise.method(function (ids, synced) { + let sql = "UPDATE " + this.table + " SET synced=" + (synced ? 1 : 0) + " " + + "WHERE " + this.idColumn + " IN ("; + return Zotero.Utilities.Internal.forEachChunkAsync( + ids, Zotero.DB.MAX_BOUND_PARAMETERS, Zotero.Promise.coroutine(function* (chunk) { + yield Zotero.DB.queryAsync(sql + chunk.map(() => '?').join(', ') + ')', chunk); + // Update the internal 'synced' property of any loaded objects + for (let i = 0; i < chunk.length; i++) { + let id = chunk[i]; + let obj = this._objectCache[id]; + if (obj) { + obj.updateSynced(!!synced, true); + } + } + }.bind(this)) + ); +}); + + Zotero.DataObjects.prototype.isEditable = function (obj) { var libraryID = obj.libraryID; if (!libraryID) { diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js index 243580a8f..877bc4386 100644 --- a/chrome/content/zotero/xpcom/data/item.js +++ b/chrome/content/zotero/xpcom/data/item.js @@ -1742,17 +1742,6 @@ Zotero.Item.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) { return env.isNew ? this.id : true; }); -/** - * Used by sync code - */ -Zotero.Item.prototype.updateClientDateModified = function () { - if (!this.id) { - throw ("Cannot update clientDateModified of unsaved item in Zotero.Item.updateClientDateModified()"); - } - var sql = "UPDATE items SET clientDateModified=? WHERE itemID=?"; - Zotero.DB.query(sql, [Zotero.DB.transactionDateTime, this.id]); -} - Zotero.Item.prototype.isRegularItem = function() { return !(this.isNote() || this.isAttachment()); diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js index 4da5ba662..acf50ce99 100644 --- a/chrome/content/zotero/xpcom/utilities_internal.js +++ b/chrome/content/zotero/xpcom/utilities_internal.js @@ -35,7 +35,7 @@ Zotero.Utilities.Internal = { * * @param {Array} arr * @param {Integer} chunkSize - * @param {Function} func + * @param {Function} func - A promise-returning function * @return {Array} The return values from the successive runs */ "forEachChunkAsync": Zotero.Promise.coroutine(function* (arr, chunkSize, func) { diff --git a/test/content/support.js b/test/content/support.js index c5edcf911..fd5141a22 100644 --- a/test/content/support.js +++ b/test/content/support.js @@ -171,9 +171,8 @@ function createUnsavedDataObject(objectType, params) { var createDataObject = Zotero.Promise.coroutine(function* (objectType, params, saveOptions) { var obj = createUnsavedDataObject(objectType, params); - var id = yield obj.saveTx(saveOptions); - var objectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType); - return objectsClass.getAsync(id); + yield obj.saveTx(saveOptions); + return obj; }); /** diff --git a/test/tests/dataObjectTest.js b/test/tests/dataObjectTest.js index 6ad415ae4..30aadae62 100644 --- a/test/tests/dataObjectTest.js +++ b/test/tests/dataObjectTest.js @@ -37,7 +37,6 @@ describe("Zotero.DataObject", function() { it("should be set after creating object", function* () { for (let type of types) { - Zotero.logError(type); let obj = yield createDataObject(type, { version: 1234 }); assert.equal(obj.version, 1234, type + " version mismatch"); yield obj.eraseTx(); @@ -126,4 +125,50 @@ describe("Zotero.DataObject", function() { assert.equal(objectsClass.getIDFromLibraryAndKey(libraryID, key), id); }) }) + + describe("#updateVersion()", function() { + it("should update the object version", function* () { + for (let type of types) { + let obj = yield createDataObject(type); + assert.equal(obj.version, 0); + + yield obj.updateVersion(1234); + assert.equal(obj.version, 1234); + assert.isFalse(obj.hasChanged()); + + obj.synced = true; + assert.ok(obj.hasChanged()); + yield obj.updateVersion(1235); + assert.equal(obj.version, 1235); + assert.ok(obj.hasChanged()); + + yield obj.eraseTx(); + } + }) + }) + + describe("#updateSynced()", function() { + it("should update the object sync status", function* () { + for (let type of types) { + let obj = yield createDataObject(type); + assert.isFalse(obj.synced); + + yield obj.updateSynced(false); + assert.isFalse(obj.synced); + assert.isFalse(obj.hasChanged()); + + yield obj.updateSynced(true); + assert.ok(obj.synced); + assert.isFalse(obj.hasChanged()); + + obj.version = 1234; + assert.ok(obj.hasChanged()); + yield obj.updateSynced(false); + assert.isFalse(obj.synced); + assert.ok(obj.hasChanged()); + + yield obj.eraseTx(); + } + }) + }) })