commit ada657fcb81bd5f9a151c9cdfc087df7ad86e502
parent cf3eed5f149fd871bb41618f3dd8e0addd752a70
Author: Dan Stillman <dstillman@zotero.org>
Date: Thu, 21 May 2015 21:56:04 -0400
Functions to modify 'version'/'synced' efficiently, plus some other fixes
Diffstat:
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
@@ -772,6 +772,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
*/
Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* (options) {
diff --git 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<Zotero.[Object]>} 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<Zotero.DataObject>} - 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<?";
- return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
-}
+ return Zotero.DB.columnQueryAsync(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
+});
-Zotero.DataObjects.prototype.getNewer = function (libraryID, date, ignoreFutureDates) {
+Zotero.DataObjects.prototype.getNewer = Zotero.Promise.method(function (libraryID, date, ignoreFutureDates) {
if (!date || date.constructor.name != 'Date') {
throw ("date must be a JS Date in "
+ "Zotero." + this._ZDO_Objects + ".getNewer()")
@@ -310,8 +313,8 @@ Zotero.DataObjects.prototype.getNewer = function (libraryID, date, ignoreFutureD
if (ignoreFutureDates) {
sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
}
- return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
-}
+ return Zotero.DB.columnQueryAsync(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
+});
/**
@@ -430,6 +433,61 @@ Zotero.DataObjects.prototype.unload = function () {
}
+/**
+ * Set the version of objects, efficiently
+ *
+ * @param {Integer[]} ids - Ids of objects to update
+ * @param {Boolean} synced
+ */
+Zotero.DataObjects.prototype.updateVersion = Zotero.Promise.method(function (ids, version) {
+ if (version != parseInt(version)) {
+ throw new Error("'version' must be an integer");
+ }
+ version = parseInt(version);
+
+ let sql = "UPDATE " + this.table + " SET version=" + version + " "
+ + "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 '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
@@ -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
@@ -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
@@ -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
@@ -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();
+ }
+ })
+ })
})