commit 66a04a39db8ec6abc7e4664c14318aa3e4fc0801
parent e4451d90025ad6c8a40c400fed43510d7846b7e8
Author: Dan Stillman <dstillman@zotero.org>
Date: Wed, 28 Jan 2015 15:07:32 -0500
Merge pull request #576 from aurimasv/async_db-av2
[Async DB] Modularize Zotero.DataObject.save()
Diffstat:
16 files changed, 2336 insertions(+), 2245 deletions(-)
diff --git a/chrome/content/zotero/bindings/zoterosearch.xml b/chrome/content/zotero/bindings/zoterosearch.xml
@@ -232,7 +232,7 @@
<body>
<![CDATA[
this.updateSearch();
- return this.search.save(true);
+ return this.search.save({fixGaps: true});
]]>
</body>
</method>
diff --git a/chrome/content/zotero/xpcom/api.js b/chrome/content/zotero/xpcom/api.js
@@ -201,7 +201,7 @@ Zotero.API.Data = {
var params = this.parsePath(path);
//Zotero.debug(params);
- return Zotero.DataObjectUtilities.getClassForObjectType(params.objectType)
+ return Zotero.DataObjectUtilities.getObjectsClassForObjectType(params.objectType)
.apiDataGenerator(params);
}
};
diff --git a/chrome/content/zotero/xpcom/data/collection.js b/chrome/content/zotero/xpcom/data/collection.js
@@ -37,38 +37,51 @@ Zotero.Collection = function() {
this._childItems = [];
}
-Zotero.Collection._super = Zotero.DataObject;
-Zotero.Collection.prototype = Object.create(Zotero.Collection._super.prototype);
-Zotero.Collection.constructor = Zotero.Collection;
+Zotero.extendClass(Zotero.DataObject, Zotero.Collection);
Zotero.Collection.prototype._objectType = 'collection';
Zotero.Collection.prototype._dataTypes = Zotero.Collection._super.prototype._dataTypes.concat([
- 'primaryData',
'childCollections',
'childItems'
]);
-Zotero.Collection.prototype.__defineGetter__('id', function () { return this._get('id'); });
-Zotero.Collection.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
-Zotero.Collection.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
-Zotero.Collection.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
-Zotero.Collection.prototype.__defineGetter__('key', function () { return this._get('key'); });
-Zotero.Collection.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
-Zotero.Collection.prototype.__defineGetter__('name', function () { return this._get('name'); });
-Zotero.Collection.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
-// .parentKey and .parentID defined in dataObject.js
-Zotero.Collection.prototype.__defineGetter__('version', function () { return this._get('version'); });
-Zotero.Collection.prototype.__defineSetter__('version', function (val) { this._set('version', val); });
-Zotero.Collection.prototype.__defineGetter__('synced', function () { return this._get('synced'); });
-Zotero.Collection.prototype.__defineSetter__('synced', function (val) { this._set('synced', val); });
-
-Zotero.Collection.prototype.__defineGetter__('parent', function (val) {
- Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
- return this.parentID;
+Zotero.defineProperty(Zotero.Collection.prototype, 'ChildObjects', {
+ get: function() Zotero.Items
});
-Zotero.Collection.prototype.__defineSetter__('parent', function (val) {
- Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
- this.parentID = val;
+
+Zotero.defineProperty(Zotero.Collection.prototype, 'id', {
+ get: function() this._get('id'),
+ set: function(val) this._set('id', val)
+});
+Zotero.defineProperty(Zotero.Collection.prototype, 'libraryID', {
+ get: function() this._get('libraryID'),
+ set: function(val) this._set('libraryID', val)
+});
+Zotero.defineProperty(Zotero.Collection.prototype, 'key', {
+ get: function() this._get('key'),
+ set: function(val) this._set('key', val)
+});
+Zotero.defineProperty(Zotero.Collection.prototype, 'name', {
+ get: function() this._get('name'),
+ set: function(val) this._set('name', val)
+});
+Zotero.defineProperty(Zotero.Collection.prototype, 'version', {
+ get: function() this._get('version'),
+ set: function(val) this._set('version', val)
+});
+Zotero.defineProperty(Zotero.Collection.prototype, 'synced', {
+ get: function() this._get('synced'),
+ set: function(val) this._set('synced', val)
+});
+Zotero.defineProperty(Zotero.Collection.prototype, 'parent', {
+ get: function() {
+ Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
+ return this.parentID;
+ },
+ set: function(val) {
+ Zotero.debug("WARNING: Zotero.Collection.prototype.parent has been deprecated -- use .parentID or .parentKey", 2);
+ this.parentID = val;
+ }
});
Zotero.Collection.prototype._set = function (field, value) {
@@ -115,44 +128,12 @@ Zotero.Collection.prototype.getName = function() {
/*
- * Build collection from database
- */
-Zotero.Collection.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload) {
- if (this._loaded.primaryData && !reload) return;
-
- var id = this._id;
- var key = this._key;
- var libraryID = this._libraryID;
-
- var sql = Zotero.Collections.getPrimaryDataSQL();
- if (id) {
- sql += " AND O.collectionID=?";
- var params = id;
- }
- else {
- sql += " AND O.libraryID=? AND O.key=?";
- var params = [libraryID, key];
- }
- var data = yield Zotero.DB.rowQueryAsync(sql, params);
-
- this._loaded.primaryData = true;
- this._clearChanged('primaryData');
-
- if (!data) {
- return;
- }
-
- this.loadFromRow(data);
-});
-
-
-/*
* Populate collection data from a database row
*/
Zotero.Collection.prototype.loadFromRow = function(row) {
Zotero.debug("Loading collection from row");
- for each(let col in Zotero.Collections.primaryFields) {
+ for each(let col in this.ObjectsClass.primaryFields) {
if (row[col] === undefined) {
Zotero.debug('Skipping missing collection field ' + col);
}
@@ -267,168 +248,139 @@ Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
return objs;
}
-
-Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
- try {
- Zotero.Collections.editCheck(this);
+Zotero.Collection.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
+ if (!this.name) {
+ throw new Error('Collection name is empty');
+ }
+
+ var proceed = yield Zotero.Collection._super.prototype._initSave.apply(this, arguments);
+ if (!proceed) return false;
+
+ // Verify parent
+ if (this._parentKey) {
+ let newParent = this.ObjectsClass.getByLibraryAndKey(
+ this.libraryID, this._parentKey
+ );
- if (!this.name) {
- throw new Error('Collection name is empty');
+ if (!newParent) {
+ throw new Error("Cannot set parent to invalid collection " + this._parentKey);
}
- if (Zotero.Utilities.isEmpty(this._changed)) {
- Zotero.debug("Collection " + this.id + " has not changed");
- return false;
+ if (newParent.id == this.id) {
+ throw new Error('Cannot move collection into itself!');
}
- var isNew = !this.id;
-
- // Register this item's identifiers in Zotero.DataObjects on transaction commit,
- // before other callbacks run
- var collectionID, libraryID, key;
- if (isNew) {
- var transactionOptions = {
- onCommit: function () {
- Zotero.Collections.registerIdentifiers(collectionID, libraryID, key);
- }
- };
- }
- else {
- var transactionOptions = null;
+ if (this.id && (yield this.hasDescendent('collection', newParent.id))) {
+ throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
}
- return Zotero.DB.executeTransaction(function* () {
- // how to know if date modified changed (in server code too?)
-
- collectionID = this._id = this.id ? this.id : yield Zotero.ID.get('collections');
- libraryID = this.libraryID;
- key = this._key = this.key ? this.key : this._generateKey();
-
- Zotero.debug("Saving collection " + this.id);
-
- // Verify parent
- if (this._parentKey) {
- let newParent = Zotero.Collections.getByLibraryAndKey(
- this.libraryID, this._parentKey
- );
-
- if (!newParent) {
- throw new Error("Cannot set parent to invalid collection " + this._parentKey);
- }
-
- if (newParent.id == this.id) {
- throw new Error('Cannot move collection into itself!');
- }
-
- if (this.id && (yield this.hasDescendent('collection', newParent.id))) {
- throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
- }
-
- var parent = newParent.id;
- }
- else {
- var parent = null;
- }
-
- var columns = [
- 'collectionID',
- 'collectionName',
- 'parentCollectionID',
- 'clientDateModified',
- 'libraryID',
- 'key',
- 'version',
- 'synced'
- ];
- var sqlValues = [
- collectionID ? { int: collectionID } : null,
- { string: this.name },
- parent ? parent : null,
- Zotero.DB.transactionDateTime,
- this.libraryID ? this.libraryID : 0,
- key,
- this.version ? this.version : 0,
- this.synced ? 1 : 0
- ];
- if (isNew) {
- var placeholders = columns.map(function () '?').join();
-
- var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
- + "VALUES (" + placeholders + ")";
- var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
- if (!collectionID) {
- collectionID = insertID;
- }
- }
- else {
- columns.shift();
- sqlValues.push(sqlValues.shift());
- let sql = 'UPDATE collections SET '
- + columns.map(function (x) x + '=?').join(', ')
- + ' WHERE collectionID=?';
- yield Zotero.DB.queryAsync(sql, sqlValues);
- }
-
- if (this._changed.parentKey) {
- var parentIDs = [];
- if (this.id && this._previousData.parentKey) {
- parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
- this.libraryID, this._previousData.parentKey
- ));
- }
- if (this.parentKey) {
- parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
- this.libraryID, this.parentKey
- ));
- }
- if (this.id) {
- Zotero.Notifier.trigger('move', 'collection', this.id);
- }
- }
-
- if (isNew && this.libraryID) {
- var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
- var group = Zotero.Groups.get(groupID);
- group.clearCollectionCache();
- }
-
- if (isNew) {
- Zotero.Notifier.trigger('add', 'collection', this.id);
- }
- else {
- Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
- }
-
- // Invalidate cached child collections
- if (parentIDs) {
- Zotero.Collections.refreshChildCollections(parentIDs);
- }
-
- // New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled
- if (isNew) {
- var id = this.id;
- this._disabled = true;
- return id;
- }
-
- yield this.reload();
- this._clearChanged();
-
- return true;
- }.bind(this), transactionOptions);
+ env.parent = newParent.id;
+ }
+ else {
+ env.parent = null;
+ }
+
+ return true;
+});
+
+Zotero.Collection.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
+ var isNew = env.isNew;
+
+ var collectionID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('collections');
+ var libraryID = env.libraryID = this.libraryID;
+ var key = env.key = this._key = this.key ? this.key : this._generateKey();
+
+ Zotero.debug("Saving collection " + this.id);
+
+ var columns = [
+ 'collectionID',
+ 'collectionName',
+ 'parentCollectionID',
+ 'clientDateModified',
+ 'libraryID',
+ 'key',
+ 'version',
+ 'synced'
+ ];
+ var sqlValues = [
+ collectionID ? { int: collectionID } : null,
+ { string: this.name },
+ env.parent ? env.parent : null,
+ Zotero.DB.transactionDateTime,
+ this.libraryID ? this.libraryID : 0,
+ key,
+ this.version ? this.version : 0,
+ this.synced ? 1 : 0
+ ];
+ if (isNew) {
+ var placeholders = columns.map(function () '?').join();
+
+ var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
+ + "VALUES (" + placeholders + ")";
+ var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
+ if (!collectionID) {
+ collectionID = env.id = insertID;
+ }
}
- catch (e) {
- try {
- yield this.reload();
- this._clearChanged();
+ else {
+ columns.shift();
+ sqlValues.push(sqlValues.shift());
+ let sql = 'UPDATE collections SET '
+ + columns.map(function (x) x + '=?').join(', ')
+ + ' WHERE collectionID=?';
+ yield Zotero.DB.queryAsync(sql, sqlValues);
+ }
+
+ if (this._changed.parentKey) {
+ var parentIDs = [];
+ if (this.id && this._previousData.parentKey) {
+ parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
+ this.libraryID, this._previousData.parentKey
+ ));
}
- catch (e2) {
- Zotero.debug(e2, 1);
+ if (this.parentKey) {
+ parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
+ this.libraryID, this.parentKey
+ ));
}
-
- Zotero.debug(e, 1);
- throw e;
+ if (this.id) {
+ Zotero.Notifier.trigger('move', 'collection', this.id);
+ }
+ env.parentIDs = parentIDs;
+ }
+});
+
+Zotero.Collection.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
+ var isNew = env.isNew;
+ if (isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) {
+ var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
+ var group = Zotero.Groups.get(groupID);
+ group.clearCollectionCache();
+ }
+
+ if (isNew) {
+ Zotero.Notifier.trigger('add', 'collection', this.id);
+ }
+ else {
+ Zotero.Notifier.trigger('modify', 'collection', this.id, this._previousData);
+ }
+
+ // Invalidate cached child collections
+ if (env.parentIDs) {
+ this.ObjectsClass.refreshChildCollections(env.parentIDs);
+ }
+
+ // New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled
+ if (isNew) {
+ var id = this.id;
+ this._disabled = true;
+ return id;
}
+
+ yield this.reload();
+ this._clearChanged();
+
+ return true;
});
@@ -466,7 +418,7 @@ Zotero.Collection.prototype.addItems = Zotero.Promise.coroutine(function* (itemI
continue;
}
- let item = yield Zotero.Items.getAsync(itemID);
+ let item = yield this.ChildObjects.getAsync(itemID);
yield item.loadCollections();
item.addToCollection(this.id);
yield item.save({
@@ -513,7 +465,7 @@ Zotero.Collection.prototype.removeItems = Zotero.Promise.coroutine(function* (it
continue;
}
- let item = yield Zotero.Items.getAsync(itemID);
+ let item = yield this.ChildObjects.getAsync(itemID);
yield item.loadCollections();
item.removeFromCollection(this.id);
yield item.save({
@@ -565,7 +517,7 @@ Zotero.Collection.prototype.diff = function (collection, includeMatches) {
var diff = [];
var thisData = this.serialize();
var otherData = collection.serialize();
- var numDiffs = Zotero.Collections.diff(thisData, otherData, diff, includeMatches);
+ var numDiffs = this.ObjectsClass.diff(thisData, otherData, diff, includeMatches);
// For the moment, just compare children and increase numDiffs if any differences
var d1 = Zotero.Utilities.arrayDiff(
@@ -625,7 +577,7 @@ Zotero.Collection.prototype.clone = function (includePrimary, newCollection) {
var sameLibrary = newCollection.libraryID == this.libraryID;
}
else {
- var newCollection = new Zotero.Collection;
+ var newCollection = new this.constructor;
var sameLibrary = true;
if (includePrimary) {
@@ -661,7 +613,7 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
// Descendent collections
if (descendents[i].type == 'collection') {
collections.push(descendents[i].id);
- var c = yield Zotero.Collections.getAsync(descendents[i].id);
+ var c = yield this.ObjectsClass.getAsync(descendents[i].id);
if (c) {
notifierData[c.id] = { old: c.toJSON() };
}
@@ -675,7 +627,7 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
}
}
if (del.length) {
- yield Zotero.Items.trash(del);
+ yield this.ChildObjects.trash(del);
}
// Remove relations
@@ -698,9 +650,9 @@ Zotero.Collection.prototype.erase = function(deleteItems) {
// TODO: Update member items
}.bind(this))
- .then(function () {
+ .then(() => {
// Clear deleted collection from internal memory
- Zotero.Collections.unload(collections);
+ this.ObjectsClass.unload(collections);
//return Zotero.Collections.reloadAll();
})
.then(function () {
@@ -815,7 +767,7 @@ Zotero.Collection.prototype.getChildren = Zotero.Promise.coroutine(function* (re
}
if (recursive) {
- let child = yield Zotero.Collections.getAsync(children[i].id);
+ let child = yield this.ObjectsClass.getAsync(children[i].id);
let descendents = yield child.getChildren(
true, nested, type, includeDeletedItems, level+1
);
@@ -871,7 +823,7 @@ Zotero.Collection.prototype.addLinkedCollection = Zotero.Promise.coroutine(funct
var predicate = Zotero.Relations.linkedObjectPredicate;
if ((yield Zotero.Relations.getByURIs(url1, predicate, url2)).length
|| (yield Zotero.Relations.getByURIs(url2, predicate, url1)).length) {
- Zotero.debug("Collections " + this.key + " and " + collection.key + " are already linked");
+ Zotero.debug(this._ObjectTypePlural + " " + this.key + " and " + collection.key + " are already linked");
return false;
}
@@ -901,9 +853,9 @@ Zotero.Collection.prototype.loadChildCollections = Zotero.Promise.coroutine(func
this._childCollections = [];
- if (ids) {
+ if (ids.length) {
for each(var id in ids) {
- var col = yield Zotero.Collections.getAsync(id);
+ var col = yield this.ObjectsClass.getAsync(id);
if (!col) {
throw new Error('Collection ' + id + ' not found');
}
@@ -943,7 +895,7 @@ Zotero.Collection.prototype.loadChildItems = Zotero.Promise.coroutine(function*
this._childItems = [];
if (ids) {
- var items = yield Zotero.Items.getAsync(ids)
+ var items = yield this.ChildObjects.getAsync(ids)
if (items) {
this._childItems = items;
}
diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js
@@ -27,9 +27,10 @@
/*
* Primary interface for accessing Zotero collection
*/
-Zotero.Collections = new function() {
- Zotero.DataObjects.apply(this, ['collection']);
- this.constructor.prototype = new Zotero.DataObjects();
+Zotero.Collections = function() {
+ this.constructor = null;
+
+ this._ZDO_object = 'collection';
this._primaryDataSQLParts = {
collectionID: "O.collectionID",
@@ -45,9 +46,13 @@ Zotero.Collections = new function() {
hasChildCollections: "(SELECT COUNT(*) FROM collections WHERE "
+ "parentCollectionID=O.collectionID) != 0 AS hasChildCollections",
hasChildItems: "(SELECT COUNT(*) FROM collectionItems WHERE "
- + "collectionID=O.collectionID) != 0 AS hasChildItems "
+ + "collectionID=O.collectionID) != 0 AS hasChildItems"
};
+
+ this._primaryDataSQLFrom = "FROM collections O "
+ + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID)";
+
/**
* Add new collection to DB and return Collection object
*
@@ -74,55 +79,51 @@ Zotero.Collections = new function() {
* Takes parent collectionID as optional parameter;
* by default, returns root collections
*/
- this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parent, recursive) {
- var toReturn = [];
+ this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parentID, recursive) {
+ let children;
- if (!parent) {
- parent = null;
+ if (parentID) {
+ let parent = yield this.getAsync(parentID);
+ yield parent.loadChildCollections();
+ children = parent.getChildCollections();
+ if (!children.length) Zotero.debug('No child collections in collection ' + parentID, 5);
+ } else if (libraryID || libraryID === 0) {
+ children = this.getCollectionsInLibrary(libraryID);
+ if (!children.length) Zotero.debug('No child collections in library ' + libraryID, 5);
+ } else {
+ throw new Error("Either library ID or parent collection ID must be provided to getNumCollectionsByParent");
}
- var sql = "SELECT collectionID AS id, collectionName AS name FROM collections C "
- + "WHERE libraryID=? AND parentCollectionID " + (parent ? '= ' + parent : 'IS NULL');
- var children = yield Zotero.DB.queryAsync(sql, [libraryID]);
-
- if (!children) {
- Zotero.debug('No child collections of collection ' + parent, 5);
- return toReturn;
+ if (!children.length) {
+ return children;
}
// Do proper collation sort
- var collation = Zotero.getLocaleCollation();
- children.sort(function (a, b) {
- return collation.compareString(1, a.name, b.name);
- });
+ children.sort(function (a, b) Zotero.localeCompare(a.name, b.name));
+
+ if (!recursive) return children;
+ let toReturn = [];
for (var i=0, len=children.length; i<len; i++) {
- var obj = yield this.getAsync(children[i].id);
- if (!obj) {
- throw ('Collection ' + children[i].id + ' not found');
- }
-
+ var obj = children[i];
toReturn.push(obj);
- // If recursive, get descendents
- if (recursive) {
- var desc = obj.getDescendents(false, 'collection');
- for (var j in desc) {
- var obj2 = yield this.getAsync(desc[j]['id']);
- if (!obj2) {
- throw new Error('Collection ' + desc[j] + ' not found');
- }
-
- // TODO: This is a quick hack so that we can indent subcollections
- // in the search dialog -- ideally collections would have a
- // getLevel() method, but there's no particularly quick way
- // of calculating that without either storing it in the DB or
- // changing the schema to Modified Preorder Tree Traversal,
- // and I don't know if we'll actually need it anywhere else.
- obj2.level = desc[j].level;
-
- toReturn.push(obj2);
+ var desc = obj.getDescendents(false, 'collection');
+ for (var j in desc) {
+ var obj2 = yield this.getAsync(desc[j]['id']);
+ if (!obj2) {
+ throw new Error('Collection ' + desc[j] + ' not found');
}
+
+ // TODO: This is a quick hack so that we can indent subcollections
+ // in the search dialog -- ideally collections would have a
+ // getLevel() method, but there's no particularly quick way
+ // of calculating that without either storing it in the DB or
+ // changing the schema to Modified Preorder Tree Traversal,
+ // and I don't know if we'll actually need it anywhere else.
+ obj2.level = desc[j].level;
+
+ toReturn.push(obj2);
}
}
@@ -130,6 +131,17 @@ Zotero.Collections = new function() {
});
+ this.getCollectionsInLibrary = Zotero.Promise.coroutine(function* (libraryID) {
+ let sql = "SELECT collectionID AS id FROM collections C "
+ + "WHERE libraryID=? AND parentCollectionId IS NULL";
+ let ids = yield Zotero.DB.queryAsync(sql, [libraryID]);
+ let collections = yield this.getAsync(ids.map(function(row) row.id));
+ if (!collections.length) return collections;
+
+ return collections.sort(function (a, b) Zotero.localeCompare(a.name, b.name));
+ });
+
+
this.getCollectionsContainingItems = function (itemIDs, asIDs) {
// If an unreasonable number of items, don't try
if (itemIDs.length > 100) {
@@ -145,8 +157,8 @@ Zotero.Collections = new function() {
}
sql = sql.substring(0, sql.length - 5);
return Zotero.DB.columnQueryAsync(sql, sqlParams)
- .then(function (collectionIDs) {
- return asIDs ? collectionIDs : Zotero.Collections.get(collectionIDs);
+ .then(collectionIDs => {
+ return asIDs ? collectionIDs : this.get(collectionIDs);
});
}
@@ -186,32 +198,23 @@ Zotero.Collections = new function() {
});
- this.erase = function (ids) {
+ this.erase = function(ids) {
ids = Zotero.flattenArguments(ids);
- Zotero.DB.beginTransaction();
- for each(var id in ids) {
- var collection = this.getAsync(id);
- if (collection) {
- collection.erase();
+ return Zotero.DB.executeTransaction(function* () {
+ for each(var id in ids) {
+ var collection = yield this.getAsync(id);
+ if (collection) {
+ yield collection.erase();
+ }
+ collection = undefined;
}
- collection = undefined;
- }
-
- this.unload(ids);
-
- Zotero.DB.commitTransaction();
- }
+
+ this.unload(ids);
+ });
+ };
+ Zotero.DataObjects.call(this);
- this.getPrimaryDataSQL = function () {
- // This should be the same as the query in Zotero.Collection.load(),
- // just without a specific collectionID
- return "SELECT "
- + Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
- + "FROM collections O "
- + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID) "
- + "WHERE 1";
- }
-}
-
+ return this;
+}.bind(Object.create(Zotero.DataObjects.prototype))();
diff --git a/chrome/content/zotero/xpcom/data/dataObject.js b/chrome/content/zotero/xpcom/data/dataObject.js
@@ -34,6 +34,8 @@ Zotero.DataObject = function () {
let objectType = this._objectType;
this._ObjectType = objectType[0].toUpperCase() + objectType.substr(1);
this._objectTypePlural = Zotero.DataObjectUtilities.getObjectTypePlural(objectType);
+ this._ObjectTypePlural = this._objectTypePlural[0].toUpperCase() + this._objectTypePlural.substr(1);
+ this._ObjectsClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
this._id = null;
this._libraryID = null;
@@ -53,23 +55,36 @@ Zotero.DataObject = function () {
};
Zotero.DataObject.prototype._objectType = 'dataObject';
-Zotero.DataObject.prototype._dataTypes = [];
+Zotero.DataObject.prototype._dataTypes = ['primaryData'];
-Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'objectType', {
+Zotero.defineProperty(Zotero.DataObject.prototype, 'objectType', {
get: function() this._objectType
});
-Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'libraryKey', {
+Zotero.defineProperty(Zotero.DataObject.prototype, 'id', {
+ get: function() this._id
+});
+Zotero.defineProperty(Zotero.DataObject.prototype, 'libraryID', {
+ get: function() this._libraryID
+});
+Zotero.defineProperty(Zotero.DataObject.prototype, 'key', {
+ get: function() this._key
+});
+Zotero.defineProperty(Zotero.DataObject.prototype, 'libraryKey', {
get: function() this._libraryID + "/" + this._key
});
-Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'parentKey', {
+Zotero.defineProperty(Zotero.DataObject.prototype, 'parentKey', {
get: function() this._parentKey,
set: function(v) this._setParentKey(v)
});
-Zotero.Utilities.Internal.defineProperty(Zotero.DataObject.prototype, 'parentID', {
+Zotero.defineProperty(Zotero.DataObject.prototype, 'parentID', {
get: function() this._getParentID(),
set: function(v) this._setParentID(v)
});
+Zotero.defineProperty(Zotero.DataObject.prototype, 'ObjectsClass', {
+ get: function() this._ObjectsClass
+});
+
Zotero.DataObject.prototype._get = function (field) {
if (this['_' + field] !== null) {
@@ -135,7 +150,7 @@ Zotero.DataObject.prototype._getParentID = function () {
if (!this._parentKey) {
return false;
}
- return this._parentID = this._getClass().getIDFromLibraryAndKey(this._libraryID, this._parentKey);
+ return this._parentID = this.ObjectsClass.getIDFromLibraryAndKey(this._libraryID, this._parentKey);
}
@@ -148,7 +163,7 @@ Zotero.DataObject.prototype._getParentID = function () {
Zotero.DataObject.prototype._setParentID = function (id) {
return this._setParentKey(
id
- ? this._getClass().getLibraryAndKeyFromID(Zotero.DataObjectUtilities.checkDataID(id))[1]
+ ? this.ObjectsClass.getLibraryAndKeyFromID(Zotero.DataObjectUtilities.checkDataID(id))[1]
: null
);
}
@@ -309,6 +324,60 @@ Zotero.DataObject.prototype._getLinkedObject = Zotero.Promise.coroutine(function
return false;
});
+/*
+ * Build object from database
+ */
+Zotero.DataObject.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) {
+ if (this._loaded.primaryData && !reload) return;
+
+ var id = this.id;
+ var key = this.key;
+ var libraryID = this.libraryID;
+
+ if (!id && !key) {
+ throw new Error('ID or key not set in Zotero.' + this._ObjectType + '.loadPrimaryData()');
+ }
+
+ var columns = [], join = [], where = [];
+ var primaryFields = this.ObjectsClass.primaryFields;
+ var idField = this.ObjectsClass.idColumn;
+ for (let i=0; i<primaryFields.length; i++) {
+ let field = primaryFields[i];
+ // If field not already set
+ if (field == idField || this['_' + field] === null || reload) {
+ columns.push(this.ObjectsClass.getPrimaryDataSQLPart(field));
+ }
+ }
+ if (!columns.length) {
+ return;
+ }
+
+ // This should match Zotero.*.primaryDataSQL, but without
+ // necessarily including all columns
+ var sql = "SELECT " + columns.join(", ") + this.ObjectsClass.primaryDataSQLFrom;
+ if (id) {
+ sql += " AND O." + idField + "=? ";
+ var params = id;
+ }
+ else {
+ sql += " AND O.key=? AND O.libraryID=? ";
+ var params = [key, libraryID];
+ }
+ sql += (where.length ? ' AND ' + where.join(' AND ') : '');
+ var row = yield Zotero.DB.rowQueryAsync(sql, params);
+
+ if (!row) {
+ if (failOnMissing) {
+ throw new Error(this._ObjectType + " " + (id ? id : libraryID + "/" + key)
+ + " not found in Zotero." + this._ObjectType + ".loadPrimaryData()");
+ }
+ this._loaded.primaryData = true;
+ this._clearChanged('primaryData');
+ return;
+ }
+
+ this.loadFromRow(row, reload);
+});
/**
* Reloads loaded, changed data
@@ -368,13 +437,6 @@ Zotero.DataObject.prototype._requireData = function (dataType) {
}
}
-/**
- * Returns a global Zotero class object given a data object. (e.g. Zotero.Items)
- * @return {obj} One of Zotero data classes
- */
-Zotero.DataObject.prototype._getClass = function () {
- return Zotero.DataObjectUtilities.getClassForObjectType(this._objectType);
-}
/**
* Loads data for a given data type
@@ -385,6 +447,14 @@ Zotero.DataObject.prototype._loadDataType = function (dataType, reload) {
return this["load" + dataType[0].toUpperCase() + dataType.substr(1)](reload);
}
+Zotero.DataObject.prototype.loadAllData = function (reload) {
+ let loadPromises = new Array(this._dataTypes.length);
+ for (let i=0; i<this._dataTypes.length; i++) {
+ loadPromises[i] = this._loadDataType(this._dataTypes[i], reload);
+ }
+
+ return Zotero.Promise.all(loadPromises);
+}
/**
* Save old version of data that's being changed, to pass to the notifier
@@ -422,6 +492,141 @@ Zotero.DataObject.prototype._clearFieldChange = function (field) {
delete this._previousData[field];
}
+
+Zotero.DataObject.prototype.isEditable = function () {
+ return Zotero.Libraries.isEditable(this.libraryID);
+}
+
+
+Zotero.DataObject.prototype.editCheck = function () {
+ if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable()) {
+ throw ("Cannot edit " + this._objectType + " in read-only Zotero library");
+ }
+}
+
+/**
+ * Save changes to database
+ *
+ * @return {Promise<Integer|Boolean>} Promise for itemID of new item,
+ * TRUE on item update, or FALSE if item was unchanged
+ */
+Zotero.DataObject.prototype.save = Zotero.Promise.coroutine(function* (options) {
+ var env = {
+ transactionOptions: null,
+ options: options || {}
+ };
+
+ var proceed = yield this._initSave(env);
+ if (!proceed) return false;
+
+ if (env.isNew) {
+ Zotero.debug('Saving data for new ' + this._objectType + ' to database', 4);
+ }
+ else {
+ Zotero.debug('Updating database with new ' + this._objectType + ' data', 4);
+ }
+
+ return Zotero.DB.executeTransaction(function* () {
+ yield this._saveData(env);
+ return yield this._finalizeSave(env);
+ }.bind(this), env.transactionOptions)
+ .catch(e => {
+ return this._recoverFromSaveError(env, e)
+ .catch(function(e2) {
+ Zotero.debug(e2, 1);
+ })
+ .then(function() {
+ Zotero.debug(e, 1);
+ throw e;
+ })
+ });
+});
+
+Zotero.DataObject.prototype.hasChanged = function() {
+ Zotero.debug(this._changed);
+ return !!Object.keys(this._changed).filter(dataType => this._changed[dataType]).length
+}
+
+Zotero.DataObject.prototype._saveData = function() {
+ throw new Error("Zotero.DataObject.prototype._saveData is an abstract method");
+}
+
+Zotero.DataObject.prototype._finalizeSave = function() {
+ throw new Error("Zotero.DataObject.prototype._finalizeSave is an abstract method");
+}
+
+Zotero.DataObject.prototype._recoverFromSaveError = Zotero.Promise.coroutine(function* () {
+ yield this.reload(null, true);
+ this._clearChanged();
+});
+
+Zotero.DataObject.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
+ env.isNew = !this.id;
+
+ if (!env.options.skipEditCheck) this.editCheck();
+
+ if (!this.hasChanged()) {
+ Zotero.debug(this._ObjectType + ' ' + this.id + ' has not changed', 4);
+ return false;
+ }
+
+ // Register this object's identifiers in Zotero.DataObjects on transaction commit,
+ // before other callbacks run
+ if (env.isNew) {
+ env.transactionOptions = {
+ onCommit: () => {
+ this.ObjectsClass.registerIdentifiers(env.id, env.libraryID, env.key);
+ }
+ };
+ }
+
+ return true;
+});
+
+/**
+ * Delete object from database
+ */
+Zotero.DataObject.prototype.erase = Zotero.Promise.coroutine(function* () {
+ var env = {};
+
+ var proceed = yield this._eraseInit(env);
+ if (!proceed) return false;
+
+ Zotero.debug('Deleting ' + this.objectType + ' ' + this.id);
+
+ yield Zotero.DB.executeTransaction(function* () {
+ yield this._eraseData(env);
+ yield this._erasePreCommit(env);
+ }.bind(this))
+ .catch(e => {
+ return this._eraseRecoverFromFailure(env);
+ });
+
+ return this._erasePostCommit(env);
+});
+
+Zotero.DataObject.prototype._eraseInit = function(env) {
+ if (!this.id) return Zotero.Promise.resolve(false);
+
+ return Zotero.Promise.resolve(true);
+};
+
+Zotero.DataObject.prototype._eraseData = function(env) {
+ throw new Error("Zotero.DataObject.prototype._eraseData is an abstract method");
+};
+
+Zotero.DataObject.prototype._erasePreCommit = function(env) {
+ return Zotero.Promise.resolve();
+};
+
+Zotero.DataObject.prototype._erasePostCommit = function(env) {
+ return Zotero.Promise.resolve();
+};
+
+Zotero.DataObject.prototype._eraseRecoverFromFailure = function(env) {
+ throw new Error("Zotero.DataObject.prototype._eraseRecoverFromFailure is an abstract method");
+};
+
/**
* Generates data object key
* @return {String} key
diff --git a/chrome/content/zotero/xpcom/data/dataObjectUtilities.js b/chrome/content/zotero/xpcom/data/dataObjectUtilities.js
@@ -59,12 +59,12 @@ Zotero.DataObjectUtilities = {
},
- "getObjectTypePlural": function getObjectTypePlural(objectType) {
+ "getObjectTypePlural": function(objectType) {
return objectType == 'search' ? 'searches' : objectType + 's';
},
- "getClassForObjectType": function getClassForObjectType(objectType) {
+ "getObjectsClassForObjectType": function(objectType) {
var objectTypePlural = this.getObjectTypePlural(objectType);
var className = objectTypePlural[0].toUpperCase() + objectTypePlural.substr(1);
return Zotero[className]
diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js
@@ -24,606 +24,608 @@
*/
-Zotero.DataObjects = function (object, objectPlural, id, table) {
- var self = this;
+Zotero.DataObjects = function () {
+ if (!this._ZDO_object) throw new Error('this._ZDO_object must be set before calling Zotero.DataObjects constructor');
- if (!object) {
- object = '';
+ if (!this._ZDO_objects) {
+ this._ZDO_objects = Zotero.DataObjectUtilities.getObjectTypePlural(this._ZDO_object);
+ }
+ if (!this._ZDO_Object) {
+ this._ZDO_Object = this._ZDO_object.substr(0, 1).toUpperCase()
+ + this._ZDO_object.substr(1);
+ }
+ if (!this._ZDO_Objects) {
+ this._ZDO_Objects = this._ZDO_objects.substr(0, 1).toUpperCase()
+ + this._ZDO_objects.substr(1);
+ }
+
+ if (!this._ZDO_id) {
+ this._ZDO_id = this._ZDO_object + 'ID';
}
- // Override these variables in child objects
- this._ZDO_object = object;
- this._ZDO_objects = objectPlural ? objectPlural : object + 's';
- this._ZDO_Object = object.substr(0, 1).toUpperCase() + object.substr(1);
- this._ZDO_Objects = this._ZDO_objects.substr(0, 1).toUpperCase()
- + this._ZDO_objects.substr(1);
- this._ZDO_id = (id ? id : object) + 'ID';
- this._ZDO_table = table ? table : this._ZDO_objects;
+ if (!this._ZDO_table) {
+ this._ZDO_table = this._ZDO_objects;
+ }
- // Certain object types don't have a libary and key and only use an id
- switch (object) {
- case 'relation':
- this._ZDO_idOnly = true;
- break;
-
- default:
- this._ZDO_idOnly = false;
+ if (!this.ObjectClass) {
+ this.ObjectClass = Zotero[this._ZDO_Object];
}
+ this.primaryDataSQLFrom = " " + this._primaryDataSQLFrom + " " + this._primaryDataSQLWhere;
+
this._objectCache = {};
this._objectKeys = {};
this._objectIDs = {};
this._loadedLibraries = {};
this._loadPromise = null;
+}
+
+Zotero.DataObjects.prototype._ZDO_idOnly = false;
+
+// Public properties
+Zotero.defineProperty(Zotero.DataObjects.prototype, 'idColumn', {
+ get: function() this._ZDO_id
+});
+Zotero.defineProperty(Zotero.DataObjects.prototype, 'table', {
+ get: function() this._ZDO_table
+});
+
+Zotero.defineProperty(Zotero.DataObjects.prototype, 'primaryFields', {
+ get: function () Object.keys(this._primaryDataSQLParts)
+}, {lazy: true});
+
+
+Zotero.DataObjects.prototype.init = function() {
+ return this._loadIDsAndKeys();
+}
+
+
+Zotero.DataObjects.prototype.isPrimaryField = function (field) {
+ return this.primaryFields.indexOf(field) != -1;
+}
+
+
+/**
+ * Retrieves one or more already-loaded items
+ *
+ * If an item hasn't been loaded, an error is thrown
+ *
+ * @param {Array|Integer} ids An individual object id or an array of object ids
+ * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
+ * otherwise, an array of Zotero.[Object]
+ */
+Zotero.DataObjects.prototype.get = function (ids) {
+ if (Array.isArray(ids)) {
+ var singleObject = false;
+ }
+ else {
+ var singleObject = true;
+ ids = [ids];
+ }
- // Public properties
- this.table = this._ZDO_table;
+ var toReturn = [];
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ // Check if already loaded
+ if (!this._objectCache[id]) {
+ throw new Zotero.Exception.UnloadedDataException(this._ZDO_Object + " " + id + " not yet loaded");
+ }
+ toReturn.push(this._objectCache[id]);
+ }
- this.init = function () {
- return this._loadIDsAndKeys();
+ // If single id, return the object directly
+ if (singleObject) {
+ return toReturn.length ? toReturn[0] : false;
}
+ return toReturn;
+};
- this.__defineGetter__('primaryFields', function () {
- var primaryFields = Object.keys(this._primaryDataSQLParts);
-
- // Once primary fields have been cached, get rid of getter for speed purposes
- delete this.primaryFields;
- this.primaryFields = primaryFields;
-
- return primaryFields;
- });
+/**
+ * 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]
+ */
+Zotero.DataObjects.prototype.getAsync = Zotero.Promise.coroutine(function* (ids, options) {
+ var toLoad = [];
+ var toReturn = [];
- this.isPrimaryField = function (field) {
- return this.primaryFields.indexOf(field) != -1;
+ if (!ids) {
+ throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()");
}
+ if (Array.isArray(ids)) {
+ var singleObject = false;
+ }
+ else {
+ var singleObject = true;
+ ids = [ids];
+ }
- /**
- * Retrieves one or more already-loaded items
- *
- * If an item hasn't been loaded, an error is thrown
- *
- * @param {Array|Integer} ids An individual object id or an array of object ids
- * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed;
- * otherwise, an array of Zotero.[Object]
- */
- this.get = function (ids) {
- if (Array.isArray(ids)) {
- var singleObject = false;
- }
- else {
- var singleObject = true;
- ids = [ids];
- }
-
- var toReturn = [];
-
- for (let i=0; i<ids.length; i++) {
- let id = ids[i];
- // Check if already loaded
- if (!this._objectCache[id]) {
- throw new Zotero.Exception.UnloadedDataException(this._ZDO_Object + " " + id + " not yet loaded");
- }
+ for (let i=0; i<ids.length; i++) {
+ let id = ids[i];
+ // Check if already loaded
+ if (this._objectCache[id]) {
toReturn.push(this._objectCache[id]);
}
-
- // If single id, return the object directly
- if (singleObject) {
- return toReturn.length ? toReturn[0] : false;
+ else {
+ toLoad.push(id);
}
-
- return toReturn;
- };
-
+ }
- /**
- * 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]
- */
- this.getAsync = Zotero.Promise.coroutine(function* (ids, options) {
+ // New object to load
+ if (toLoad.length) {
// Serialize loads
if (this._loadPromise && this._loadPromise.isPending()) {
yield this._loadPromise;
}
- var deferred = Zotero.Promise.defer();
+ let deferred = Zotero.Promise.defer();
this._loadPromise = deferred.promise;
- var toLoad = [];
- var toReturn = [];
-
- if (!ids) {
- throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()");
- }
-
- if (Array.isArray(ids)) {
- var singleObject = false;
- }
- else {
- var singleObject = true;
- ids = [ids];
- }
-
- for (let i=0; i<ids.length; i++) {
- let id = ids[i];
- // Check if already loaded
- if (this._objectCache[id]) {
- toReturn.push(this._objectCache[id]);
- }
- else {
- toLoad.push(id);
- }
- }
-
- // New object to load
- if (toLoad.length) {
- let loaded = yield this._load(null, toLoad, options);
- for (let i=0; i<toLoad.length; i++) {
- let id = toLoad[i];
- let obj = loaded[id];
- if (!obj) {
- Zotero.debug(this._ZDO_Object + " " + id + " doesn't exist", 2);
- continue;
- }
- toReturn.push(obj);
+ let loaded = yield this._load(null, toLoad, options);
+ for (let i=0; i<toLoad.length; i++) {
+ let id = toLoad[i];
+ let obj = loaded[id];
+ if (!obj) {
+ Zotero.debug(this._ZDO_Object + " " + id + " doesn't exist", 2);
+ continue;
}
+ toReturn.push(obj);
}
-
deferred.resolve();
-
- // If single id, return the object directly
- if (singleObject) {
- return toReturn.length ? toReturn[0] : false;
- }
-
- return toReturn;
- });
-
-
- /**
- * @deprecated - use .libraryKey
- */
- this.makeLibraryKeyHash = function (libraryID, key) {
- Zotero.debug("WARNING: Zotero.DataObjects.makeLibraryKeyHash() is deprecated -- use obj.libraryKey instead");
- return libraryID + '_' + key;
}
-
- /**
- * @deprecated - use .libraryKey
- */
- this.getLibraryKeyHash = function (obj) {
- Zotero.debug("WARNING: Zotero.DataObjects.getLibraryKeyHash() is deprecated -- use obj.libraryKey instead");
- return this.makeLibraryKeyHash(obj.libraryID, obj.key);
+ // If single id, return the object directly
+ if (singleObject) {
+ return toReturn.length ? toReturn[0] : false;
}
-
- this.parseLibraryKey = function (libraryKey) {
- var [libraryID, key] = libraryKey.split('/');
- return {
- libraryID: parseInt(libraryID),
- key: key
- };
+ return toReturn;
+});
+
+
+/**
+ * @deprecated - use .libraryKey
+ */
+Zotero.DataObjects.prototype.makeLibraryKeyHash = function (libraryID, key) {
+ Zotero.debug("WARNING: " + this._ZDO_Objects + ".makeLibraryKeyHash() is deprecated -- use .libraryKey instead");
+ return libraryID + '_' + key;
+}
+
+
+/**
+ * @deprecated - use .libraryKey
+ */
+Zotero.DataObjects.prototype.getLibraryKeyHash = function (obj) {
+ Zotero.debug("WARNING: " + this._ZDO_Objects + ".getLibraryKeyHash() is deprecated -- use .libraryKey instead");
+ return this.makeLibraryKeyHash(obj.libraryID, obj.key);
+}
+
+
+Zotero.DataObjects.prototype.parseLibraryKey = function (libraryKey) {
+ var [libraryID, key] = libraryKey.split('/');
+ return {
+ libraryID: parseInt(libraryID),
+ key: key
+ };
+}
+
+
+/**
+ * @deprecated - Use Zotero.DataObjects.parseLibraryKey()
+ */
+Zotero.DataObjects.prototype.parseLibraryKeyHash = function (libraryKey) {
+ Zotero.debug("WARNING: " + this._ZDO_Objects + ".parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead");
+ var [libraryID, key] = libraryKey.split('_');
+ if (!key) {
+ return false;
}
-
-
- /**
- * @deprecated - Use Zotero.DataObjects.parseLibraryKey()
- */
- this.parseLibraryKeyHash = function (libraryKey) {
- Zotero.debug("WARNING: Zotero.DataObjects.parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead");
- var [libraryID, key] = libraryKey.split('_');
- if (!key) {
- return false;
- }
- return {
- libraryID: parseInt(libraryID),
- key: key
- };
- }
-
-
- /**
- * Retrieves an object by its libraryID and key
- *
- * @param {Integer} libraryID
- * @param {String} key
- * @return {Zotero.DataObject} Zotero data object, or FALSE if not found
- */
- this.getByLibraryAndKey = function (libraryID, key, options) {
- var id = this.getIDFromLibraryAndKey(libraryID, key);
- if (!id) {
- return false;
- }
- return Zotero[this._ZDO_Objects].get(id, options);
+ return {
+ libraryID: parseInt(libraryID),
+ key: key
};
-
-
- /**
- * Asynchronously retrieves an object by its libraryID and key
- *
- * @param {Integer} - libraryID
- * @param {String} - key
- * @return {Promise<Zotero.DataObject>} - Promise for a data object, or FALSE if not found
- */
- this.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) {
- var id = this.getIDFromLibraryAndKey(libraryID, key);
- if (!id) {
- return false;
- }
- return Zotero[this._ZDO_Objects].getAsync(id, options);
- });
-
-
- this.exists = function (itemID) {
- return !!this.getLibraryAndKeyFromID(itemID);
+}
+
+
+/**
+ * Retrieves an object by its libraryID and key
+ *
+ * @param {Integer} libraryID
+ * @param {String} key
+ * @return {Zotero.DataObject} Zotero data object, or FALSE if not found
+ */
+Zotero.DataObjects.prototype.getByLibraryAndKey = function (libraryID, key, options) {
+ var id = this.getIDFromLibraryAndKey(libraryID, key);
+ if (!id) {
+ return false;
}
-
-
- /**
- * @return {Array} Array with libraryID and key
- */
- this.getLibraryAndKeyFromID = function (id) {
- return this._objectKeys[id] ? this._objectKeys[id] : false;
+ return Zotero[this._ZDO_Objects].get(id, options);
+};
+
+
+/**
+ * Asynchronously retrieves an object by its libraryID and key
+ *
+ * @param {Integer} - libraryID
+ * @param {String} - key
+ * @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) {
+ var id = this.getIDFromLibraryAndKey(libraryID, key);
+ if (!id) {
+ return false;
+ }
+ return Zotero[this._ZDO_Objects].getAsync(id, options);
+});
+
+
+Zotero.DataObjects.prototype.exists = function (itemID) {
+ return !!this.getLibraryAndKeyFromID(itemID);
+}
+
+
+/**
+ * @return {Array} Array with libraryID and key
+ */
+Zotero.DataObjects.prototype.getLibraryAndKeyFromID = function (id) {
+ return this._objectKeys[id] ? this._objectKeys[id] : false;
+}
+
+
+Zotero.DataObjects.prototype.getIDFromLibraryAndKey = function (libraryID, key) {
+ if (libraryID === null) {
+ throw new Error("libraryID cannot be NULL (did you mean 0?)");
+ }
+ return (this._objectIDs[libraryID] && this._objectIDs[libraryID][key])
+ ? this._objectIDs[libraryID][key] : false;
+}
+
+
+Zotero.DataObjects.prototype.getOlder = function (libraryID, date) {
+ if (!date || date.constructor.name != 'Date') {
+ throw ("date must be a JS Date in "
+ + "Zotero." + this._ZDO_Objects + ".getOlder()")
}
+ var sql = "SELECT ROWID FROM " + this._ZDO_table
+ + " WHERE libraryID=? AND clientDateModified<?";
+ return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
+}
+
+
+Zotero.DataObjects.prototype.getNewer = function (libraryID, date, ignoreFutureDates) {
+ if (!date || date.constructor.name != 'Date') {
+ throw ("date must be a JS Date in "
+ + "Zotero." + this._ZDO_Objects + ".getNewer()")
+ }
- this.getIDFromLibraryAndKey = function (libraryID, key) {
- if (libraryID === null) {
- throw new Error("libraryID cannot be NULL (did you mean 0?)");
+ var sql = "SELECT ROWID FROM " + this._ZDO_table
+ + " WHERE libraryID=? AND clientDateModified>?";
+ if (ignoreFutureDates) {
+ sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
+ }
+ return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
+}
+
+
+/**
+ * @param {Integer} libraryID
+ * @return {Promise} A promise for an array of object ids
+ */
+Zotero.DataObjects.prototype.getUnsynced = function (libraryID) {
+ var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table
+ + " WHERE libraryID=? AND synced=0";
+ return Zotero.DB.columnQueryAsync(sql, [libraryID]);
+}
+
+
+/**
+ * Get JSON from the sync cache that hasn't yet been written to the
+ * main object tables
+ *
+ * @param {Integer} libraryID
+ * @return {Promise} A promise for an array of JSON objects
+ */
+Zotero.DataObjects.prototype.getUnwrittenData = function (libraryID) {
+ var sql = "SELECT data FROM syncCache SC "
+ + "LEFT JOIN " + this._ZDO_table + " "
+ + "USING (libraryID) "
+ + "WHERE SC.libraryID=? AND "
+ + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM "
+ + "syncObjectTypes WHERE name='" + this._ZDO_object + "') "
+ + "AND IFNULL(O.version, 0) < SC.version";
+ return Zotero.DB.columnQueryAsync(sql, [libraryID]);
+}
+
+
+/**
+ * Reload loaded data of loaded objects
+ *
+ * @param {Array|Number} ids - An id or array of ids
+ * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded
+ * types if not provided
+ * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally.
+ * This should be set to true for data that was
+ * changed externally (e.g., globally renamed tags).
+ */
+Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
+ ids = Zotero.flattenArguments(ids);
+
+ Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
+ + this._ZDO_objects + ' ' + ids);
+
+ for (let i=0; i<ids.length; i++) {
+ if (this._objectCache[ids[i]]) {
+ yield this._objectCache[ids[i]].reload(dataTypes, reloadUnchanged);
}
- return (this._objectIDs[libraryID] && this._objectIDs[libraryID][key])
- ? this._objectIDs[libraryID][key] : false;
}
-
- this.getOlder = function (libraryID, date) {
- if (!date || date.constructor.name != 'Date') {
- throw ("date must be a JS Date in "
- + "Zotero." + this._ZDO_Objects + ".getOlder()")
+ return true;
+});
+
+
+Zotero.DataObjects.prototype.reloadAll = function (libraryID) {
+ Zotero.debug("Reloading all " + this._ZDO_objects);
+
+ // Remove objects not stored in database
+ var sql = "SELECT ROWID FROM " + this._ZDO_table;
+ var params = [];
+ if (libraryID !== undefined) {
+ sql += ' WHERE libraryID=?';
+ params.push(libraryID);
+ }
+ return Zotero.DB.columnQueryAsync(sql, params)
+ .then(function (ids) {
+ for (var id in this._objectCache) {
+ if (!ids || ids.indexOf(parseInt(id)) == -1) {
+ delete this._objectCache[id];
+ }
}
- var sql = "SELECT ROWID FROM " + this._ZDO_table
- + " WHERE libraryID=? AND clientDateModified<?";
- return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
+ // Reload data
+ this._loadedLibraries[libraryID] = false;
+ return this._load(libraryID);
+ });
+}
+
+
+Zotero.DataObjects.prototype.registerIdentifiers = function (id, libraryID, key) {
+ Zotero.debug("Registering " + this._ZDO_object + " " + id + " as " + libraryID + "/" + key);
+ if (!this._objectIDs[libraryID]) {
+ this._objectIDs[libraryID] = {};
}
-
-
- this.getNewer = function (libraryID, date, ignoreFutureDates) {
- if (!date || date.constructor.name != 'Date') {
- throw ("date must be a JS Date in "
- + "Zotero." + this._ZDO_Objects + ".getNewer()")
+ this._objectIDs[libraryID][key] = id;
+ this._objectKeys[id] = [libraryID, key];
+}
+
+
+/**
+ * Clear object from internal array
+ *
+ * @param int[] ids objectIDs
+ */
+Zotero.DataObjects.prototype.unload = function () {
+ var ids = Zotero.flattenArguments(arguments);
+ for (var i=0; i<ids.length; i++) {
+ let id = ids[i];
+ let [libraryID, key] = this.getLibraryAndKeyFromID(id);
+ if (key) {
+ delete this._objectIDs[libraryID][key];
+ delete this._objectKeys[id];
+ }
+ delete this._objectCache[id];
+ }
+}
+
+
+/**
+ * @param {Object} data1 - JSON of first object
+ * @param {Object} data2 - JSON of second object
+ * @param {Array} diff - Empty array to put diff data in
+ * @param {Boolean} [includeMatches=false] - Include all fields, even those
+ * that aren't different
+ */
+Zotero.DataObjects.prototype.diff = function (data1, data2, diff, includeMatches) {
+ diff.push({}, {});
+ var numDiffs = 0;
+
+ var skipFields = ['collectionKey', 'itemKey', 'searchKey'];
+
+ for (var field in data1) {
+ if (skipFields.indexOf(field) != -1) {
+ continue;
}
- var sql = "SELECT ROWID FROM " + this._ZDO_table
- + " WHERE libraryID=? AND clientDateModified>?";
- if (ignoreFutureDates) {
- sql += " AND clientDateModified<=CURRENT_TIMESTAMP";
+ if (data1[field] === false && (data2[field] === false || data2[field] === undefined)) {
+ continue;
}
- return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]);
- }
-
-
- /**
- * @param {Integer} libraryID
- * @return {Promise} A promise for an array of object ids
- */
- this.getUnsynced = function (libraryID) {
- var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table
- + " WHERE libraryID=? AND synced=0";
- return Zotero.DB.columnQueryAsync(sql, [libraryID]);
- }
-
-
- /**
- * Get JSON from the sync cache that hasn't yet been written to the
- * main object tables
- *
- * @param {Integer} libraryID
- * @return {Promise} A promise for an array of JSON objects
- */
- this.getUnwrittenData = function (libraryID) {
- var sql = "SELECT data FROM syncCache SC "
- + "LEFT JOIN " + this._ZDO_table + " "
- + "USING (libraryID) "
- + "WHERE SC.libraryID=? AND "
- + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM "
- + "syncObjectTypes WHERE name='" + this._ZDO_object + "') "
- + "AND IFNULL(O.version, 0) < SC.version";
- return Zotero.DB.columnQueryAsync(sql, [libraryID]);
- }
-
-
- /**
- * Reload loaded data of loaded objects
- *
- * @param {Array|Number} ids - An id or array of ids
- * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded
- * types if not provided
- * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally.
- * This should be set to true for data that was
- * changed externally (e.g., globally renamed tags).
- */
- this.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
- ids = Zotero.flattenArguments(ids);
- Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
- + this._ZDO_objects + ' ' + ids);
+ var changed = data1[field] !== data2[field];
- for (let i=0; i<ids.length; i++) {
- if (this._objectCache[ids[i]]) {
- yield this._objectCache[ids[i]].reload(dataTypes, reloadUnchanged);
- }
+ if (includeMatches || changed) {
+ diff[0][field] = data1[field] !== false ? data1[field] : '';
+ diff[1][field] = (data2[field] !== false && data2[field] !== undefined)
+ ? data2[field] : '';
}
- return true;
- });
-
-
- this.reloadAll = function (libraryID) {
- Zotero.debug("Reloading all " + this._ZDO_objects);
-
- // Remove objects not stored in database
- var sql = "SELECT ROWID FROM " + this._ZDO_table;
- var params = [];
- if (libraryID !== undefined) {
- sql += ' WHERE libraryID=?';
- params.push(libraryID);
+ if (changed) {
+ numDiffs++;
}
- return Zotero.DB.columnQueryAsync(sql, params)
- .then(function (ids) {
- for (var id in this._objectCache) {
- if (!ids || ids.indexOf(parseInt(id)) == -1) {
- delete this._objectCache[id];
- }
- }
-
- // Reload data
- this._loadedLibraries[libraryID] = false;
- return this._load(libraryID);
- });
}
-
- this.registerIdentifiers = function (id, libraryID, key) {
- Zotero.debug("Registering " + this._ZDO_object + " " + id + " as " + libraryID + "/" + key);
- if (!this._objectIDs[libraryID]) {
- this._objectIDs[libraryID] = {};
- }
- this._objectIDs[libraryID][key] = id;
- this._objectKeys[id] = [libraryID, key];
- }
-
-
- /**
- * Clear object from internal array
- *
- * @param int[] ids objectIDs
- */
- this.unload = function () {
- var ids = Zotero.flattenArguments(arguments);
- for (var i=0; i<ids.length; i++) {
- let id = ids[i];
- let [libraryID, key] = this.getLibraryAndKeyFromID(id);
- if (key) {
- delete this._objectIDs[libraryID][key];
- delete this._objectKeys[id];
- }
- delete this._objectCache[id];
+ // DEBUG: some of this is probably redundant
+ for (var field in data2) {
+ if (skipFields.indexOf(field) != -1) {
+ continue;
}
- }
-
-
- /**
- * @param {Object} data1 - JSON of first object
- * @param {Object} data2 - JSON of second object
- * @param {Array} diff - Empty array to put diff data in
- * @param {Boolean} [includeMatches=false] - Include all fields, even those
- * that aren't different
- */
- this.diff = function (data1, data2, diff, includeMatches) {
- diff.push({}, {});
- var numDiffs = 0;
- var skipFields = ['collectionKey', 'itemKey', 'searchKey'];
+ if (diff[0][field] !== undefined) {
+ continue;
+ }
- for (var field in data1) {
- if (skipFields.indexOf(field) != -1) {
- continue;
- }
-
- if (data1[field] === false && (data2[field] === false || data2[field] === undefined)) {
- continue;
- }
-
- var changed = data1[field] !== data2[field];
-
- if (includeMatches || changed) {
- diff[0][field] = data1[field] !== false ? data1[field] : '';
- diff[1][field] = (data2[field] !== false && data2[field] !== undefined)
- ? data2[field] : '';
- }
-
- if (changed) {
- numDiffs++;
- }
+ if (data2[field] === false && (data1[field] === false || data1[field] === undefined)) {
+ continue;
}
- // DEBUG: some of this is probably redundant
- for (var field in data2) {
- if (skipFields.indexOf(field) != -1) {
- continue;
- }
-
- if (diff[0][field] !== undefined) {
- continue;
- }
-
- if (data2[field] === false && (data1[field] === false || data1[field] === undefined)) {
- continue;
- }
-
- var changed = data1[field] !== data2[field];
-
- if (includeMatches || changed) {
- diff[0][field] = (data1[field] !== false && data1[field] !== undefined)
- ? data1[field] : '';
- diff[1][field] = data2[field] !== false ? data2[field] : '';
- }
-
- if (changed) {
- numDiffs++;
- }
+ var changed = data1[field] !== data2[field];
+
+ if (includeMatches || changed) {
+ diff[0][field] = (data1[field] !== false && data1[field] !== undefined)
+ ? data1[field] : '';
+ diff[1][field] = data2[field] !== false ? data2[field] : '';
}
- return numDiffs;
+ if (changed) {
+ numDiffs++;
+ }
+ }
+
+ return numDiffs;
+}
+
+
+Zotero.DataObjects.prototype.isEditable = function (obj) {
+ var libraryID = obj.libraryID;
+ if (!libraryID) {
+ return true;
}
+ if (!Zotero.Libraries.isEditable(libraryID)) return false;
- this.isEditable = function (obj) {
- var libraryID = obj.libraryID;
- if (!libraryID) {
- return true;
- }
- var type = Zotero.Libraries.getType(libraryID);
- switch (type) {
- case 'user':
- return true;
-
- case 'group':
- var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID);
- var group = Zotero.Groups.get(groupID);
- if (!group.editable) {
- return false;
- }
- if (obj.objectType == 'item' && obj.isAttachment()
- && (obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
- obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)) {
- return group.filesEditable;
- }
- return true;
-
- default:
- throw ("Unsupported library type '" + type + "' in Zotero.DataObjects.isEditable()");
- }
+ if (obj.objectType == 'item' && obj.isAttachment()
+ && (obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
+ obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)
+ && !Zotero.Libraries.isFilesEditable(libraryID)
+ ) {
+ return false;
}
+ return true;
+}
+
+
+Zotero.DataObjects.prototype.editCheck = function (obj) {
+ if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable(obj)) {
+ throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
+ }
+}
+
+Zotero.defineProperty(Zotero.DataObjects.prototype, "primaryDataSQL", {
+ get: function () {
+ return "SELECT "
+ + Object.keys(this._primaryDataSQLParts).map((val) => this._primaryDataSQLParts[val]).join(', ')
+ + this.primaryDataSQLFrom;
+ }
+}, {lazy: true});
+
+Zotero.DataObjects.prototype._primaryDataSQLWhere = "WHERE 1";
+
+Zotero.DataObjects.prototype.getPrimaryDataSQLPart = function (part) {
+ var sql = this._primaryDataSQLParts[part];
+ if (!sql) {
+ throw new Error("Invalid primary data SQL part '" + part + "'");
+ }
+ return sql;
+}
+
+
+Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
+ var loaded = {};
- this.editCheck = function (obj) {
- if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable(obj)) {
- throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library");
- }
+ // If library isn't an integer (presumably false or null), skip it
+ if (parseInt(libraryID) != libraryID) {
+ libraryID = false;
}
+ if (libraryID === false && !ids) {
+ throw new Error("Either libraryID or ids must be provided");
+ }
- this.getPrimaryDataSQLPart = function (part) {
- var sql = this._primaryDataSQLParts[part];
- if (!sql) {
- throw new Error("Invalid primary data SQL part '" + part + "'");
- }
- return sql;
+ if (libraryID !== false && this._loadedLibraries[libraryID]) {
+ return loaded;
}
+ // getPrimaryDataSQL() should use "O" for the primary table alias
+ var sql = this.primaryDataSQL;
+ var params = [];
+ if (libraryID !== false) {
+ sql += ' AND O.libraryID=?';
+ params.push(libraryID);
+ }
+ if (ids) {
+ sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')';
+ }
- this._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
- var loaded = {};
-
- // If library isn't an integer (presumably false or null), skip it
- if (parseInt(libraryID) != libraryID) {
- libraryID = false;
- }
-
- if (libraryID === false && !ids) {
- throw new Error("Either libraryID or ids must be provided");
- }
-
- if (libraryID !== false && this._loadedLibraries[libraryID]) {
- return loaded;
- }
-
- // getPrimaryDataSQL() should use "O" for the primary table alias
- var sql = this.getPrimaryDataSQL();
- var params = [];
- if (libraryID !== false) {
- sql += ' AND O.libraryID=?';
- params.push(libraryID);
- }
- if (ids) {
- sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')';
- }
-
- var t = new Date();
- yield Zotero.DB.queryAsync(
- sql,
- params,
- {
- onRow: function (row) {
- var id = row.getResultByIndex(this._ZDO_id);
- var columns = Object.keys(this._primaryDataSQLParts);
- var rowObj = {};
- for (let i=0; i<columns.length; i++) {
- rowObj[columns[i]] = row.getResultByIndex(i);
- }
- var obj;
-
- // Existing object -- reload in place
- if (this._objectCache[id]) {
- this._objectCache[id].loadFromRow(rowObj, true);
- obj = this._objectCache[id];
- }
- // Object doesn't exist -- create new object and stuff in cache
- else {
- obj = new Zotero[this._ZDO_Object];
- obj.loadFromRow(rowObj, true);
- if (!options || !options.noCache) {
- this._objectCache[id] = obj;
- }
- }
- loaded[id] = obj;
- }.bind(this)
- }
- );
- Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms");
-
- if (!ids) {
- this._loadedLibraries[libraryID] = true;
-
- // If loading all objects, remove cached objects that no longer exist
- for (let i in this._objectCache) {
- let obj = this._objectCache[i];
- if (libraryID !== false && obj.libraryID !== libraryID) {
- continue;
+ var t = new Date();
+ yield Zotero.DB.queryAsync(
+ sql,
+ params,
+ {
+ onRow: function (row) {
+ var id = row.getResultByIndex(this._ZDO_id);
+ var columns = Object.keys(this._primaryDataSQLParts);
+ var rowObj = {};
+ for (let i=0; i<columns.length; i++) {
+ rowObj[columns[i]] = row.getResultByIndex(i);
}
- if (!loaded[obj.id]) {
- this.unload(obj.id);
+ var obj;
+
+ // Existing object -- reload in place
+ if (this._objectCache[id]) {
+ this._objectCache[id].loadFromRow(rowObj, true);
+ obj = this._objectCache[id];
}
+ // Object doesn't exist -- create new object and stuff in cache
+ else {
+ obj = new Zotero[this._ZDO_Object];
+ obj.loadFromRow(rowObj, true);
+ if (!options || !options.noCache) {
+ this._objectCache[id] = obj;
+ }
+ }
+ loaded[id] = obj;
+ }.bind(this)
+ }
+ );
+ Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms");
+
+ if (!ids) {
+ this._loadedLibraries[libraryID] = true;
+
+ // If loading all objects, remove cached objects that no longer exist
+ for (let i in this._objectCache) {
+ let obj = this._objectCache[i];
+ if (libraryID !== false && obj.libraryID !== libraryID) {
+ continue;
}
-
- if (this._postLoad) {
- this._postLoad(libraryID, ids);
+ if (!loaded[obj.id]) {
+ this.unload(obj.id);
}
}
- return loaded;
- });
-
-
- this._loadIDsAndKeys = Zotero.Promise.coroutine(function* () {
- var sql = "SELECT ROWID AS id, libraryID, key FROM " + this._ZDO_table;
- var rows = yield Zotero.DB.queryAsync(sql);
- for (let i=0; i<rows.length; i++) {
- let row = rows[i];
- this._objectKeys[row.id] = [row.libraryID, row.key];
- if (!this._objectIDs[row.libraryID]) {
- this._objectIDs[row.libraryID] = {};
- }
- this._objectIDs[row.libraryID][row.key] = row.id;
+ if (this._postLoad) {
+ this._postLoad(libraryID, ids);
}
- });
-}
+ }
+
+ return loaded;
+});
+
+
+Zotero.DataObjects.prototype._loadIDsAndKeys = Zotero.Promise.coroutine(function* () {
+ var sql = "SELECT ROWID AS id, libraryID, key FROM " + this._ZDO_table;
+ var rows = yield Zotero.DB.queryAsync(sql);
+ for (let i=0; i<rows.length; i++) {
+ let row = rows[i];
+ this._objectKeys[row.id] = [row.libraryID, row.key];
+ if (!this._objectIDs[row.libraryID]) {
+ this._objectIDs[row.libraryID] = {};
+ }
+ this._objectIDs[row.libraryID][row.key] = row.id;
+ }
+});
diff --git a/chrome/content/zotero/xpcom/data/item.js b/chrome/content/zotero/xpcom/data/item.js
@@ -69,9 +69,9 @@ Zotero.Item = function(itemTypeOrID) {
this._attachments = null;
this._notes = null;
- this._tags = {};
- this._collections = {};
- this._relations = {};
+ this._tags = [];
+ this._collections = [];
+ this._relations = [];
this._bestAttachmentState = null;
this._fileExists = null;
@@ -89,52 +89,81 @@ Zotero.Item = function(itemTypeOrID) {
}
}
-Zotero.Item._super = Zotero.DataObject;
-Zotero.Item.prototype = Object.create(Zotero.Item._super.prototype);
-Zotero.Item.constructor = Zotero.Item;
+Zotero.extendClass(Zotero.DataObject, Zotero.Item);
Zotero.Item.prototype._objectType = 'item';
+Zotero.defineProperty(Zotero.Item.prototype, 'ContainerObjectsClass', {
+ get: function() Zotero.Collections
+});
+
Zotero.Item.prototype._dataTypes = Zotero.Item._super.prototype._dataTypes.concat([
- 'primaryData',
'itemData',
'note',
'creators',
'childItems',
- 'relatedItems', // TODO: remove
+// 'relatedItems', // TODO: remove
'tags',
'collections',
'relations'
]);
-Zotero.Item.prototype.__defineGetter__('id', function () this._id);
-Zotero.Item.prototype.__defineGetter__('itemID', function () {
- Zotero.debug("Item.itemID is deprecated -- use Item.id");
- return this._id;
+Zotero.defineProperty(Zotero.Item.prototype, 'id', {
+ get: function() this._id,
+ set: function(val) this.setField('id', val)
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'itemID', {
+ get: function() {
+ Zotero.debug("Item.itemID is deprecated -- use Item.id");
+ return this._id;
+ }
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'libraryID', {
+ get: function() this._libraryID,
+ set: function(val) this.setField('libraryID', val)
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'key', {
+ get: function() this._key,
+ set: function(val) this.setField('key', val)
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'itemTypeID', {
+ get: function() this._itemTypeID
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'dateAdded', {
+ get: function() this._dateAdded
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'dateModified', {
+ get: function() this._dateModified,
+ set: function(val) this.setField('dateModified', val)
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'version', {
+ get: function() this._itemVersion,
+ set: function(val) this.setField('version', val)
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'synced', {
+ get: function() this._synced,
+ set: function(val) this.setField('synced', val)
});
-Zotero.Item.prototype.__defineSetter__('id', function (val) { this.setField('id', val); });
-Zotero.Item.prototype.__defineGetter__('libraryID', function () this._libraryID );
-Zotero.Item.prototype.__defineSetter__('libraryID', function (val) { this.setField('libraryID', val); });
-Zotero.Item.prototype.__defineGetter__('key', function () this._key );
-Zotero.Item.prototype.__defineSetter__('key', function (val) { this.setField('key', val) });
-Zotero.Item.prototype.__defineGetter__('itemTypeID', function () this._itemTypeID);
-Zotero.Item.prototype.__defineGetter__('dateAdded', function () this._dateAdded );
-Zotero.Item.prototype.__defineGetter__('dateModified', function () this._dateModified );
-Zotero.Item.prototype.__defineGetter__('version', function () this._itemVersion );
-Zotero.Item.prototype.__defineSetter__('version', function (val) { return this.setField('itemVersion', val); });
-Zotero.Item.prototype.__defineGetter__('synced', function () this._synced );
-Zotero.Item.prototype.__defineSetter__('synced', function (val) { return this.setField('synced', val); });
// .parentKey and .parentID defined in dataObject.js, but create aliases
-Zotero.Item.prototype.__defineGetter__('parentItemKey', function () this._parentKey );
-Zotero.Item.prototype.__defineSetter__('parentItemKey', function (val) this._setParentKey(val) );
-Zotero.Item.prototype.__defineGetter__('parentItemID', function () this._getParentID() );
-Zotero.Item.prototype.__defineSetter__('parentItemID', function (val) this._setParentID(val) );
-
-Zotero.Item.prototype.__defineGetter__('firstCreator', function () this._firstCreator );
-Zotero.Item.prototype.__defineGetter__('sortCreator', function () this._sortCreator );
+Zotero.defineProperty(Zotero.Item.prototype, 'parentItemID', {
+ get: function() this.parentID,
+ set: function(val) this.parentID = val
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'parentItemKey', {
+ get: function() this.parentKey,
+ set: function(val) this.parentKey = val
+});
-Zotero.Item.prototype.__defineGetter__('relatedItems', function () { return this._getRelatedItems(true); });
-Zotero.Item.prototype.__defineSetter__('relatedItems', function (arr) { this._setRelatedItems(arr); });
+Zotero.defineProperty(Zotero.Item.prototype, 'firstCreator', {
+ get: function() this._firstCreator
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'sortCreator', {
+ get: function() this._sortCreator
+});
+Zotero.defineProperty(Zotero.Item.prototype, 'relatedItems', {
+ get: function() this._getRelatedItems(true),
+ set: function(arr) this._setRelatedItems(arr)
+});
Zotero.Item.prototype.getID = function() {
Zotero.debug('Item.getID() is deprecated -- use Item.id');
@@ -148,7 +177,7 @@ Zotero.Item.prototype.getType = function() {
Zotero.Item.prototype.isPrimaryField = function (fieldName) {
Zotero.debug("Zotero.Item.isPrimaryField() is deprecated -- use Zotero.Items.isPrimaryField()");
- return Zotero.Items.isPrimaryField(fieldName);
+ return this.ObjectsClass.isPrimaryField(fieldName);
}
Zotero.Item.prototype._get = function (fieldName) {
@@ -171,13 +200,13 @@ Zotero.Item.prototype._setParentKey = function() {
/*
* Retrieves (and loads from DB, if necessary) an itemData field value
*
- * Field can be passed as fieldID or fieldName
- *
- * If |unformatted| is true, skip any special processing of DB value
- * (e.g. multipart date field) (default false)
- *
- * If |includeBaseMapped| is true and field is a base field, returns value of
- * type-specific field instead (e.g. 'label' for 'publisher' in 'audioRecording')
+ * @param {String|Integer} field fieldID or fieldName
+ * @param {Boolean} [unformatted] Skip any special processing of DB value
+ * (e.g. multipart date field)
+ * @param {Boolean} includeBaseMapped If true and field is a base field, returns
+ * value of type-specific field instead
+ * (e.g. 'label' for 'publisher' in 'audioRecording')
+ * @return {String} Value as string or empty string if value is not present
*/
Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped) {
// We don't allow access after saving to force use of the centrally cached
@@ -203,10 +232,12 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
} else if (creators.length > 3) {
return creatorsData[0].lastName + " " + Zotero.getString('general.etAl');
}
- } else if (field === 'id' || Zotero.Items.isPrimaryField(field)) {
+ } else if (field === 'id' || this.ObjectsClass.isPrimaryField(field)) {
var privField = '_' + field;
//Zotero.debug('Returning ' + (this[privField] ? this[privField] : '') + ' (typeof ' + typeof this[privField] + ')');
return this[privField];
+ } else if (field == 'year') {
+ return this.getField('date', true, true).substr(0,4);
}
if (this.isNote()) {
@@ -263,72 +294,17 @@ Zotero.Item.prototype.getField = function(field, unformatted, includeBaseMapped)
* @param {Boolean} asNames
* @return {Integer{}|String[]}
*/
-Zotero.Item.prototype.getUsedFields = Zotero.Promise.coroutine(function* (asNames) {
+Zotero.Item.prototype.getUsedFields = function(asNames) {
this._requireData('itemData');
return Object.keys(this._itemData)
- .filter(id => this._itemData[id] !== false)
+ .filter(id => this._itemData[id] !== false && this._itemData[id] !== null)
.map(id => asNames ? Zotero.ItemFields.getName(id) : parseInt(id));
-});
+};
/*
- * Build object from database
- */
-Zotero.Item.prototype.loadPrimaryData = Zotero.Promise.coroutine(function* (reload, failOnMissing) {
- if (this._loaded.primaryData && !reload) return;
-
- var id = this._id;
- var key = this._key;
- var libraryID = this._libraryID;
-
- if (!id && !key) {
- throw new Error('ID or key not set in Zotero.Item.loadPrimaryData()');
- }
-
- var columns = [], join = [], where = [];
- var primaryFields = Zotero.Items.primaryFields;
- for (let i=0; i<primaryFields.length; i++) {
- let field = primaryFields[i];
- // If field not already set
- if (field == 'itemID' || this['_' + field] === null || reload) {
- columns.push(Zotero.Items.getPrimaryDataSQLPart(field));
- }
- }
- if (!columns.length) {
- return;
- }
- // This should match Zotero.Items.getPrimaryDataSQL(), but without
- // necessarily including all columns
- var sql = "SELECT " + columns.join(", ") + Zotero.Items.primaryDataSQLFrom;
- if (id) {
- sql += " AND O.itemID=? ";
- var params = id;
- }
- else {
- sql += " AND O.key=? AND O.libraryID=? ";
- var params = [key, libraryID];
- }
- sql += (where.length ? ' AND ' + where.join(' AND ') : '');
- var row = yield Zotero.DB.rowQueryAsync(sql, params);
-
- if (!row) {
- if (failOnMissing) {
- throw new Error("Item " + (id ? id : libraryID + "/" + key)
- + " not found in Zotero.Item.loadPrimaryData()");
- }
- this._loaded.primaryData = true;
- this._clearChanged('primaryData');
- return;
- }
-
- this.loadFromRow(row, reload);
- return;
-});
-
-
-/*
* Populate basic item data from a database row
*/
Zotero.Item.prototype.loadFromRow = function(row, reload) {
@@ -337,8 +313,13 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
this.setType(row.itemTypeID, true);
}
+ this._parseRowData(row);
+ this._finalizeLoadFromRow(row);
+}
+
+Zotero.Item.prototype._parseRowData = function(row) {
if (false) {
- var primaryFields = Zotero.Items.primaryFields;
+ var primaryFields = this.ObjectsClass.primaryFields;
for (let i=0; i<primaryFields.length; i++) {
if (primaryFields[i] === undefined) {
Zotero.debug('Skipping missing field ' + primaryFields[i]);
@@ -417,7 +398,7 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
}
}
else {
- var primaryFields = Zotero.Items.primaryFields;
+ var primaryFields = this.ObjectsClass.primaryFields;
for (let i=0; i<primaryFields.length; i++) {
let col = primaryFields[i];
@@ -470,6 +451,9 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
}
}
}
+}
+
+Zotero.Item.prototype._finalizeLoadFromRow = function(row) {
this._loaded.primaryData = true;
this._clearChanged('primaryData');
this._identified = true;
@@ -477,15 +461,6 @@ Zotero.Item.prototype.loadFromRow = function(row, reload) {
/*
- * Check if any data fields have changed since last save
- */
-Zotero.Item.prototype.hasChanged = function() {
- Zotero.debug(this._changed);
- return !!Object.keys(this._changed).filter((dataType) => this._changed[dataType]).length
-}
-
-
-/*
* Set or change the item's type
*/
Zotero.Item.prototype.setType = function(itemTypeID, loadIn) {
@@ -735,7 +710,7 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
}
// Primary field
- if (Zotero.Items.isPrimaryField(field)) {
+ if (this.ObjectsClass.isPrimaryField(field)) {
this._requireData('primaryData');
if (loadIn) {
@@ -745,9 +720,22 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
switch (field) {
case 'itemTypeID':
case 'dateAdded':
+ break;
+
case 'dateModified':
+ // Make sure it's valid
+ let date = Zotero.Date.sqlToDate(value, true);
+ if (!date) throw new Error("Invalid SQL date: " + value);
+
+ value = Zotero.Date.dateToSQL(date);
+ break;
+
case 'version':
+ value = parseInt(value);
+ break;
+
case 'synced':
+ value = !!value;
break;
default:
@@ -772,15 +760,6 @@ Zotero.Item.prototype.setField = function(field, value, loadIn) {
this.setType(value, loadIn);
}
else {
- switch (field) {
- case 'version':
- value = parseInt(value);
- break;
-
- case 'synced':
- value = !!value;
- break;
- }
this['_' + field] = value;
@@ -1059,28 +1038,27 @@ Zotero.Item.prototype.removeCreator = function(orderIndex, allowMissing) {
return true;
}
-
-Zotero.Item.prototype.__defineGetter__('deleted', function () {
- if (!this.id) {
- return false;
- }
- if (this._deleted !== null) {
- return this._deleted;
- }
- this._requireData('primaryData');
-});
-
-
-Zotero.Item.prototype.__defineSetter__('deleted', function (val) {
- var deleted = !!val;
-
- if (this._deleted == deleted) {
- Zotero.debug("Deleted state hasn't changed for item " + this.id);
- return;
+Zotero.defineProperty(Zotero.Item.prototype, 'deleted', {
+ get: function() {
+ if (!this.id) {
+ return false;
+ }
+ if (this._deleted !== null) {
+ return this._deleted;
+ }
+ this._requireData('primaryData');
+ },
+ set: function(val) {
+ var deleted = !!val;
+
+ if (this._deleted == deleted) {
+ Zotero.debug("Deleted state hasn't changed for item " + this.id);
+ return;
+ }
+ this._markFieldChange('deleted', !!this._deleted);
+ this._changed.deleted = true;
+ this._deleted = deleted;
}
- this._markFieldChange('deleted', !!this._deleted);
- this._changed.deleted = true;
- this._deleted = deleted;
});
@@ -1103,7 +1081,7 @@ Zotero.Item.prototype.addRelatedItem = Zotero.Promise.coroutine(function* (itemI
return false;
}
- var item = yield Zotero.Items.getAsync(itemID);
+ var item = yield this.ObjectsClass.getAsync(itemID);
if (!item) {
throw ("Can't relate item to invalid item " + itemID
+ " in Zotero.Item.addRelatedItem()");
@@ -1147,609 +1125,576 @@ Zotero.Item.prototype.removeRelatedItem = Zotero.Promise.coroutine(function* (it
});
-/**
- * Save changes to database
- *
- * @return {Promise<Integer|Boolean>} Promise for itemID of new item,
- * TRUE on item update, or FALSE if item was unchanged
- */
-Zotero.Item.prototype.save = Zotero.Promise.coroutine(function* (options) {
- try {
- if (!options) {
- options = {};
- }
-
- var isNew = !this.id;
-
- Zotero.Items.editCheck(this);
-
- if (!this.hasChanged()) {
- Zotero.debug('Item ' + this.id + ' has not changed', 4);
- return false;
- }
+Zotero.Item.prototype.isEditable = function() {
+ var editable = Zotero.Item._super.prototype.isEditable.apply(this);
+ if (!editable) return false;
+
+ // Check if we're allowed to save attachments
+ if (this.isAttachment()
+ && (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL ||
+ this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)
+ && !Zotero.Libraries.isFilesEditable(this.libraryID)
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
+Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
+ var isNew = env.isNew;
+ var options = env.options;
+
+ var itemTypeID = this.itemTypeID;
- // Register this item's identifiers in Zotero.DataObjects on transaction commit,
- // before other callbacks run
- var itemID, libraryID, key;
- if (isNew) {
- var transactionOptions = {
- onCommit: function () {
- Zotero.Items.registerIdentifiers(itemID, libraryID, key);
+ var sqlColumns = [];
+ var sqlValues = [];
+ var reloadParentChildItems = {};
+
+ //
+ // Primary fields
+ //
+ // If available id value, use it -- otherwise we'll use autoincrement
+ var itemID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('items');
+ Zotero.debug('=');
+ var libraryID = env.libraryID = this.libraryID;
+ var key = env.key = this._key = this.key ? this.key : this._generateKey();
+
+ sqlColumns.push(
+ 'itemTypeID',
+ 'dateAdded',
+ 'libraryID',
+ 'key',
+ 'version',
+ 'synced'
+ );
+
+ sqlValues.push(
+ { int: itemTypeID },
+ this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
+ this.libraryID ? this.libraryID : 0,
+ key,
+ this.version ? this.version : 0,
+ this.synced ? 1 : 0
+ );
+
+ if (this._changed.primaryData && this._changed.primaryData._dateModified) {
+ sqlColumns.push('dateModified', 'clientDateModified');
+ sqlValues.push(this.dateModified, Zotero.DB.transactionDateTime);
+ }
+ else if (isNew) {
+ sqlColumns.push('dateModified', 'clientDateModified');
+ sqlValues.push(Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime);
+ }
+ else {
+ for each (let field in ['dateModified', 'clientDateModified']) {
+ switch (field) {
+ case 'dateModified':
+ case 'clientDateModified':
+ let skipFlag = "skip" + field[0].toUpperCase() + field.substr(1) + "Update";
+ if (!options[skipFlag]) {
+ sqlColumns.push(field);
+ sqlValues.push(Zotero.DB.transactionDateTime);
}
- };
+ break;
+ }
}
- else {
- var transactionOptions = null;
+ }
+
+ if (isNew) {
+ sqlColumns.unshift('itemID');
+ sqlValues.unshift(parseInt(itemID));
+
+ var sql = "INSERT INTO items (" + sqlColumns.join(", ") + ") "
+ + "VALUES (" + sqlValues.map(function () "?").join() + ")";
+ var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
+ if (!itemID) {
+ itemID = env.id = insertID;
}
- var itemTypeID = this.itemTypeID;
+ Zotero.Notifier.trigger('add', 'item', itemID);
+ }
+ else {
+ var sql = "UPDATE items SET " + sqlColumns.join("=?, ") + "=? WHERE itemID=?";
+ sqlValues.push(parseInt(itemID));
+ yield Zotero.DB.queryAsync(sql, sqlValues);
- return Zotero.DB.executeTransaction(function* () {
- if (isNew) {
- Zotero.debug('Saving data for new item to database');
- }
- else {
- Zotero.debug('Updating database with new item data', 4);
- }
-
- var sqlColumns = [];
- var sqlValues = [];
- var reloadParentChildItems = {};
-
- //
- // Primary fields
- //
- // If available id value, use it -- otherwise we'll use autoincrement
- itemID = this._id = this.id ? this.id : yield Zotero.ID.get('items');
- Zotero.debug('=');
- libraryID = this.libraryID;
- key = this._key = this.key ? this.key : this._generateKey();
-
- sqlColumns.push(
- 'itemTypeID',
- 'dateAdded',
- 'libraryID',
- 'key',
- 'version',
- 'synced'
- );
+ var notifierData = {};
+ notifierData[itemID] = { changed: this._previousData };
+ Zotero.Notifier.trigger('modify', 'item', itemID, notifierData);
+ }
+
+ //
+ // ItemData
+ //
+ if (this._changed.itemData) {
+ let del = [];
+
+ let valueSQL = "SELECT valueID FROM itemDataValues WHERE value=?";
+ let insertValueSQL = "INSERT INTO itemDataValues VALUES (?,?)";
+ let replaceSQL = "REPLACE INTO itemData VALUES (?,?,?)";
+
+ for (let fieldID in this._changed.itemData) {
+ fieldID = parseInt(fieldID);
+ let value = this.getField(fieldID, true);
- sqlValues.push(
- { int: itemTypeID },
- this.dateAdded ? this.dateAdded : Zotero.DB.transactionDateTime,
- this.libraryID ? this.libraryID : 0,
- key,
- this.version ? this.version : 0,
- this.synced ? 1 : 0
- );
+ // If field changed and is empty, mark row for deletion
+ if (!value) {
+ del.push(fieldID);
+ continue;
+ }
- if (isNew) {
- sqlColumns.push('dateModified', 'clientDateModified');
- sqlValues.push(Zotero.DB.transactionDateTime, Zotero.DB.transactionDateTime);
+ if (Zotero.ItemFields.getID('accessDate') == fieldID
+ && (this.getField(fieldID)) == 'CURRENT_TIMESTAMP') {
+ value = Zotero.DB.transactionDateTime;
}
- else {
- for each (let field in ['dateModified', 'clientDateModified']) {
- switch (field) {
- case 'dateModified':
- case 'clientDateModified':
- let skipFlag = "skip" + field[0].toUpperCase() + field.substr(1) + "Update";
- if (!options[skipFlag]) {
- sqlColumns.push(field);
- sqlValues.push(Zotero.DB.transactionDateTime);
- }
- break;
- }
- }
+
+ let valueID = yield Zotero.DB.valueQueryAsync(valueSQL, [value], { debug: true })
+ if (!valueID) {
+ valueID = yield Zotero.ID.get('itemDataValues');
+ yield Zotero.DB.queryAsync(insertValueSQL, [valueID, value], { debug: false });
}
+ yield Zotero.DB.queryAsync(replaceSQL, [itemID, fieldID, valueID], { debug: false });
+ }
+
+ // Delete blank fields
+ if (del.length) {
+ sql = 'DELETE from itemData WHERE itemID=? AND '
+ + 'fieldID IN (' + del.map(function () '?').join() + ')';
+ yield Zotero.DB.queryAsync(sql, [itemID].concat(del));
+ }
+ }
+
+ //
+ // Creators
+ //
+ if (this._changed.creators) {
+ for (let orderIndex in this._changed.creators) {
+ orderIndex = parseInt(orderIndex);
+
if (isNew) {
- sqlColumns.unshift('itemID');
- sqlValues.unshift(parseInt(itemID));
-
- var sql = "INSERT INTO items (" + sqlColumns.join(", ") + ") "
- + "VALUES (" + sqlValues.map(function () "?").join() + ")";
- var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
- if (!itemID) {
- itemID = insertID;
- }
-
- Zotero.Notifier.trigger('add', 'item', itemID);
+ Zotero.debug('Adding creator in position ' + orderIndex, 4);
}
else {
- var sql = "UPDATE items SET " + sqlColumns.join("=?, ") + "=? WHERE itemID=?";
- sqlValues.push(parseInt(itemID));
- yield Zotero.DB.queryAsync(sql, sqlValues);
-
- var notifierData = {};
- notifierData[itemID] = { changed: this._previousData };
- Zotero.Notifier.trigger('modify', 'item', itemID, notifierData);
+ Zotero.debug('Creator ' + orderIndex + ' has changed', 4);
}
- //
- // ItemData
- //
- if (this._changed.itemData) {
- let del = [];
-
- let valueSQL = "SELECT valueID FROM itemDataValues WHERE value=?";
- let insertValueSQL = "INSERT INTO itemDataValues VALUES (?,?)";
- let replaceSQL = "REPLACE INTO itemData VALUES (?,?,?)";
-
- for (let fieldID in this._changed.itemData) {
- fieldID = parseInt(fieldID);
- let value = this.getField(fieldID, true);
-
- // If field changed and is empty, mark row for deletion
- if (!value) {
- del.push(fieldID);
- continue;
- }
-
- if (Zotero.ItemFields.getID('accessDate') == fieldID
- && (this.getField(fieldID)) == 'CURRENT_TIMESTAMP') {
- value = Zotero.DB.transactionDateTime;
- }
-
- let valueID = yield Zotero.DB.valueQueryAsync(valueSQL, [value], { debug: true })
- if (!valueID) {
- valueID = yield Zotero.ID.get('itemDataValues');
- yield Zotero.DB.queryAsync(insertValueSQL, [valueID, value], { debug: false });
- }
-
- yield Zotero.DB.queryAsync(replaceSQL, [itemID, fieldID, valueID], { debug: false });
- }
-
- // Delete blank fields
- if (del.length) {
- sql = 'DELETE from itemData WHERE itemID=? AND '
- + 'fieldID IN (' + del.map(function () '?').join() + ')';
- yield Zotero.DB.queryAsync(sql, [itemID].concat(del));
- }
+ let creatorData = this.getCreator(orderIndex);
+ // If no creator in this position, just remove the item-creator association
+ if (!creatorData) {
+ let sql = "DELETE FROM itemCreators WHERE itemID=? AND orderIndex=?";
+ yield Zotero.DB.queryAsync(sql, [itemID, orderIndex]);
+ Zotero.Prefs.set('purge.creators', true);
+ continue;
}
- //
- // Creators
- //
- if (this._changed.creators) {
- for (let orderIndex in this._changed.creators) {
- orderIndex = parseInt(orderIndex);
-
- if (isNew) {
- Zotero.debug('Adding creator in position ' + orderIndex, 4);
- }
- else {
- Zotero.debug('Creator ' + orderIndex + ' has changed', 4);
- }
-
- let creatorData = this.getCreator(orderIndex);
- // If no creator in this position, just remove the item-creator association
- if (!creatorData) {
- let sql = "DELETE FROM itemCreators WHERE itemID=? AND orderIndex=?";
- yield Zotero.DB.queryAsync(sql, [itemID, orderIndex]);
- Zotero.Prefs.set('purge.creators', true);
- continue;
- }
-
- let previousCreatorID = this._previousData.creators[orderIndex]
- ? this._previousData.creators[orderIndex].id
- : false;
- let newCreatorID = yield Zotero.Creators.getIDFromData(creatorData, true);
-
- // If there was previously a creator at this position and it's different from
- // the new one, the old one might need to be purged.
- if (previousCreatorID && previousCreatorID != newCreatorID) {
- Zotero.Prefs.set('purge.creators', true);
- }
-
- let sql = "INSERT OR REPLACE INTO itemCreators "
- + "(itemID, creatorID, creatorTypeID, orderIndex) VALUES (?, ?, ?, ?)";
- yield Zotero.DB.queryAsync(
- sql,
- [
- itemID,
- newCreatorID,
- creatorData.creatorTypeID,
- orderIndex
- ]
- );
- }
- }
+ let previousCreatorID = !isNew && this._previousData.creators[orderIndex]
+ ? this._previousData.creators[orderIndex].id
+ : false;
+ let newCreatorID = yield Zotero.Creators.getIDFromData(creatorData, true);
- // Parent item
- let parentItem = this.parentKey;
- parentItem = parentItem ? Zotero.Items.getByLibraryAndKey(this.libraryID, parentItem) : null;
- if (this._changed.parentKey) {
- if (isNew) {
- if (!parentItem) {
- // TODO: clear caches?
- let msg = this._parentKey + " is not a valid item key";
- throw new Zotero.Error(msg, "MISSING_OBJECT");
- }
-
- let newParentItemNotifierData = {};
- //newParentItemNotifierData[newParentItem.id] = {};
- Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData);
-
- switch (Zotero.ItemTypes.getName(itemTypeID)) {
- case 'note':
- case 'attachment':
- reloadParentChildItems[parentItem.id] = true;
- break;
- }
- }
- else {
- let type = Zotero.ItemTypes.getName(itemTypeID);
- let Type = type[0].toUpperCase() + type.substr(1);
-
- if (this._parentKey) {
- if (!parentItem) {
- // TODO: clear caches
- let msg = "Cannot set source to invalid item " + this._parentKey;
- throw new Zotero.Error(msg, "MISSING_OBJECT");
- }
-
- let newParentItemNotifierData = {};
- //newParentItemNotifierData[newParentItem.id] = {};
- Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData);
- }
-
- var oldParentKey = this._previousData.parentKey;
- if (oldParentKey) {
- var oldParentItem = Zotero.Items.getByLibraryAndKey(this.libraryID, oldParentKey);
- if (oldParentItem) {
- let oldParentItemNotifierData = {};
- //oldParentItemNotifierData[oldParentItem.id] = {};
- Zotero.Notifier.trigger('modify', 'item', oldParentItem.id, oldParentItemNotifierData);
- }
- else {
- Zotero.debug("Old source item " + oldParentKey
- + " didn't exist in Zotero.Item.save()", 2);
- }
- }
-
- // If this was an independent item, remove from any collections
- // where it existed previously and add parent instead
- if (!oldParentKey) {
- let sql = "SELECT collectionID FROM collectionItems WHERE itemID=?";
- let changedCollections = yield Zotero.DB.columnQueryAsync(sql, this.id);
- if (changedCollections) {
- for (let i=0; i<changedCollections.length; i++) {
- yield parentItem.loadCollections();
- parentItem.addToCollection(changedCollections[i]);
- yield this.loadCollections();
- this.removeFromCollection(changedCollections[i]);
-
- Zotero.Notifier.trigger(
- 'remove',
- 'collection-item',
- changedCollections[i] + '-' + this.id
- );
- }
- parentItem.save({
- skipDateModifiedUpdate: true
- });
- }
- }
-
- // Update DB, if not a note or attachment we're changing below
- if (!this._changed.attachmentData &&
- (!this._changed.note || !this.isNote())) {
- var sql = "UPDATE item" + Type + "s SET parentItemID=? "
- + "WHERE itemID=?";
- var bindParams = [parentItem ? parentItem.id : null, this.id];
- yield Zotero.DB.queryAsync(sql, bindParams);
- }
-
- // Update the counts of the previous and new sources
- if (oldParentItem) {
- reloadParentChildItems[oldParentItem.id] = true;
- }
- if (parentItem) {
- reloadParentChildItems[parentItem.id] = true;
- }
- }
+ // If there was previously a creator at this position and it's different from
+ // the new one, the old one might need to be purged.
+ if (previousCreatorID && previousCreatorID != newCreatorID) {
+ Zotero.Prefs.set('purge.creators', true);
}
- // Trashed status
- if (this._changed.deleted) {
- if (this.deleted) {
- sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
- }
- else {
- // If undeleting, remove any merge-tracking relations
- var relations = yield Zotero.Relations.getByURIs(
- Zotero.URI.getItemURI(this),
- Zotero.Relations.deletedItemPredicate,
- false
- );
- for each(let relation in relations) {
- relation.erase();
- }
-
- sql = "DELETE FROM deletedItems WHERE itemID=?";
- }
- yield Zotero.DB.queryAsync(sql, itemID);
-
- // Refresh trash
- Zotero.Notifier.trigger('refresh', 'trash', this.libraryID);
- if (this._deleted) {
- Zotero.Notifier.trigger('trash', 'item', this.id);
- }
-
- if (parentItem) {
- reloadParentChildItems[parentItem.id] = true;
- }
+ let sql = "INSERT OR REPLACE INTO itemCreators "
+ + "(itemID, creatorID, creatorTypeID, orderIndex) VALUES (?, ?, ?, ?)";
+ yield Zotero.DB.queryAsync(
+ sql,
+ [
+ itemID,
+ newCreatorID,
+ creatorData.creatorTypeID,
+ orderIndex
+ ]
+ );
+ }
+ }
+
+ // Parent item
+ let parentItem = this.parentKey;
+ parentItem = parentItem ? this.ObjectsClass.getByLibraryAndKey(this.libraryID, parentItem) : null;
+ if (this._changed.parentKey) {
+ if (isNew) {
+ if (!parentItem) {
+ // TODO: clear caches?
+ let msg = this._parentKey + " is not a valid item key";
+ throw new Zotero.Error(msg, "MISSING_OBJECT");
}
- // Note
- if ((isNew && this.isNote()) || this._changed.note) {
- if (!isNew) {
- if (this._noteText === null || this._noteTitle === null) {
- throw new Error("Cached note values not set with "
- + "this._changed.note set to true");
- }
- }
-
- let parent = this.isNote() ? this.parentID : null;
- let noteText = this._noteText ? this._noteText : '';
- // Add <div> wrapper if not present
- if (!noteText.match(/^<div class="zotero-note znv[0-9]+">[\s\S]*<\/div>$/)) {
- // Keep consistent with getNote()
- noteText = '<div class="zotero-note znv1">' + noteText + '</div>';
- }
-
- let params = [
- parent ? parent : null,
- noteText,
- this._noteTitle ? this._noteTitle : ''
- ];
- let sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=?";
- if (yield Zotero.DB.valueQueryAsync(sql, itemID)) {
- sql = "UPDATE itemNotes SET parentItemID=?, note=?, title=? WHERE itemID=?";
- params.push(itemID);
- }
- else {
- sql = "INSERT INTO itemNotes "
- + "(itemID, parentItemID, note, title) VALUES (?,?,?,?)";
- params.unshift(itemID);
- }
- yield Zotero.DB.queryAsync(sql, params);
-
- if (parentItem) {
- reloadParentChildItems[parentItem.id] = true;
- }
- }
+ let newParentItemNotifierData = {};
+ //newParentItemNotifierData[newParentItem.id] = {};
+ Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData);
- //
- // Attachment
- //
- if (!isNew) {
- // If attachment title changes, update parent attachments
- if (this._changed.itemData && this._changed.itemData[110] && this.isAttachment() && parentItem) {
+ switch (Zotero.ItemTypes.getName(itemTypeID)) {
+ case 'note':
+ case 'attachment':
reloadParentChildItems[parentItem.id] = true;
- }
+ break;
}
+ }
+ else {
+ let type = Zotero.ItemTypes.getName(itemTypeID);
+ let Type = type[0].toUpperCase() + type.substr(1);
- if (this.isAttachment() || this._changed.attachmentData) {
- let sql = "REPLACE INTO itemAttachments (itemID, parentItemID, linkMode, "
- + "contentType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)";
- let parent = this.parentID;
- let linkMode = this.attachmentLinkMode;
- let contentType = this.attachmentContentType;
- let charsetID = Zotero.CharacterSets.getID(this.attachmentCharset);
- let path = this.attachmentPath;
- let syncState = this.attachmentSyncState;
-
- if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
- // Save attachment within attachment base directory as relative path
- if (Zotero.Prefs.get('saveRelativeAttachmentPath')) {
- path = Zotero.Attachments.getBaseDirectoryRelativePath(path);
- }
- // If possible, convert relative path to absolute
- else {
- let file = Zotero.Attachments.resolveRelativePath(path);
- if (file) {
- path = file.persistentDescriptor;
- }
- }
+ if (this._parentKey) {
+ if (!parentItem) {
+ // TODO: clear caches
+ let msg = "Cannot set source to invalid item " + this._parentKey;
+ throw new Zotero.Error(msg, "MISSING_OBJECT");
}
- let params = [
- itemID,
- parent ? parent : null,
- { int: linkMode },
- contentType ? { string: contentType } : null,
- charsetID ? { int: charsetID } : null,
- path ? { string: path } : null,
- syncState ? { int: syncState } : 0
- ];
- yield Zotero.DB.queryAsync(sql, params);
-
- // Clear cached child attachments of the parent
- if (!isNew && parentItem) {
- reloadParentChildItems[parentItem.id] = true;
- }
+ let newParentItemNotifierData = {};
+ //newParentItemNotifierData[newParentItem.id] = {};
+ Zotero.Notifier.trigger('modify', 'item', parentItem.id, newParentItemNotifierData);
}
- // Tags
- if (this._changed.tags) {
- let oldTags = this._previousData.tags;
- let newTags = this._tags;
-
- // Convert to individual JSON objects, diff, and convert back
- let oldTagsJSON = oldTags.map(function (x) JSON.stringify(x));
- let newTagsJSON = newTags.map(function (x) JSON.stringify(x));
- let toAdd = Zotero.Utilities.arrayDiff(newTagsJSON, oldTagsJSON)
- .map(function (x) JSON.parse(x));
- let toRemove = Zotero.Utilities.arrayDiff(oldTagsJSON, newTagsJSON)
- .map(function (x) JSON.parse(x));;
-
- for (let i=0; i<toAdd.length; i++) {
- let tag = toAdd[i];
- let tagID = yield Zotero.Tags.getIDFromName(this.libraryID, tag.tag, true);
- // "OR REPLACE" allows changing type
- let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
- yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
- Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + tag.tag);
+ var oldParentKey = this._previousData.parentKey;
+ if (oldParentKey) {
+ var oldParentItem = this.ObjectsClass.getByLibraryAndKey(this.libraryID, oldParentKey);
+ if (oldParentItem) {
+ let oldParentItemNotifierData = {};
+ //oldParentItemNotifierData[oldParentItem.id] = {};
+ Zotero.Notifier.trigger('modify', 'item', oldParentItem.id, oldParentItemNotifierData);
}
-
- if (toRemove.length) {
- yield Zotero.Tags.load(this.libraryID);
- for (let i=0; i<toRemove.length; i++) {
- let tag = toRemove[i];
- let tagID = Zotero.Tags.getID(this.libraryID, tag.tag);
- let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?";
- yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
- Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + tag.tag);
- }
- Zotero.Prefs.set('purge.tags', true);
+ else {
+ Zotero.debug("Old source item " + oldParentKey
+ + " didn't exist in Zotero.Item.save()", 2);
}
}
- // Collections
- if (this._changed.collections) {
- let oldCollections = this._previousData.collections;
- let newCollections = this._collections;
-
- let toAdd = Zotero.Utilities.arrayDiff(newCollections, oldCollections);
- let toRemove = Zotero.Utilities.arrayDiff(oldCollections, newCollections);
-
- for (let i=0; i<toAdd.length; i++) {
- let collectionID = toAdd[i];
-
- let sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) FROM collectionItems "
- + "WHERE collectionID=?";
- let orderIndex = yield Zotero.DB.valueQueryAsync(sql, collectionID);
-
- sql = "INSERT OR IGNORE INTO collectionItems "
- + "(collectionID, itemID, orderIndex) VALUES (?, ?, ?)";
- yield Zotero.DB.queryAsync(sql, [collectionID, this.id, orderIndex]);
-
- Zotero.Collections.refreshChildItems(collectionID);
- Zotero.Notifier.trigger('add', 'collection-item', collectionID + '-' + this.id);
- }
-
- if (toRemove.length) {
- let sql = "DELETE FROM collectionItems WHERE itemID=? AND collectionID IN ("
- + toRemove.join(',')
- + ")";
- yield Zotero.DB.queryAsync(sql, this.id);
-
- for (let i=0; i<toRemove.length; i++) {
- let collectionID = toRemove[i];
- Zotero.Collections.refreshChildItems(collectionID);
- Zotero.Notifier.trigger('remove', 'collection-item', collectionID + '-' + this.id);
+ // If this was an independent item, remove from any collections
+ // where it existed previously and add parent instead
+ if (!oldParentKey) {
+ let sql = "SELECT collectionID FROM collectionItems WHERE itemID=?";
+ let changedCollections = yield Zotero.DB.columnQueryAsync(sql, this.id);
+ if (changedCollections) {
+ for (let i=0; i<changedCollections.length; i++) {
+ yield parentItem.loadCollections();
+ parentItem.addToCollection(changedCollections[i]);
+ yield this.loadCollections();
+ this.removeFromCollection(changedCollections[i]);
+
+ Zotero.Notifier.trigger(
+ 'remove',
+ 'collection-item',
+ changedCollections[i] + '-' + this.id
+ );
}
+ parentItem.save({
+ skipDateModifiedUpdate: true
+ });
}
}
- // Related items
- if (this._changed.relatedItems) {
- var removed = [];
- var newids = [];
- var currentIDs = this._getRelatedItems(true);
-
- for each(var id in currentIDs) {
- newids.push(id);
- }
-
- if (newids.length) {
- var sql = "REPLACE INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
- var replaceStatement = Zotero.DB.getAsyncStatement(sql);
-
- for each(var linkedItemID in newids) {
- replaceStatement.bindInt32Parameter(0, itemID);
- replaceStatement.bindInt32Parameter(1, linkedItemID);
-
- yield Zotero.DB.executeAsyncStatement(replaceStatement);
- }
- }
-
- Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
+ // Update DB, if not a note or attachment we're changing below
+ if (!this._changed.attachmentData &&
+ (!this._changed.note || !this.isNote())) {
+ var sql = "UPDATE item" + Type + "s SET parentItemID=? "
+ + "WHERE itemID=?";
+ var bindParams = [parentItem ? parentItem.id : null, this.id];
+ yield Zotero.DB.queryAsync(sql, bindParams);
}
- // Related items
- if (this._changed.relatedItems) {
- var removed = [];
- var newids = [];
- var currentIDs = this._getRelatedItems(true);
-
- for each(var id in this._previousData.related) {
- if (currentIDs.indexOf(id) == -1) {
- removed.push(id);
- }
- }
- for each(var id in currentIDs) {
- if (this._previousData.related.indexOf(id) != -1) {
- continue;
- }
- newids.push(id);
- }
-
- if (removed.length) {
- var sql = "DELETE FROM itemSeeAlso WHERE itemID=? "
- + "AND linkedItemID IN ("
- + removed.map(function () '?').join()
- + ")";
- yield Zotero.DB.queryAsync(sql, [this.id].concat(removed));
- }
-
- if (newids.length) {
- var sql = "INSERT INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
- var insertStatement = Zotero.DB.getAsyncStatement(sql);
-
- for each(var linkedItemID in newids) {
- insertStatement.bindInt32Parameter(0, this.id);
- insertStatement.bindInt32Parameter(1, linkedItemID);
-
- yield Zotero.DB.executeAsyncStatement(insertStatement);
- }
- }
-
- Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
+ // Update the counts of the previous and new sources
+ if (oldParentItem) {
+ reloadParentChildItems[oldParentItem.id] = true;
+ }
+ if (parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
+ }
+ }
+
+ // Trashed status
+ if (this._changed.deleted) {
+ if (this.deleted) {
+ sql = "REPLACE INTO deletedItems (itemID) VALUES (?)";
+ }
+ else {
+ // If undeleting, remove any merge-tracking relations
+ var relations = yield Zotero.Relations.getByURIs(
+ Zotero.URI.getItemURI(this),
+ Zotero.Relations.deletedItemPredicate,
+ false
+ );
+ for each(let relation in relations) {
+ relation.erase();
}
- // Update child item counts and contents
- if (reloadParentChildItems) {
- for (let parentItemID in reloadParentChildItems) {
- let parentItem = yield Zotero.Items.getAsync(parentItemID);
- yield parentItem.reload(['primaryData', 'childItems'], true);
- parentItem.clearBestAttachmentState();
+ sql = "DELETE FROM deletedItems WHERE itemID=?";
+ }
+ yield Zotero.DB.queryAsync(sql, itemID);
+
+ // Refresh trash
+ Zotero.Notifier.trigger('refresh', 'trash', this.libraryID);
+ if (this._deleted) {
+ Zotero.Notifier.trigger('trash', 'item', this.id);
+ }
+
+ if (parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
+ }
+
+ // Note
+ if ((isNew && this.isNote()) || this._changed.note) {
+ if (!isNew) {
+ if (this._noteText === null || this._noteTitle === null) {
+ throw new Error("Cached note values not set with "
+ + "this._changed.note set to true");
+ }
+ }
+
+ let parent = this.isNote() ? this.parentID : null;
+ let noteText = this._noteText ? this._noteText : '';
+ // Add <div> wrapper if not present
+ if (!noteText.match(/^<div class="zotero-note znv[0-9]+">[\s\S]*<\/div>$/)) {
+ // Keep consistent with getNote()
+ noteText = '<div class="zotero-note znv1">' + noteText + '</div>';
+ }
+
+ let params = [
+ parent ? parent : null,
+ noteText,
+ this._noteTitle ? this._noteTitle : ''
+ ];
+ let sql = "SELECT COUNT(*) FROM itemNotes WHERE itemID=?";
+ if (yield Zotero.DB.valueQueryAsync(sql, itemID)) {
+ sql = "UPDATE itemNotes SET parentItemID=?, note=?, title=? WHERE itemID=?";
+ params.push(itemID);
+ }
+ else {
+ sql = "INSERT INTO itemNotes "
+ + "(itemID, parentItemID, note, title) VALUES (?,?,?,?)";
+ params.unshift(itemID);
+ }
+ yield Zotero.DB.queryAsync(sql, params);
+
+ if (parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
+ }
+
+ //
+ // Attachment
+ //
+ if (!isNew) {
+ // If attachment title changes, update parent attachments
+ if (this._changed.itemData && this._changed.itemData[110] && this.isAttachment() && parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
+ }
+
+ if (this.isAttachment() || this._changed.attachmentData) {
+ let sql = "REPLACE INTO itemAttachments (itemID, parentItemID, linkMode, "
+ + "contentType, charsetID, path, syncState) VALUES (?,?,?,?,?,?,?)";
+ let parent = this.parentID;
+ let linkMode = this.attachmentLinkMode;
+ let contentType = this.attachmentContentType;
+ let charsetID = Zotero.CharacterSets.getID(this.attachmentCharset);
+ let path = this.attachmentPath;
+ let syncState = this.attachmentSyncState;
+
+ if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
+ // Save attachment within attachment base directory as relative path
+ if (Zotero.Prefs.get('saveRelativeAttachmentPath')) {
+ path = Zotero.Attachments.getBaseDirectoryRelativePath(path);
+ }
+ // If possible, convert relative path to absolute
+ else {
+ let file = Zotero.Attachments.resolveRelativePath(path);
+ if (file) {
+ path = file.persistentDescriptor;
}
}
-
- // New items have to be reloaded via Zotero.Items.get(), so mark them as disabled
- if (isNew) {
- var id = this.id;
- this._disabled = true;
- return id;
+ }
+
+ let params = [
+ itemID,
+ parent ? parent : null,
+ { int: linkMode },
+ contentType ? { string: contentType } : null,
+ charsetID ? { int: charsetID } : null,
+ path ? { string: path } : null,
+ syncState ? { int: syncState } : 0
+ ];
+ yield Zotero.DB.queryAsync(sql, params);
+
+ // Clear cached child attachments of the parent
+ if (!isNew && parentItem) {
+ reloadParentChildItems[parentItem.id] = true;
+ }
+ }
+
+ // Tags
+ if (this._changed.tags) {
+ let oldTags = this._previousData.tags;
+ let newTags = this._tags;
+
+ // Convert to individual JSON objects, diff, and convert back
+ let oldTagsJSON = oldTags.map(function (x) JSON.stringify(x));
+ let newTagsJSON = newTags.map(function (x) JSON.stringify(x));
+ let toAdd = Zotero.Utilities.arrayDiff(newTagsJSON, oldTagsJSON)
+ .map(function (x) JSON.parse(x));
+ let toRemove = Zotero.Utilities.arrayDiff(oldTagsJSON, newTagsJSON)
+ .map(function (x) JSON.parse(x));;
+
+ for (let i=0; i<toAdd.length; i++) {
+ let tag = toAdd[i];
+ let tagID = yield Zotero.Tags.getIDFromName(this.libraryID, tag.tag, true);
+ // "OR REPLACE" allows changing type
+ let sql = "INSERT OR REPLACE INTO itemTags (itemID, tagID, type) VALUES (?, ?, ?)";
+ yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
+ Zotero.Notifier.trigger('add', 'item-tag', this.id + '-' + tag.tag);
+ }
+
+ if (toRemove.length) {
+ yield Zotero.Tags.load(this.libraryID);
+ for (let i=0; i<toRemove.length; i++) {
+ let tag = toRemove[i];
+ let tagID = Zotero.Tags.getID(this.libraryID, tag.tag);
+ let sql = "DELETE FROM itemTags WHERE itemID=? AND tagID=? AND type=?";
+ yield Zotero.DB.queryAsync(sql, [this.id, tagID, tag.type ? tag.type : 0]);
+ Zotero.Notifier.trigger('remove', 'item-tag', this.id + '-' + tag.tag);
}
+ Zotero.Prefs.set('purge.tags', true);
+ }
+ }
+
+ // Collections
+ if (this._changed.collections) {
+ let oldCollections = this._previousData.collections || [];
+ let newCollections = this._collections;
+
+ let toAdd = Zotero.Utilities.arrayDiff(newCollections, oldCollections);
+ let toRemove = Zotero.Utilities.arrayDiff(oldCollections, newCollections);
+
+ for (let i=0; i<toAdd.length; i++) {
+ let collectionID = toAdd[i];
- // Always reload primary data. DataObject.reload() only reloads changed data types, so
- // it won't reload, say, dateModified and firstCreator if only creator data was changed
- // and not primaryData.
- yield this.loadPrimaryData(true);
- yield this.reload();
- this._clearChanged();
+ let sql = "SELECT IFNULL(MAX(orderIndex)+1, 0) FROM collectionItems "
+ + "WHERE collectionID=?";
+ let orderIndex = yield Zotero.DB.valueQueryAsync(sql, collectionID);
- return true;
- }.bind(this), transactionOptions);
+ sql = "INSERT OR IGNORE INTO collectionItems "
+ + "(collectionID, itemID, orderIndex) VALUES (?, ?, ?)";
+ yield Zotero.DB.queryAsync(sql, [collectionID, this.id, orderIndex]);
+
+ yield this.ContainerObjectsClass.refreshChildItems(collectionID);
+ Zotero.Notifier.trigger('add', 'collection-item', collectionID + '-' + this.id);
+ }
+
+ if (toRemove.length) {
+ let sql = "DELETE FROM collectionItems WHERE itemID=? AND collectionID IN ("
+ + toRemove.join(',')
+ + ")";
+ yield Zotero.DB.queryAsync(sql, this.id);
+
+ for (let i=0; i<toRemove.length; i++) {
+ let collectionID = toRemove[i];
+ yield this.ContainerObjectsClass.refreshChildItems(collectionID);
+ Zotero.Notifier.trigger('remove', 'collection-item', collectionID + '-' + this.id);
+ }
+ }
}
- catch (e) {
- try {
- yield this.loadPrimaryData(true);
- yield this.reload();
- this._clearChanged();
+
+ // Related items
+ if (this._changed.relatedItems) {
+ var removed = [];
+ var newids = [];
+ var currentIDs = this._getRelatedItems(true);
+
+ for each(var id in currentIDs) {
+ newids.push(id);
}
- catch (e2) {
- Zotero.debug(e2, 1);
+
+ if (newids.length) {
+ var sql = "REPLACE INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
+ var replaceStatement = Zotero.DB.getAsyncStatement(sql);
+
+ for each(var linkedItemID in newids) {
+ replaceStatement.bindInt32Parameter(0, itemID);
+ replaceStatement.bindInt32Parameter(1, linkedItemID);
+
+ yield Zotero.DB.executeAsyncStatement(replaceStatement);
+ }
}
- Zotero.debug(e, 1);
- throw e;
+ Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
+ }
+
+ // Related items
+ if (this._changed.relatedItems) {
+ var removed = [];
+ var newids = [];
+ var currentIDs = this._getRelatedItems(true);
+
+ for each(var id in this._previousData.related) {
+ if (currentIDs.indexOf(id) == -1) {
+ removed.push(id);
+ }
+ }
+ for each(var id in currentIDs) {
+ if (this._previousData.related.indexOf(id) != -1) {
+ continue;
+ }
+ newids.push(id);
+ }
+
+ if (removed.length) {
+ var sql = "DELETE FROM itemSeeAlso WHERE itemID=? "
+ + "AND linkedItemID IN ("
+ + removed.map(function () '?').join()
+ + ")";
+ yield Zotero.DB.queryAsync(sql, [this.id].concat(removed));
+ }
+
+ if (newids.length) {
+ var sql = "INSERT INTO itemSeeAlso (itemID, linkedItemID) VALUES (?,?)";
+ var insertStatement = Zotero.DB.getAsyncStatement(sql);
+
+ for each(var linkedItemID in newids) {
+ insertStatement.bindInt32Parameter(0, this.id);
+ insertStatement.bindInt32Parameter(1, linkedItemID);
+
+ yield Zotero.DB.executeAsyncStatement(insertStatement);
+ }
+ }
+
+ Zotero.Notifier.trigger('modify', 'item', removed.concat(newids));
+ }
+
+ // Update child item counts and contents
+ if (reloadParentChildItems) {
+ for (let parentItemID in reloadParentChildItems) {
+ let parentItem = yield this.ObjectsClass.getAsync(parentItemID);
+ yield parentItem.reload(['primaryData', 'childItems'], true);
+ parentItem.clearBestAttachmentState();
+ }
}
});
+Zotero.Item.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
+ // New items have to be reloaded via Zotero.Items.get(), so mark them as disabled
+ if (env.isNew) {
+ var id = this.id;
+ this._disabled = true;
+ return id;
+ }
+
+ // Always reload primary data. DataObject.reload() only reloads changed data types, so
+ // it won't reload, say, dateModified and firstCreator if only creator data was changed
+ // and not primaryData.
+ yield this.loadPrimaryData(true);
+ yield this.reload();
+ this._clearChanged();
+
+ return true;
+});
/**
* Used by sync code
@@ -2002,9 +1947,9 @@ Zotero.Item.prototype.getNotes = function(includeTrashed) {
// Sort by title if necessary
if (!sortChronologically) {
var collation = Zotero.getLocaleCollation();
- rows.sort(function (a, b) {
- var aTitle = Zotero.Items.getSortTitle(a.title);
- var bTitle = Zotero.Items.getSortTitle(b.title);
+ rows.sort((a, b) => {
+ var aTitle = this.ObjectsClass.getSortTitle(a.title);
+ var bTitle = this.ObjectsClass.getSortTitle(b.title);
return collation.compareString(1, aTitle, bTitle);
});
}
@@ -2483,7 +2428,7 @@ Zotero.Item.prototype._updateAttachmentStates = function (exists) {
}
try {
- var item = Zotero.Items.getByLibraryAndKey(this.libraryID, parentKey);
+ var item = this.ObjectsClass.getByLibraryAndKey(this.libraryID, parentKey);
}
catch (e) {
if (e instanceof Zotero.Exception.UnloadedDataException) {
@@ -2688,39 +2633,39 @@ Zotero.Item.prototype.getAttachmentLinkMode = function() {
* Possible values specified as constants in Zotero.Attachments
* (e.g. Zotero.Attachments.LINK_MODE_LINKED_FILE)
*/
-Zotero.Item.prototype.__defineGetter__('attachmentLinkMode', function () {
- if (!this.isAttachment()) {
- return undefined;
- }
- return this._attachmentLinkMode;
-});
-
-
-Zotero.Item.prototype.__defineSetter__('attachmentLinkMode', function (val) {
- if (!this.isAttachment()) {
- throw (".attachmentLinkMode can only be set for attachment items");
- }
-
- switch (val) {
- case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
- case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
- case Zotero.Attachments.LINK_MODE_LINKED_FILE:
- case Zotero.Attachments.LINK_MODE_LINKED_URL:
- break;
-
- default:
- throw ("Invalid attachment link mode '" + val
- + "' in Zotero.Item.attachmentLinkMode setter");
- }
-
- if (val === this.attachmentLinkMode) {
- return;
- }
- if (!this._changed.attachmentData) {
- this._changed.attachmentData = {};
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentLinkMode', {
+ get: function() {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+ return this._attachmentLinkMode;
+ },
+ set: function(val) {
+ if (!this.isAttachment()) {
+ throw (".attachmentLinkMode can only be set for attachment items");
+ }
+
+ switch (val) {
+ case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
+ case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
+ case Zotero.Attachments.LINK_MODE_LINKED_FILE:
+ case Zotero.Attachments.LINK_MODE_LINKED_URL:
+ break;
+
+ default:
+ throw ("Invalid attachment link mode '" + val
+ + "' in Zotero.Item.attachmentLinkMode setter");
+ }
+
+ if (val === this.attachmentLinkMode) {
+ return;
+ }
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
+ }
+ this._changed.attachmentData.linkMode = true;
+ this._attachmentLinkMode = val;
}
- this._changed.attachmentData.linkMode = true;
- this._attachmentLinkMode = val;
});
@@ -2729,40 +2674,42 @@ Zotero.Item.prototype.getAttachmentMIMEType = function() {
return this.attachmentContentType;
};
-Zotero.Item.prototype.__defineGetter__('attachmentMIMEType', function () {
- Zotero.debug(".attachmentMIMEType deprecated -- use .attachmentContentType");
- return this.attachmentContentType;
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentMIMEType', {
+ get: function() {
+ Zotero.debug(".attachmentMIMEType deprecated -- use .attachmentContentType");
+ return this.attachmentContentType;
+ }
});
/**
* Content type of an attachment (e.g. 'text/plain')
*/
-Zotero.Item.prototype.__defineGetter__('attachmentContentType', function () {
- if (!this.isAttachment()) {
- return undefined;
- }
- return this._attachmentContentType;
-});
-
-
-Zotero.Item.prototype.__defineSetter__('attachmentContentType', function (val) {
- if (!this.isAttachment()) {
- throw (".attachmentContentType can only be set for attachment items");
- }
-
- if (!val) {
- val = '';
- }
-
- if (val == this.attachmentContentType) {
- return;
- }
-
- if (!this._changed.attachmentData) {
- this._changed.attachmentData = {};
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentContentType', {
+ get: function() {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+ return this._attachmentContentType;
+ },
+ set: function(val) {
+ if (!this.isAttachment()) {
+ throw (".attachmentContentType can only be set for attachment items");
+ }
+
+ if (!val) {
+ val = '';
+ }
+
+ if (val == this.attachmentContentType) {
+ return;
+ }
+
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
+ }
+ this._changed.attachmentData.contentType = true;
+ this._attachmentContentType = val;
}
- this._changed.attachmentData.contentType = true;
- this._attachmentContentType = val;
});
@@ -2775,76 +2722,75 @@ Zotero.Item.prototype.getAttachmentCharset = function() {
/**
* Character set of an attachment
*/
-Zotero.Item.prototype.__defineGetter__('attachmentCharset', function () {
- if (!this.isAttachment()) {
- return undefined;
- }
- return this._attachmentCharset
-});
-
-
-Zotero.Item.prototype.__defineSetter__('attachmentCharset', function (val) {
- if (!this.isAttachment()) {
- throw (".attachmentCharset can only be set for attachment items");
- }
-
- var oldVal = this.attachmentCharset;
- if (oldVal) {
- oldVal = Zotero.CharacterSets.getID(oldVal);
- }
- if (!oldVal) {
- oldVal = null;
- }
-
- if (val) {
- val = Zotero.CharacterSets.getID(val);
- }
- if (!val) {
- val = null;
- }
-
- if (val == oldVal) {
- return;
- }
-
- if (!this._changed.attachmentData) {
- this._changed.attachmentData= {};
- }
- this._changed.attachmentData.charset = true;
- this._attachmentCharset = val;
-});
-
-
-Zotero.Item.prototype.__defineGetter__('attachmentPath', function () {
- if (!this.isAttachment()) {
- return undefined;
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentCharset', {
+ get: function() {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+ return this._attachmentCharset
+ },
+ set: function(val) {
+ if (!this.isAttachment()) {
+ throw (".attachmentCharset can only be set for attachment items");
+ }
+
+ var oldVal = this.attachmentCharset;
+ if (oldVal) {
+ oldVal = Zotero.CharacterSets.getID(oldVal);
+ }
+ if (!oldVal) {
+ oldVal = null;
+ }
+
+ if (val) {
+ val = Zotero.CharacterSets.getID(val);
+ }
+ if (!val) {
+ val = null;
+ }
+
+ if (val == oldVal) {
+ return;
+ }
+
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData= {};
+ }
+ this._changed.attachmentData.charset = true;
+ this._attachmentCharset = val;
}
- return this._attachmentPath;
});
-
-Zotero.Item.prototype.__defineSetter__('attachmentPath', function (val) {
- if (!this.isAttachment()) {
- throw (".attachmentPath can only be set for attachment items");
- }
-
- if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
- throw ('attachmentPath cannot be set for link attachments');
- }
-
- if (!val) {
- val = '';
- }
-
- if (val == this.attachmentPath) {
- return;
- }
-
- if (!this._changed.attachmentData) {
- this._changed.attachmentData = {};
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentPath', {
+ get: function() {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+ return this._attachmentPath;
+ },
+ set: function(val) {
+ if (!this.isAttachment()) {
+ throw (".attachmentPath can only be set for attachment items");
+ }
+
+ if (this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ throw ('attachmentPath cannot be set for link attachments');
+ }
+
+ if (!val) {
+ val = '';
+ }
+
+ if (val == this.attachmentPath) {
+ return;
+ }
+
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
+ }
+ this._changed.attachmentData.path = true;
+ this._attachmentPath = val;
}
- this._changed.attachmentData.path = true;
- this._attachmentPath = val;
});
@@ -2865,51 +2811,51 @@ Zotero.Item.prototype.updateAttachmentPath = function () {
};
-Zotero.Item.prototype.__defineGetter__('attachmentSyncState', function () {
- if (!this.isAttachment()) {
- return undefined;
- }
- return this._attachmentSyncState;
-});
-
-
-Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) {
- if (!this.isAttachment()) {
- throw ("attachmentSyncState can only be set for attachment items");
- }
-
- switch (this.attachmentLinkMode) {
- case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
- case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
- break;
-
- default:
- throw ("attachmentSyncState can only be set for snapshots and "
- + "imported files");
- }
-
- switch (val) {
- case Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD:
- case Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD:
- case Zotero.Sync.Storage.SYNC_STATE_IN_SYNC:
- case Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD:
- case Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD:
- break;
-
- default:
- throw ("Invalid sync state '" + val
- + "' in Zotero.Item.attachmentSyncState setter");
- }
-
- if (val == this.attachmentSyncState) {
- return;
- }
-
- if (!this._changed.attachmentData) {
- this._changed.attachmentData = {};
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentSyncState', {
+ get: function() {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+ return this._attachmentSyncState;
+ },
+ set: function(val) {
+ if (!this.isAttachment()) {
+ throw ("attachmentSyncState can only be set for attachment items");
+ }
+
+ switch (this.attachmentLinkMode) {
+ case Zotero.Attachments.LINK_MODE_IMPORTED_URL:
+ case Zotero.Attachments.LINK_MODE_IMPORTED_FILE:
+ break;
+
+ default:
+ throw ("attachmentSyncState can only be set for snapshots and "
+ + "imported files");
+ }
+
+ switch (val) {
+ case Zotero.Sync.Storage.SYNC_STATE_TO_UPLOAD:
+ case Zotero.Sync.Storage.SYNC_STATE_TO_DOWNLOAD:
+ case Zotero.Sync.Storage.SYNC_STATE_IN_SYNC:
+ case Zotero.Sync.Storage.SYNC_STATE_FORCE_UPLOAD:
+ case Zotero.Sync.Storage.SYNC_STATE_FORCE_DOWNLOAD:
+ break;
+
+ default:
+ throw ("Invalid sync state '" + val
+ + "' in Zotero.Item.attachmentSyncState setter");
+ }
+
+ if (val == this.attachmentSyncState) {
+ return;
+ }
+
+ if (!this._changed.attachmentData) {
+ this._changed.attachmentData = {};
+ }
+ this._changed.attachmentData.syncState = true;
+ this._attachmentSyncState = val;
}
- this._changed.attachmentData.syncState = true;
- this._attachmentSyncState = val;
});
@@ -2922,29 +2868,31 @@ Zotero.Item.prototype.__defineSetter__('attachmentSyncState', function (val) {
* @return {Promise<Number|undefined>} File modification time as timestamp in milliseconds,
* or undefined if no file
*/
-Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', Zotero.Promise.coroutine(function* () {
- if (!this.isAttachment()) {
- return undefined;
- }
-
- if (!this.id) {
- return undefined;
- }
-
- var path = yield this.getFilePathAsync();
- if (!path) {
- return undefined;
- }
-
- var fmtime = OS.File.stat(path).lastModificationDate;
-
- if (fmtime < 1) {
- Zotero.debug("File mod time " + fmtime + " is less than 1 -- interpreting as 1", 2);
- fmtime = 1;
- }
-
- return fmtime;
-}));
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentModificationTime', {
+ get: Zotero.Promise.coroutine(function* () {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+
+ if (!this.id) {
+ return undefined;
+ }
+
+ var path = yield this.getFilePathAsync();
+ if (!path) {
+ return undefined;
+ }
+
+ var fmtime = OS.File.stat(path).lastModificationDate;
+
+ if (fmtime < 1) {
+ Zotero.debug("File mod time " + fmtime + " is less than 1 -- interpreting as 1", 2);
+ fmtime = 1;
+ }
+
+ return fmtime;
+ })
+});
/**
@@ -2955,21 +2903,23 @@ Zotero.Item.prototype.__defineGetter__('attachmentModificationTime', Zotero.Prom
*
* @return {String} MD5 hash of file as hex string
*/
-Zotero.Item.prototype.__defineGetter__('attachmentHash', function () {
- if (!this.isAttachment()) {
- return undefined;
- }
-
- if (!this.id) {
- return undefined;
- }
-
- var file = this.getFile();
- if (!file) {
- return undefined;
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentHash', {
+ get: function () {
+ if (!this.isAttachment()) {
+ return undefined;
+ }
+
+ if (!this.id) {
+ return undefined;
+ }
+
+ var file = this.getFile();
+ if (!file) {
+ return undefined;
+ }
+
+ return Zotero.Utilities.Internal.md5(file) || undefined;
}
-
- return Zotero.Utilities.Internal.md5(file) || undefined;
});
@@ -2982,84 +2932,86 @@ Zotero.Item.prototype.__defineGetter__('attachmentHash', function () {
*
* @return {Promise<String>} - A promise for attachment text or empty string if unavailable
*/
-Zotero.Item.prototype.__defineGetter__('attachmentText', Zotero.Promise.coroutine(function* () {
- if (!this.isAttachment()) {
- return undefined;
- }
-
- if (!this.id) {
- return null;
- }
-
- var file = this.getFile();
-
- if (!(yield OS.File.exists(file.path))) {
- file = false;
- }
-
- var cacheFile = Zotero.Fulltext.getItemCacheFile(this);
- if (!file) {
- if (cacheFile.exists()) {
- var str = yield Zotero.File.getContentsAsync(cacheFile);
-
- return str.trim();
+Zotero.defineProperty(Zotero.Item.prototype, 'attachmentText', {
+ get: Zotero.Promise.coroutine(function* () {
+ if (!this.isAttachment()) {
+ return undefined;
}
- return '';
- }
-
- var contentType = this.attachmentContentType;
- if (!contentType) {
- contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
- if (contentType) {
- this.attachmentContentType = contentType;
- yield this.save();
+
+ if (!this.id) {
+ return null;
+ }
+
+ var file = this.getFile();
+
+ if (!(yield OS.File.exists(file.path))) {
+ file = false;
}
- }
-
- var str;
- if (Zotero.Fulltext.isCachedMIMEType(contentType)) {
- var reindex = false;
- if (!cacheFile.exists()) {
- Zotero.debug("Regenerating item " + this.id + " full-text cache file");
- reindex = true;
+ var cacheFile = Zotero.Fulltext.getItemCacheFile(this);
+ if (!file) {
+ if (cacheFile.exists()) {
+ var str = yield Zotero.File.getContentsAsync(cacheFile);
+
+ return str.trim();
+ }
+ return '';
}
- // Fully index item if it's not yet
- else if (!(yield Zotero.Fulltext.isFullyIndexed(this))) {
- Zotero.debug("Item " + this.id + " is not fully indexed -- caching now");
- reindex = true;
+
+ var contentType = this.attachmentContentType;
+ if (!contentType) {
+ contentType = yield Zotero.MIME.getMIMETypeFromFile(file);
+ if (contentType) {
+ this.attachmentContentType = contentType;
+ yield this.save();
+ }
}
- if (reindex) {
- if (!Zotero.Fulltext.pdfConverterIsRegistered()) {
- Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3);
+ var str;
+ if (Zotero.Fulltext.isCachedMIMEType(contentType)) {
+ var reindex = false;
+
+ if (!cacheFile.exists()) {
+ Zotero.debug("Regenerating item " + this.id + " full-text cache file");
+ reindex = true;
+ }
+ // Fully index item if it's not yet
+ else if (!(yield Zotero.Fulltext.isFullyIndexed(this))) {
+ Zotero.debug("Item " + this.id + " is not fully indexed -- caching now");
+ reindex = true;
+ }
+
+ if (reindex) {
+ if (!Zotero.Fulltext.pdfConverterIsRegistered()) {
+ Zotero.debug("PDF converter is unavailable -- returning empty .attachmentText", 3);
+ return '';
+ }
+ yield Zotero.Fulltext.indexItems(this.id, false);
+ }
+
+ if (!cacheFile.exists()) {
+ Zotero.debug("Cache file doesn't exist after indexing -- returning empty .attachmentText");
return '';
}
- yield Zotero.Fulltext.indexItems(this.id, false);
+ str = yield Zotero.File.getContentsAsync(cacheFile);
+ }
+
+ else if (contentType == 'text/html') {
+ str = yield Zotero.File.getContentsAsync(file);
+ str = Zotero.Utilities.unescapeHTML(str);
}
- if (!cacheFile.exists()) {
- Zotero.debug("Cache file doesn't exist after indexing -- returning empty .attachmentText");
+ else if (contentType == 'text/plain') {
+ str = yield Zotero.File.getContentsAsync(file);
+ }
+
+ else {
return '';
}
- str = yield Zotero.File.getContentsAsync(cacheFile);
- }
-
- else if (contentType == 'text/html') {
- str = yield Zotero.File.getContentsAsync(file);
- str = Zotero.Utilities.unescapeHTML(str);
- }
-
- else if (contentType == 'text/plain') {
- str = yield Zotero.File.getContentsAsync(file);
- }
-
- else {
- return '';
- }
-
- return str.trim();
-}));
+
+ return str.trim();
+ })
+});
@@ -3136,7 +3088,7 @@ Zotero.Item.prototype.getBestAttachments = Zotero.Promise.coroutine(function* ()
+ "AND IA.itemID NOT IN (SELECT itemID FROM deletedItems) "
+ "ORDER BY contentType='application/pdf' DESC, value=? DESC, dateAdded ASC";
var itemIDs = yield Zotero.DB.columnQueryAsync(sql, [this.id, Zotero.Attachments.LINK_MODE_LINKED_URL, url]);
- return Zotero.Items.get(itemIDs);
+ return this.ObjectsClass.get(itemIDs);
});
@@ -3392,7 +3344,7 @@ Zotero.Item.prototype.setCollections = function (collectionIDsOrKeys) {
var collectionIDs = collectionIDsOrKeys.map(function (val) {
return parseInt(val) == val
? parseInt(val)
- : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, val);
+ : this.ContainerObjectsClass.getIDFromLibraryAndKey(this.libraryID, val);
}.bind(this));
collectionIDs = Zotero.Utilities.arrayUnique(collectionIDs);
@@ -3417,7 +3369,7 @@ Zotero.Item.prototype.setCollections = function (collectionIDsOrKeys) {
Zotero.Item.prototype.addToCollection = function (collectionIDOrKey) {
var collectionID = parseInt(collectionIDOrKey) == collectionIDOrKey
? parseInt(collectionIDOrKey)
- : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey)
+ : this.ContainerObjectsClass.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey)
if (!collectionID) {
throw new Error("Invalid collection '" + collectionIDOrKey + "'");
@@ -3442,7 +3394,7 @@ Zotero.Item.prototype.addToCollection = function (collectionIDOrKey) {
Zotero.Item.prototype.removeFromCollection = function (collectionIDOrKey) {
var collectionID = parseInt(collectionIDOrKey) == collectionIDOrKey
? parseInt(collectionIDOrKey)
- : Zotero.Collections.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey)
+ : this.ContainerObjectsClass.getIDFromLibraryAndKey(this.libraryID, collectionIDOrKey)
if (!collectionID) {
throw new Error("Invalid collection '" + collectionIDOrKey + "'");
@@ -3591,7 +3543,7 @@ Zotero.Item.prototype.diff = function (item, includeMatches, ignoreFields) {
var thisData = this.serialize();
var otherData = item.serialize();
- var numDiffs = Zotero.Items.diff(thisData, otherData, diff, includeMatches);
+ var numDiffs = this.ObjectsClass.diff(thisData, otherData, diff, includeMatches);
diff[0].creators = [];
diff[1].creators = [];
@@ -3727,7 +3679,7 @@ Zotero.Item.prototype.multiDiff = Zotero.Promise.coroutine(function* (otherItems
let otherItem = otherItems[i];
let diff = [];
let otherData = yield otherItem.toJSON();
- let numDiffs = Zotero.Items.diff(thisData, otherData, diff);
+ let numDiffs = this.ObjectsClass.diff(thisData, otherData, diff);
if (numDiffs) {
for (let field in diff[1]) {
@@ -3778,6 +3730,7 @@ Zotero.Item.prototype.clone = function(libraryID, skipTags) {
var sameLibrary = libraryID == this.libraryID;
var newItem = new Zotero.Item;
+ newItem.libraryID = libraryID;
newItem.setType(this.itemTypeID);
var fieldIDs = this.getUsedFields();
@@ -3838,99 +3791,89 @@ Zotero.Item.prototype.copy = Zotero.Promise.coroutine(function* () {
});;
-/**
- * Delete item from database and clear from Zotero.Items internal array
- *
- * Items.erase() should be used for multiple items
- */
-Zotero.Item.prototype.erase = Zotero.Promise.coroutine(function* () {
- if (!this.id) {
- return false;
- }
-
- Zotero.debug('Deleting item ' + this.id);
+Zotero.Item.prototype._eraseInit = Zotero.Promise.coroutine(function* (env) {
+ var proceed = yield Zotero.Item._super.prototype._eraseInit.apply(this, arguments);
+ if (!proceed) return false;
- var changedItems = [];
- var changedItemsNotifierData = {};
- var deletedItemNotifierData = {};
+ env.deletedItemNotifierData = {};
+ env.deletedItemNotifierData[this.id] = { old: this.toJSON() };
- yield Zotero.DB.executeTransaction(function* () {
- deletedItemNotifierData[this.id] = { old: this.toJSON() };
-
- // Remove item from parent collections
- var parentCollectionIDs = this.collections;
- if (parentCollectionIDs) {
- for (var i=0; i<parentCollectionIDs.length; i++) {
- let parentCollection = yield Zotero.Collections.getAsync(parentCollectionIDs[i]);
- yield parentCollection.removeItem(this.id);
- }
+ return true;
+});
+
+Zotero.Item.prototype._eraseData = Zotero.Promise.coroutine(function* (env) {
+ // Remove item from parent collections
+ var parentCollectionIDs = this.collections;
+ if (parentCollectionIDs) {
+ for (var i=0; i<parentCollectionIDs.length; i++) {
+ let parentCollection = yield Zotero.Collections.getAsync(parentCollectionIDs[i]);
+ yield parentCollection.removeItem(this.id);
}
-
- var parentItem = this.parentKey;
- parentItem = parentItem ? Zotero.Items.getByLibraryAndKey(this.libraryID, parentItem) : null;
-
- // // Delete associated attachment files
- if (this.isAttachment()) {
- let linkMode = this.getAttachmentLinkMode();
- // If link only, nothing to delete
- if (linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
- try {
- let file = Zotero.Attachments.getStorageDirectory(this);
- yield OS.File.removeDir(file.path, {
- ignoreAbsent: true,
- ignorePermissions: true
- });
- }
- catch (e) {
- Zotero.debug(e, 2);
- Components.utils.reportError(e);
- }
+ }
+
+ var parentItem = this.parentKey;
+ parentItem = parentItem ? this.ObjectsClass.getByLibraryAndKey(this.libraryID, parentItem) : null;
+
+ // // Delete associated attachment files
+ if (this.isAttachment()) {
+ let linkMode = this.getAttachmentLinkMode();
+ // If link only, nothing to delete
+ if (linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL) {
+ try {
+ let file = Zotero.Attachments.getStorageDirectory(this);
+ yield OS.File.removeDir(file.path, {
+ ignoreAbsent: true,
+ ignorePermissions: true
+ });
}
- }
- // Regular item
- else {
- let sql = "SELECT itemID FROM itemNotes WHERE parentItemID=?1 UNION "
- + "SELECT itemID FROM itemAttachments WHERE parentItemID=?1";
- let toDelete = yield Zotero.DB.columnQueryAsync(sql, [this.id]);
- for (let i=0; i<toDelete.length; i++) {
- let obj = yield Zotero.Items.getAsync(toDelete[i]);
- yield obj.erase();
+ catch (e) {
+ Zotero.debug(e, 2);
+ Components.utils.reportError(e);
}
}
-
- // Flag related items for notification
- // TEMP: Do something with relations
- /*var relateds = this._getRelatedItems(true);
- for each(var id in relateds) {
- let relatedItem = Zotero.Items.get(id);
- }*/
-
- // Clear fulltext cache
- if (this.isAttachment()) {
- yield Zotero.Fulltext.clearItemWords(this.id);
- //Zotero.Fulltext.clearItemContent(this.id);
- }
-
- // Remove relations (except for merge tracker)
- var uri = Zotero.URI.getItemURI(this);
- yield Zotero.Relations.eraseByURI(uri, [Zotero.Relations.deletedItemPredicate]);
-
- yield Zotero.DB.queryAsync('DELETE FROM items WHERE itemID=?', this.id);
-
- if (parentItem) {
- yield parentItem.reload(['primaryData', 'childItems'], true);
- parentItem.clearBestAttachmentState();
+ }
+ // Regular item
+ else {
+ let sql = "SELECT itemID FROM itemNotes WHERE parentItemID=?1 UNION "
+ + "SELECT itemID FROM itemAttachments WHERE parentItemID=?1";
+ let toDelete = yield Zotero.DB.columnQueryAsync(sql, [this.id]);
+ for (let i=0; i<toDelete.length; i++) {
+ let obj = yield this.ObjectsClass.getAsync(toDelete[i]);
+ yield obj.erase();
}
- }.bind(this));
+ }
+
+ // Flag related items for notification
+ // TEMP: Do something with relations
+ /*var relateds = this._getRelatedItems(true);
+ for each(var id in relateds) {
+ let relatedItem = Zotero.Items.get(id);
+ }*/
+
+ // Clear fulltext cache
+ if (this.isAttachment()) {
+ yield Zotero.Fulltext.clearItemWords(this.id);
+ //Zotero.Fulltext.clearItemContent(this.id);
+ }
+
+ // Remove relations (except for merge tracker)
+ var uri = Zotero.URI.getItemURI(this);
+ yield Zotero.Relations.eraseByURI(uri, [Zotero.Relations.deletedItemPredicate]);
- Zotero.Items.unload(this.id);
+ env.parentItem = parentItem;
+});
+
+Zotero.Item.prototype._erasePreCommit = Zotero.Promise.coroutine(function* (env) {
+ yield Zotero.DB.queryAsync('DELETE FROM items WHERE itemID=?', this.id);
- // Send notification of changed items
- if (changedItems.length) {
- Zotero.Notifier.trigger('modify', 'item', changedItems, changedItemsNotifierData);
+ if (env.parentItem) {
+ yield env.parentItem.reload(['primaryData', 'childItems'], true);
+ env.parentItem.clearBestAttachmentState();
}
- Zotero.Notifier.trigger('delete', 'item', this.id, deletedItemNotifierData);
+ this.ObjectsClass.unload(this.id);
+
+ Zotero.Notifier.trigger('delete', 'item', this.id, env.deletedItemNotifierData);
Zotero.Prefs.set('purge.items', true);
Zotero.Prefs.set('purge.creators', true);
@@ -3962,7 +3905,7 @@ Zotero.Item.prototype.fromJSON = function (json) {
case 'dateAdded':
case 'dateModified':
- item[field] = val;
+ this['_'+field] = val;
break;
case 'tags':
@@ -4011,8 +3954,9 @@ Zotero.Item.prototype.fromJSON = function (json) {
if (!changedFields[field] &&
// Invalid fields will already have been cleared by the type change
Zotero.ItemFields.isValidForType(
- Zotero.ItemFields.getID(field), data.itemTypeID
- )) {
+ Zotero.ItemFields.getID(field), this.itemTypeID
+ )
+ ) {
this.setField(field, false);
}
}
@@ -4021,18 +3965,17 @@ Zotero.Item.prototype.fromJSON = function (json) {
this.deleted = !!json.deleted;
// Creators
- var numCreators = 0;
+ let pos = 0;
if (json.creators) {
- for each (let creator in json.creators) {
- this.setCreator(pos, creator);
- numCreators++;
+ while (pos<json.creators.length) {
+ this.setCreator(pos, json.creators[pos]);
+ pos++;
}
}
// Remove item's remaining creators not in JSON
- var rem = this.numCreators() - numCreators;
- for (let j = 0; j < rem; j++) {
+ while (pos < this.numCreators()) {
// Keep removing last creator
- this.removeCreator(numCreators);
+ this.removeCreator(this.numCreators() - 1);
}
// Both notes and attachments might have parents and notes
@@ -4125,7 +4068,7 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
// Collections
yield this.loadCollections();
obj.collections = this.getCollections().map(function (id) {
- return Zotero.Collections.getLibraryAndKeyFromID(id)[1];
+ return this.ContainerObjectsClass.getLibraryAndKeyFromID(id)[1];
});
// Relations
@@ -4136,9 +4079,9 @@ Zotero.Item.prototype.toJSON = Zotero.Promise.coroutine(function* (options, patc
obj.relations[rel.predicate] = rel.object;
}
var relatedItems = this._getRelatedItems().map(function (key) {
- return Zotero.Items.getIDFromLibraryAndKey(this.libraryID, key);
+ return this.ObjectsClass.getIDFromLibraryAndKey(this.libraryID, key);
}.bind(this)).filter(function (val) val !== false);
- relatedItems = Zotero.Items.get(relatedItems);
+ relatedItems = this.ObjectsClass.get(relatedItems);
var pred = Zotero.Relations.relatedItemPredicate;
for (let i=0; i<relatedItems.length; i++) {
let item = relatedItems[i];
@@ -4269,21 +4212,22 @@ Zotero.Item.prototype.loadItemData = Zotero.Promise.coroutine(function* (reload)
this._loaded.itemData = true;
this._clearChanged('itemData');
- this.loadDisplayTitle(reload);
+ yield this.loadDisplayTitle(reload);
});
Zotero.Item.prototype.loadNote = Zotero.Promise.coroutine(function* (reload) {
- Zotero.debug("Loading note data for item " + this.libraryKey);
-
if (this._loaded.note && !reload) {
return;
}
if (!this.isNote() && !this.isAttachment()) {
- throw new Error("Can only load note for note or attachment item");
+ Zotero.debug("Can only load note for note or attachment item");
+ return;
}
+ Zotero.debug("Loading note data for item " + this.libraryKey);
+
var sql = "SELECT note FROM itemNotes WHERE itemID=?";
var row = yield Zotero.DB.rowQueryAsync(sql, this.id);
if (row) {
@@ -4474,6 +4418,12 @@ Zotero.Item.prototype.loadChildItems = Zotero.Promise.coroutine(function* (reloa
return;
}
+
+ if (this.isNote() || this.isAttachment()) {
+ Zotero.debug("Can only load child items for regular item");
+ return;
+ }
+
// Attachments
this._attachments = {
rows: null,
@@ -4676,7 +4626,7 @@ Zotero.Item.prototype._setRelatedItems = Zotero.Promise.coroutine(function* (ite
continue;
}
- var item = yield Zotero.Items.getAsync(id);
+ var item = yield this.ObjectsClass.getAsync(id);
if (!item) {
throw ("Can't relate item to invalid item " + id
+ " in Zotero.Item._setRelatedItems()");
@@ -4707,7 +4657,7 @@ Zotero.Item.prototype._setRelatedItems = Zotero.Promise.coroutine(function* (ite
newIDs = oldIDs.concat(newIDs);
this._relatedItems = [];
for each(var itemID in newIDs) {
- this._relatedItems.push(yield Zotero.Items.getAsync(itemID));
+ this._relatedItems.push(yield this.ObjectsClass.getAsync(itemID));
}
return true;
});
@@ -4734,7 +4684,7 @@ Zotero.Item.prototype._getOldCreators = function () {
Zotero.Item.prototype._disabledCheck = function () {
if (this._disabled) {
var msg = "New Zotero.Item objects shouldn't be accessed after save -- use Zotero.Items.get()";
- Zotero.debug(msg, 2);
+ Zotero.debug(msg, 2, true);
Components.utils.reportError(msg);
}
}
diff --git a/chrome/content/zotero/xpcom/data/itemFields.js b/chrome/content/zotero/xpcom/data/itemFields.js
@@ -103,7 +103,7 @@ Zotero.ItemFields = new function() {
}
if (typeof field == 'number') {
- return field;
+ return _fields[field] ? field : false;
}
return _fields[field] ? _fields[field]['id'] : false;
diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js
@@ -27,17 +27,16 @@
/*
* Primary interface for accessing Zotero items
*/
-Zotero.Items = new function() {
- Zotero.DataObjects.apply(this, ['item']);
- this.constructor.prototype = new Zotero.DataObjects();
+Zotero.Items = function() {
+ this.constructor = null;
- // Privileged methods
- this.add = add;
- this.getSortTitle = getSortTitle;
+ this._ZDO_object = 'item';
- Object.defineProperty(this, "_primaryDataSQLParts", {
+ // This needs to wait until all Zotero components are loaded to initialize,
+ // but otherwise it can be just a simple property
+ Zotero.defineProperty(this, "_primaryDataSQLParts", {
get: function () {
- return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = {
+ return {
itemID: "O.itemID",
itemTypeID: "O.itemTypeID",
dateAdded: "O.dateAdded",
@@ -88,18 +87,17 @@ Zotero.Items = new function() {
attachmentContentType: "IA.contentType AS attachmentContentType",
attachmentPath: "IA.path AS attachmentPath",
attachmentSyncState: "IA.syncState AS attachmentSyncState"
- });
+ };
}
- });
+ }, {lazy: true});
- // Private members
- var _primaryDataSQLParts;
- var _cachedFields = {};
- var _firstCreatorSQL = '';
- var _sortCreatorSQL = '';
- var _emptyTrashIdleObserver = null;
- var _emptyTrashTimer = null;
+ this._primaryDataSQLFrom = "FROM items O "
+ + "LEFT JOIN itemAttachments IA USING (itemID) "
+ + "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
+ + "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ + "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
+ + "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID)";
/**
* Return items marked as deleted
@@ -215,77 +213,7 @@ Zotero.Items = new function() {
};
- /*
- * Create a new item with optional metadata and pass back the primary reference
- *
- * Using "var item = new Zotero.Item()" and "item.save()" directly results
- * in an orphaned reference to the created item. If other code retrieves the
- * new item with Zotero.Items.get() and modifies it, the original reference
- * will not reflect the changes.
- *
- * Using this method avoids the need to call Zotero.Items.get() after save()
- * in order to get the primary item reference. Since it accepts metadata
- * as a JavaScript object, it also offers a simpler syntax than
- * item.setField() and item.setCreator().
- *
- * Callers with no need for an up-to-date reference after save() (or who
- * don't mind doing an extra Zotero.Items.get()) can use Zotero.Item
- * directly if they prefer.
- *
- * Sample usage:
- *
- * var data = {
- * title: "Shakespeare: The Invention of the Human",
- * publisher: "Riverhead Hardcover",
- * date: '1998-10-26',
- * ISBN: 1573221201,
- * pages: 745,
- * creators: [
- * ['Harold', 'Bloom', 'author']
- * ]
- * };
- * var item = Zotero.Items.add('book', data);
- */
- function add(itemTypeOrID, data) {
- var item = new Zotero.Item(itemTypeOrID);
- for (var field in data) {
- if (field == 'creators') {
- var i = 0;
- for each(var creator in data.creators) {
- // TODO: accept format from toArray()
-
- var fields = {
- firstName: creator[0],
- lastName: creator[1],
- fieldMode: creator[3] ? creator[3] : 0
- };
-
- var creatorDataID = Zotero.Creators.getDataID(fields);
- if (creatorDataID) {
- var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID);
- // TODO: identical creators?
- var creatorID = linkedCreators[0];
- }
- else {
- var creatorObj = new Zotero.Creator;
- creatorObj.setFields(fields);
- var creatorID = creatorObj.save();
- }
-
- item.setCreator(i, Zotero.Creators.get(creatorID), creator[2]);
- i++;
- }
- }
- else {
- item.setField(field, data[field]);
- }
- }
- var id = item.save();
-
- return this.getAsync(id);
- }
-
-
+ this._cachedFields = {};
this.cacheFields = Zotero.Promise.coroutine(function* (libraryID, fields, items) {
if (items && items.length == 0) {
return;
@@ -315,14 +243,14 @@ Zotero.Items = new function() {
var fieldIDs = [];
for each(var field in fields) {
// Check if field already cached
- if (_cachedFields[libraryID] && _cachedFields[libraryID].indexOf(field) != -1) {
+ if (this._cachedFields[libraryID] && this._cachedFields[libraryID].indexOf(field) != -1) {
continue;
}
- if (!_cachedFields[libraryID]) {
- _cachedFields[libraryID] = [];
+ if (!this._cachedFields[libraryID]) {
+ this._cachedFields[libraryID] = [];
}
- _cachedFields[libraryID].push(field);
+ this._cachedFields[libraryID].push(field);
if (this.isPrimaryField(field)) {
primaryFields.push(field);
@@ -472,7 +400,7 @@ Zotero.Items = new function() {
for (let i=0; i<allItemIDs.length; i++) {
let itemID = allItemIDs[i];
let item = this._objectCache[itemID];
- yield this._objectCache[itemID].loadDisplayTitle()
+ yield item.loadDisplayTitle()
}
}
@@ -497,7 +425,7 @@ Zotero.Items = new function() {
// Move child items to master
var ids = otherItem.getAttachments(true).concat(otherItem.getNotes(true));
for each(var id in ids) {
- var attachment = yield Zotero.Items.getAsync(id);
+ var attachment = yield this.getAsync(id);
// TODO: Skip identical children?
@@ -549,7 +477,7 @@ Zotero.Items = new function() {
}
yield item.save();
- });
+ }.bind(this));
};
@@ -604,9 +532,11 @@ Zotero.Items = new function() {
/**
* Start idle observer to delete trashed items older than a certain number of days
*/
+ this._emptyTrashIdleObserver = null;
+ this._emptyTrashTimer = null;
this.startEmptyTrashTimer = function () {
- _emptyTrashIdleObserver = {
- observe: function (subject, topic, data) {
+ this._emptyTrashIdleObserver = {
+ observe: (subject, topic, data) => {
if (topic == 'idle' || topic == 'timer-callback') {
var days = Zotero.Prefs.get('trashAutoEmptyDays');
if (!days) {
@@ -620,20 +550,20 @@ Zotero.Items = new function() {
// TODO: increase number after dealing with slow
// tag.getLinkedItems() call during deletes
var num = 10;
- Zotero.Items.emptyTrash(null, days, num)
- .then(function (deleted) {
+ this.emptyTrash(null, days, num)
+ .then(deleted => {
if (!deleted) {
- _emptyTrashTimer = null;
+ this._emptyTrashTimer = null;
return;
}
// Set a timer to do more every few seconds
- if (!_emptyTrashTimer) {
- _emptyTrashTimer = Components.classes["@mozilla.org/timer;1"]
+ if (!this._emptyTrashTimer) {
+ this._emptyTrashTimer = Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
}
- _emptyTrashTimer.init(
- _emptyTrashIdleObserver.observe,
+ this._emptyTrashTimer.init(
+ this._emptyTrashIdleObserver.observe,
5 * 1000,
Components.interfaces.nsITimer.TYPE_ONE_SHOT
);
@@ -641,8 +571,8 @@ Zotero.Items = new function() {
}
// When no longer idle, cancel timer
else if (topic == 'back') {
- if (_emptyTrashTimer) {
- _emptyTrashTimer.cancel();
+ if (this._emptyTrashTimer) {
+ this._emptyTrashTimer.cancel();
}
}
}
@@ -650,7 +580,7 @@ Zotero.Items = new function() {
var idleService = Components.classes["@mozilla.org/widget/idleservice;1"].
getService(Components.interfaces.nsIIdleService);
- idleService.addIdleObserver(_emptyTrashIdleObserver, 305);
+ idleService.addIdleObserver(this._emptyTrashIdleObserver, 305);
}
@@ -693,28 +623,12 @@ Zotero.Items = new function() {
});
- this.getPrimaryDataSQL = function () {
- return "SELECT "
- + Object.keys(this._primaryDataSQLParts).map((val) => this._primaryDataSQLParts[val]).join(', ')
- + this.primaryDataSQLFrom;
- };
-
-
- this.primaryDataSQLFrom = " FROM items O "
- + "LEFT JOIN itemAttachments IA USING (itemID) "
- + "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) "
- + "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
- + "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) "
- + "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID) "
- + "WHERE 1";
-
-
this._postLoad = function (libraryID, ids) {
if (!ids) {
- if (!_cachedFields[libraryID]) {
- _cachedFields[libraryID] = [];
+ if (!this._cachedFields[libraryID]) {
+ this._cachedFields[libraryID] = [];
}
- _cachedFields[libraryID] = this.primaryFields.concat();
+ this._cachedFields[libraryID] = this.primaryFields.concat();
}
}
@@ -724,6 +638,7 @@ Zotero.Items = new function() {
*
* Why do we do this entirely in SQL? Because we're crazy. Crazy like foxes.
*/
+ var _firstCreatorSQL = '';
function _getFirstCreatorSQL() {
if (_firstCreatorSQL) {
return _firstCreatorSQL;
@@ -828,6 +743,7 @@ Zotero.Items = new function() {
/*
* Generate SQL to retrieve sortCreator field
*/
+ var _sortCreatorSQL = '';
function _getSortCreatorSQL() {
if (_sortCreatorSQL) {
return _sortCreatorSQL;
@@ -947,7 +863,7 @@ Zotero.Items = new function() {
}
- function getSortTitle(title) {
+ this.getSortTitle = function(title) {
if (title === false || title === undefined) {
return '';
}
@@ -956,4 +872,8 @@ Zotero.Items = new function() {
}
return title.replace(/^[\[\'\"](.*)[\'\"\]]?$/, '$1')
}
-}
+
+ Zotero.DataObjects.call(this);
+
+ return this;
+}.bind(Object.create(Zotero.DataObjects.prototype))();
diff --git a/chrome/content/zotero/xpcom/data/libraries.js b/chrome/content/zotero/xpcom/data/libraries.js
@@ -28,7 +28,7 @@ Zotero.Libraries = new function () {
_userLibraryID,
_libraryDataLoaded = false;
- Zotero.Utilities.Internal.defineProperty(this, 'userLibraryID', {
+ Zotero.defineProperty(this, 'userLibraryID', {
get: function() {
if (!_libraryDataLoaded) {
throw new Error("Library data not yet loaded");
@@ -177,4 +177,12 @@ Zotero.Libraries = new function () {
throw new Error("Unsupported library type '" + type + "' in Zotero.Libraries.getName()");
}
}
-}
+
+ this.isGroupLibrary = function (libraryID) {
+ if (!_libraryDataLoaded) {
+ throw new Error("Library data not yet loaded");
+ }
+
+ return this.getType(libraryID) == 'group';
+ }
+}
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js
@@ -23,18 +23,15 @@
***** END LICENSE BLOCK *****
*/
-Zotero.Relations = new function () {
- Zotero.DataObjects.apply(this, ['relation']);
- this.constructor.prototype = new Zotero.DataObjects();
+Zotero.Relations = function () {
+ this.constructor = null;
- this.__defineGetter__('relatedItemPredicate', function () "dc:relation");
- this.__defineGetter__('linkedObjectPredicate', function () "owl:sameAs");
- this.__defineGetter__('deletedItemPredicate', function () 'dc:isReplacedBy');
+ this._ZDO_object = 'relation';
+ this._ZDO_idOnly = true;
- var _namespaces = {
- dc: 'http://purl.org/dc/elements/1.1/',
- owl: 'http://www.w3.org/2002/07/owl#'
- };
+ Zotero.defineProperty(this, 'relatedItemPredicate', {value: 'dc:relation'});
+ Zotero.defineProperty(this, 'linkedObjectPredicate', {value: 'owl:sameAs'});
+ Zotero.defineProperty(this, 'deletedItemPredicate', {value: 'dc:isReplacedBy'});
this.get = function (id) {
if (typeof id != 'number') {
@@ -52,7 +49,7 @@ Zotero.Relations = new function () {
*/
this.getByURIs = Zotero.Promise.coroutine(function* (subject, predicate, object) {
if (predicate) {
- predicate = _getPrefixAndValue(predicate).join(':');
+ predicate = this._getPrefixAndValue(predicate).join(':');
}
if (!subject && !predicate && !object) {
@@ -141,7 +138,7 @@ Zotero.Relations = new function () {
this.add = Zotero.Promise.coroutine(function* (libraryID, subject, predicate, object) {
- predicate = _getPrefixAndValue(predicate).join(':');
+ predicate = this._getPrefixAndValue(predicate).join(':');
var relation = new Zotero.Relation;
if (!libraryID) {
@@ -272,11 +269,15 @@ Zotero.Relations = new function () {
return relation;
}
+ this._namespaces = {
+ dc: 'http://purl.org/dc/elements/1.1/',
+ owl: 'http://www.w3.org/2002/07/owl#'
+ };
- function _getPrefixAndValue(uri) {
+ this._getPrefixAndValue = function(uri) {
var [prefix, value] = uri.split(':');
if (prefix && value) {
- if (!_namespaces[prefix]) {
+ if (!this._namespaces[prefix]) {
throw ("Invalid prefix '" + prefix + "' in Zotero.Relations._getPrefixAndValue()");
}
return [prefix, value];
@@ -290,4 +291,8 @@ Zotero.Relations = new function () {
}
throw ("Invalid namespace in URI '" + uri + "' in Zotero.Relations._getPrefixAndValue()");
}
-}
+
+ Zotero.DataObjects.call(this);
+
+ return this;
+}.bind(Object.create(Zotero.DataObjects.prototype))();
+\ No newline at end of file
diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js
@@ -37,13 +37,10 @@ Zotero.Search = function() {
this._hasPrimaryConditions = false;
}
-Zotero.Search._super = Zotero.DataObject;
-Zotero.Search.prototype = Object.create(Zotero.Search._super.prototype);
-Zotero.Search.constructor = Zotero.Search;
+Zotero.extendClass(Zotero.DataObject, Zotero.Search);
Zotero.Search.prototype._objectType = 'search';
Zotero.Search.prototype._dataTypes = Zotero.Search._super.prototype._dataTypes.concat([
- 'primaryData',
'conditions'
]);
@@ -62,21 +59,33 @@ Zotero.Search.prototype.setName = function(val) {
this.name = val;
}
-
-Zotero.Search.prototype.__defineGetter__('id', function () { return this._get('id'); });
-Zotero.Search.prototype.__defineSetter__('id', function (val) { this._set('id', val); });
-Zotero.Search.prototype.__defineGetter__('libraryID', function () { return this._get('libraryID'); });
-Zotero.Search.prototype.__defineSetter__('libraryID', function (val) { return this._set('libraryID', val); });
-Zotero.Search.prototype.__defineGetter__('key', function () { return this._get('key'); });
-Zotero.Search.prototype.__defineSetter__('key', function (val) { this._set('key', val) });
-Zotero.Search.prototype.__defineGetter__('name', function () { return this._get('name'); });
-Zotero.Search.prototype.__defineSetter__('name', function (val) { this._set('name', val); });
-Zotero.Search.prototype.__defineGetter__('version', function () { return this._get('version'); });
-Zotero.Search.prototype.__defineSetter__('version', function (val) { this._set('version', val); });
-Zotero.Search.prototype.__defineGetter__('synced', function () { return this._get('synced'); });
-Zotero.Search.prototype.__defineSetter__('synced', function (val) { this._set('synced', val); });
-
-Zotero.Search.prototype.__defineGetter__('conditions', function (arr) { this.getSearchConditions(); });
+Zotero.defineProperty(Zotero.Search.prototype, 'id', {
+ get: function() this._get('id'),
+ set: function(val) this._set('id', val)
+});
+Zotero.defineProperty(Zotero.Search.prototype, 'libraryID', {
+ get: function() this._get('libraryID'),
+ set: function(val) this._set('libraryID', val)
+});
+Zotero.defineProperty(Zotero.Search.prototype, 'key', {
+ get: function() this._get('key'),
+ set: function(val) this._set('key', val)
+});
+Zotero.defineProperty(Zotero.Search.prototype, 'name', {
+ get: function() this._get('name'),
+ set: function(val) this._set('name', val)
+});
+Zotero.defineProperty(Zotero.Search.prototype, 'version', {
+ get: function() this._get('version'),
+ set: function(val) this._set('version', val)
+});
+Zotero.defineProperty(Zotero.Search.prototype, 'synced', {
+ get: function() this._get('synced'),
+ set: function(val) this._set('synced', val)
+});
+Zotero.defineProperty(Zotero.Search.prototype, 'conditions', {
+ get: function() this.getSearchConditions()
+});
Zotero.Search.prototype._set = function (field, value) {
if (field == 'id' || field == 'libraryID' || field == 'key') {
@@ -161,152 +170,115 @@ Zotero.Search.prototype.loadFromRow = function (row) {
this._identified = true;
}
+Zotero.Search.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
+ if (!this.name) {
+ throw('Name not provided for saved search');
+ }
+
+ return Zotero.Search._super.prototype._initSave.apply(this, arguments);
+});
-/*
- * Save the search to the DB and return a savedSearchID
- *
- * If there are gaps in the searchConditionIDs, |fixGaps| must be true
- * and the caller must dispose of the search or reload the condition ids,
- * which may change after the save.
- *
- * For new searches, name must be set called before saving
- */
-Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
- try {
- Zotero.Searches.editCheck(this);
-
- if (!this.name) {
- throw('Name not provided for saved search');
+Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
+ var fixGaps = env.options.fixGaps;
+ var isNew = env.isNew;
+
+ var searchID = env.id = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
+ var libraryID = env.libraryID = this.libraryID;
+ var key = env.key = this._key = this.key ? this.key : this._generateKey();
+
+ var columns = [
+ 'savedSearchID',
+ 'savedSearchName',
+ 'clientDateModified',
+ 'libraryID',
+ 'key',
+ 'version',
+ 'synced'
+ ];
+ var placeholders = columns.map(function () '?').join();
+ var sqlValues = [
+ searchID ? { int: searchID } : null,
+ { string: this.name },
+ Zotero.DB.transactionDateTime,
+ this.libraryID ? this.libraryID : 0,
+ key,
+ this.version ? this.version : 0,
+ this.synced ? 1 : 0
+ ];
+
+ var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") "
+ + "VALUES (" + placeholders + ")";
+ var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
+ if (!searchID) {
+ searchID = env.id = insertID;
+ }
+
+ if (!isNew) {
+ var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
+ yield Zotero.DB.queryAsync(sql, this.id);
+ }
+
+ // Close gaps in savedSearchIDs
+ var saveConditions = {};
+ var i = 1;
+ for (var id in this._conditions) {
+ if (!fixGaps && id != i) {
+ Zotero.DB.rollbackTransaction();
+ throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
}
+ saveConditions[i] = this._conditions[id];
+ i++;
+ }
+
+ this._conditions = saveConditions;
+
+ for (var i in this._conditions){
+ var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
+ + "searchConditionID, condition, operator, value, required) "
+ + "VALUES (?,?,?,?,?,?)";
- var isNew = !this.id;
-
- // Register this item's identifiers in Zotero.DataObjects on transaction commit,
- // before other callbacks run
- var searchID, libraryID, key;
- if (isNew) {
- var transactionOptions = {
- onCommit: function () {
- Zotero.Searches.registerIdentifiers(searchID, libraryID, key);
- }
- };
- }
- else {
- var transactionOptions = null;
- }
+ // Convert condition and mode to "condition[/mode]"
+ var condition = this._conditions[i].mode ?
+ this._conditions[i].condition + '/' + this._conditions[i].mode :
+ this._conditions[i].condition
- return Zotero.DB.executeTransaction(function* () {
- searchID = this._id = this.id ? this.id : yield Zotero.ID.get('savedSearches');
- libraryID = this.libraryID;
- key = this._key = this.key ? this.key : this._generateKey();
-
- Zotero.debug("Saving " + (isNew ? 'new ' : '') + "search " + this.id);
-
- var columns = [
- 'savedSearchID',
- 'savedSearchName',
- 'clientDateModified',
- 'libraryID',
- 'key',
- 'version',
- 'synced'
- ];
- var placeholders = columns.map(function () '?').join();
- var sqlValues = [
- searchID ? { int: searchID } : null,
- { string: this.name },
- Zotero.DB.transactionDateTime,
- this.libraryID ? this.libraryID : 0,
- key,
- this.version ? this.version : 0,
- this.synced ? 1 : 0
- ];
-
- var sql = "REPLACE INTO savedSearches (" + columns.join(', ') + ") "
- + "VALUES (" + placeholders + ")";
- var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
- if (!searchID) {
- searchID = insertID;
- }
-
- if (!isNew) {
- var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
- yield Zotero.DB.queryAsync(sql, this.id);
- }
-
- // Close gaps in savedSearchIDs
- var saveConditions = {};
- var i = 1;
- for (var id in this._conditions) {
- if (!fixGaps && id != i) {
- Zotero.DB.rollbackTransaction();
- throw ('searchConditionIDs not contiguous and |fixGaps| not set in save() of saved search ' + this._id);
- }
- saveConditions[i] = this._conditions[id];
- i++;
- }
-
- this._conditions = saveConditions;
-
- for (var i in this._conditions){
- var sql = "INSERT INTO savedSearchConditions (savedSearchID, "
- + "searchConditionID, condition, operator, value, required) "
- + "VALUES (?,?,?,?,?,?)";
-
- // Convert condition and mode to "condition[/mode]"
- var condition = this._conditions[i].mode ?
- this._conditions[i].condition + '/' + this._conditions[i].mode :
- this._conditions[i].condition
-
- var sqlParams = [
- searchID,
- i,
- condition,
- this._conditions[i].operator ? this._conditions[i].operator : null,
- this._conditions[i].value ? this._conditions[i].value : null,
- this._conditions[i].required ? 1 : null
- ];
- yield Zotero.DB.queryAsync(sql, sqlParams);
- }
-
-
- if (isNew) {
- Zotero.Notifier.trigger('add', 'search', this.id);
- }
- else {
- Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
- }
-
- if (isNew && this.libraryID) {
- var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
- var group = yield Zotero.Groups.get(groupID);
- group.clearSearchCache();
- }
-
- if (isNew) {
- var id = this.id;
- this._disabled = true;
- return id;
- }
-
- yield this.reload();
- this._clearChanged();
-
- return isNew ? this.id : true;
- }.bind(this), transactionOptions);
+ var sqlParams = [
+ searchID,
+ i,
+ condition,
+ this._conditions[i].operator ? this._conditions[i].operator : null,
+ this._conditions[i].value ? this._conditions[i].value : null,
+ this._conditions[i].required ? 1 : null
+ ];
+ yield Zotero.DB.queryAsync(sql, sqlParams);
}
- catch (e) {
- try {
- yield this.reload();
- this._clearChanged();
- }
- catch (e2) {
- Zotero.debug(e2, 1);
- }
-
- Zotero.debug(e, 1);
- throw e;
+});
+
+Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
+ var isNew = env.isNew;
+ if (isNew) {
+ Zotero.Notifier.trigger('add', 'search', this.id);
+ }
+ else {
+ Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
}
+
+ if (isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) {
+ var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
+ var group = yield Zotero.Groups.get(groupID);
+ group.clearSearchCache();
+ }
+
+ if (isNew) {
+ var id = this.id;
+ this._disabled = true;
+ return id;
+ }
+
+ yield this.reload();
+ this._clearChanged();
+
+ return isNew ? this.id : true;
});
@@ -1189,7 +1161,7 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
let objLibraryID;
let objKey = condition.value;
let objectType = condition.name == 'collection' ? 'collection' : 'search';
- let objectTypeClass = Zotero.DataObjectUtilities.getClassForObjectType(objectType);
+ let objectTypeClass = Zotero.DataObjectUtilities.getObjectsClassForObjectType(objectType);
// Old-style library-key hash
if (objKey.contains('_')) {
@@ -1665,29 +1637,25 @@ Zotero.Search.prototype._buildQuery = Zotero.Promise.coroutine(function* () {
this._sqlParams = sqlParams.length ? sqlParams : false;
});
-Zotero.Searches = new function(){
- Zotero.DataObjects.apply(this, ['search', 'searches', 'savedSearch', 'savedSearches']);
- this.constructor.prototype = new Zotero.DataObjects();
-
- Object.defineProperty(this, "_primaryDataSQLParts", {
- get: function () {
- return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = {
- savedSearchID: "O.savedSearchID",
- name: "O.savedSearchName",
- libraryID: "O.libraryID",
- key: "O.key",
- version: "O.version",
- synced: "O.synced"
- });
- }
- });
-
-
- var _primaryDataSQLParts;
+Zotero.Searches = function() {
+ this.constructor = null;
+
+ this._ZDO_object = 'search';
+ this._ZDO_id = 'savedSearch';
+ this._ZDO_table = 'savedSearches';
+
+ this._primaryDataSQLParts = {
+ savedSearchID: "O.savedSearchID",
+ name: "O.savedSearchName",
+ libraryID: "O.libraryID",
+ key: "O.key",
+ version: "O.version",
+ synced: "O.synced"
+ }
this.init = Zotero.Promise.coroutine(function* () {
- yield this.constructor.prototype.init.apply(this);
+ yield Zotero.DataObjects.prototype.init.apply(this);
yield Zotero.SearchConditions.init();
});
@@ -1735,6 +1703,8 @@ Zotero.Searches = new function(){
let id = ids[i];
var search = new Zotero.Search;
search.id = id;
+ yield search.loadPrimaryData();
+ yield search.loadConditions();
notifierData[id] = { old: search.serialize() };
var sql = "DELETE FROM savedSearchConditions WHERE savedSearchID=?";
@@ -1756,7 +1726,11 @@ Zotero.Searches = new function(){
+ Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " "
+ "FROM savedSearches O WHERE 1";
}
-}
+
+ Zotero.DataObjects.call(this);
+
+ return this;
+}.bind(Object.create(Zotero.DataObjects.prototype))();
diff --git a/chrome/content/zotero/xpcom/utilities_internal.js b/chrome/content/zotero/xpcom/utilities_internal.js
@@ -493,24 +493,6 @@ Zotero.Utilities.Internal = {
}, 0, 0, null);
return pipe.inputStream;
- },
-
- /**
- * Defines property on the object
- * More compact way to do Object.defineProperty
- *
- * @param {Object} obj Target object
- * @param {String} prop Property to be defined
- * @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true
- */
- "defineProperty": function(obj, prop, desc) {
- if (typeof prop != 'string') throw new Error("Property must be a string");
- var d = { __proto__: null, enumerable: true }; // Enumerable by default
- for (let p in desc) {
- if (!desc.hasOwnProperty(p)) continue;
- d[p] = desc[p];
- }
- Object.defineProperty(obj, prop, d);
}
}
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
@@ -1400,6 +1400,40 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
}
+ /**
+ * Defines property on the object
+ * More compact way to do Object.defineProperty
+ *
+ * @param {Object} obj Target object
+ * @param {String} prop Property to be defined
+ * @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true
+ * @param {Object} opts Options:
+ * lazy {Boolean} If true, the _getter_ is intended for late
+ * initialization of the property. The getter is replaced with a simple
+ * property once initialized.
+ */
+ this.defineProperty = function(obj, prop, desc, opts) {
+ if (typeof prop != 'string') throw new Error("Property must be a string");
+ var d = { __proto__: null, enumerable: true, configurable: true }; // Enumerable by default
+ for (let p in desc) {
+ if (!desc.hasOwnProperty(p)) continue;
+ d[p] = desc[p];
+ }
+
+ if (opts) {
+ if (opts.lazy && d.get) {
+ let getter = d.get;
+ d.get = function() {
+ var val = getter.call(this);
+ this[prop] = val; // Replace getter with value
+ return val;
+ }
+ }
+ }
+
+ Object.defineProperty(obj, prop, d);
+ }
+
/*
* This function should be removed
*
@@ -1497,6 +1531,12 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
};
}
+ this.defineProperty(this, "localeCompare", {
+ get: function() {
+ var collation = this.getLocaleCollation();
+ return collation.compareString.bind(collation, 1);
+ }
+ }, {lazy: true});
/*
* Sets font size based on prefs -- intended for use on root element
@@ -1580,6 +1620,46 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
/**
+ * Defines property on the object
+ * More compact way to do Object.defineProperty
+ *
+ * @param {Object} obj Target object
+ * @param {String} prop Property to be defined
+ * @param {Object} desc Propery descriptor. If not overriden, "enumerable" is true
+ * @param {Object} opts Options:
+ * lateInit {Boolean} If true, the _getter_ is intended for late
+ * initialization of the property. The getter is replaced with a simple
+ * property once initialized.
+ */
+ this.defineProperty = function(obj, prop, desc, opts) {
+ if (typeof prop != 'string') throw new Error("Property must be a string");
+ var d = { __proto__: null, enumerable: true, configurable: true }; // Enumerable by default
+ for (let p in desc) {
+ if (!desc.hasOwnProperty(p)) continue;
+ d[p] = desc[p];
+ }
+
+ if (opts) {
+ if (opts.lateInit && d.get) {
+ let getter = d.get;
+ d.get = function() {
+ var val = getter.call(this);
+ this[prop] = val; // Replace getter with value
+ return val;
+ }
+ }
+ }
+
+ Object.defineProperty(obj, prop, d);
+ }
+
+ this.extendClass = function(superClass, newClass) {
+ newClass._super = superClass;
+ newClass.prototype = Object.create(superClass.prototype);
+ newClass.prototype.constructor = newClass;
+ }
+
+ /**
* Allow other events (e.g., UI updates) on main thread to be processed if necessary
*
* @param {Integer} [timeout=50] Maximum number of milliseconds to wait
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
@@ -1971,6 +1971,7 @@ var ZoteroPane = new function()
}
var self = this;
+ var deferred = Zotero.Promise.defer();
this.collectionsView.addEventListener('load', function () {
Zotero.spawn(function* () {
var currentLibraryID = self.getSelectedLibraryID();
@@ -1993,15 +1994,22 @@ var ZoteroPane = new function()
yield self.collectionsView.selectLibrary(item.libraryID);
yield self.itemsView.selectItem(itemID, expand);
}
+ deferred.resolve(true);
+ })
+ .catch(function(e) {
+ deferred.reject(e);
});
});
+ })
+ .catch(function(e) {
+ deferred.reject(e);
});
});
// open Zotero pane
this.show();
- return true;
+ return deferred.promise;
});