Merge pull request #576 from aurimasv/async_db-av2

[Async DB] Modularize Zotero.DataObject.save()
This commit is contained in:
Dan Stillman 2015-01-28 15:07:32 -05:00
commit 66a04a39db
16 changed files with 2430 additions and 2341 deletions

View file

@ -232,7 +232,7 @@
<body>
<![CDATA[
this.updateSearch();
return this.search.save(true);
return this.search.save({fixGaps: true});
]]>
</body>
</method>

View file

@ -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);
}
};

View file

@ -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.defineProperty(Zotero.Collection.prototype, 'ChildObjects', {
get: function() Zotero.Items
});
Zotero.Collection.prototype.__defineGetter__('parent', function (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;
});
Zotero.Collection.prototype.__defineSetter__('parent', function (val) {
},
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) {
@ -114,45 +127,13 @@ 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,48 +248,17 @@ 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');
}
if (Zotero.Utilities.isEmpty(this._changed)) {
Zotero.debug("Collection " + this.id + " has not changed");
return false;
}
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;
}
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);
var proceed = yield Zotero.Collection._super.prototype._initSave.apply(this, arguments);
if (!proceed) return false;
// Verify parent
if (this._parentKey) {
let newParent = Zotero.Collections.getByLibraryAndKey(
let newParent = this.ObjectsClass.getByLibraryAndKey(
this.libraryID, this._parentKey
);
@ -324,12 +274,24 @@ Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
throw ('Cannot move collection "' + this.name + '" into one of its own descendents');
}
var parent = newParent.id;
env.parent = newParent.id;
}
else {
var parent = null;
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',
@ -343,7 +305,7 @@ Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
var sqlValues = [
collectionID ? { int: collectionID } : null,
{ string: this.name },
parent ? parent : null,
env.parent ? env.parent : null,
Zotero.DB.transactionDateTime,
this.libraryID ? this.libraryID : 0,
key,
@ -357,7 +319,7 @@ Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
+ "VALUES (" + placeholders + ")";
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
if (!collectionID) {
collectionID = insertID;
collectionID = env.id = insertID;
}
}
else {
@ -372,21 +334,25 @@ Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
if (this._changed.parentKey) {
var parentIDs = [];
if (this.id && this._previousData.parentKey) {
parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
this.libraryID, this._previousData.parentKey
));
}
if (this.parentKey) {
parentIDs.push(Zotero.Collections.getIDFromLibraryAndKey(
parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
this.libraryID, this.parentKey
));
}
if (this.id) {
Zotero.Notifier.trigger('move', 'collection', this.id);
}
env.parentIDs = parentIDs;
}
});
if (isNew && this.libraryID) {
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();
@ -400,8 +366,8 @@ Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
}
// Invalidate cached child collections
if (parentIDs) {
Zotero.Collections.refreshChildCollections(parentIDs);
if (env.parentIDs) {
this.ObjectsClass.refreshChildCollections(env.parentIDs);
}
// New collections have to be reloaded via Zotero.Collections.get(), so mark them as disabled
@ -415,20 +381,6 @@ Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
this._clearChanged();
return true;
}.bind(this), transactionOptions);
}
catch (e) {
try {
yield this.reload();
this._clearChanged();
}
catch (e2) {
Zotero.debug(e2, 1);
}
Zotero.debug(e, 1);
throw e;
}
});
@ -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;
}

View file

@ -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",
@ -48,6 +49,10 @@ Zotero.Collections = new function() {
+ "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,38 +79,35 @@ 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']);
@ -124,12 +126,22 @@ Zotero.Collections = new function() {
toReturn.push(obj2);
}
}
}
return toReturn;
});
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);
});
}
@ -189,29 +201,20 @@ Zotero.Collections = new function() {
this.erase = function(ids) {
ids = Zotero.flattenArguments(ids);
Zotero.DB.beginTransaction();
return Zotero.DB.executeTransaction(function* () {
for each(var id in ids) {
var collection = this.getAsync(id);
var collection = yield this.getAsync(id);
if (collection) {
collection.erase();
yield collection.erase();
}
collection = undefined;
}
this.unload(ids);
});
};
Zotero.DB.commitTransaction();
}
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";
}
}
Zotero.DataObjects.call(this);
return this;
}.bind(Object.create(Zotero.DataObjects.prototype))();

View file

@ -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

View file

@ -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]

View file

@ -24,59 +24,63 @@
*/
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);
}
// 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);
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);
this._ZDO_id = (id ? id : object) + 'ID';
this._ZDO_table = table ? 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._ZDO_id) {
this._ZDO_id = this._ZDO_object + 'ID';
}
if (!this._ZDO_table) {
this._ZDO_table = this._ZDO_objects;
}
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
this.table = this._ZDO_table;
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});
this.init = function () {
Zotero.DataObjects.prototype.init = function() {
return this._loadIDsAndKeys();
}
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;
});
this.isPrimaryField = function (field) {
Zotero.DataObjects.prototype.isPrimaryField = function (field) {
return this.primaryFields.indexOf(field) != -1;
}
@ -90,7 +94,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @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) {
Zotero.DataObjects.prototype.get = function (ids) {
if (Array.isArray(ids)) {
var singleObject = false;
}
@ -127,14 +131,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @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) {
// Serialize loads
if (this._loadPromise && this._loadPromise.isPending()) {
yield this._loadPromise;
}
var deferred = Zotero.Promise.defer();
this._loadPromise = deferred.promise;
Zotero.DataObjects.prototype.getAsync = Zotero.Promise.coroutine(function* (ids, options) {
var toLoad = [];
var toReturn = [];
@ -163,6 +160,13 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
// New object to load
if (toLoad.length) {
// Serialize loads
if (this._loadPromise && this._loadPromise.isPending()) {
yield this._loadPromise;
}
let deferred = Zotero.Promise.defer();
this._loadPromise = deferred.promise;
let loaded = yield this._load(null, toLoad, options);
for (let i=0; i<toLoad.length; i++) {
let id = toLoad[i];
@ -173,9 +177,8 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
toReturn.push(obj);
}
}
deferred.resolve();
}
// If single id, return the object directly
if (singleObject) {
@ -189,8 +192,8 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
/**
* @deprecated - use .libraryKey
*/
this.makeLibraryKeyHash = function (libraryID, key) {
Zotero.debug("WARNING: Zotero.DataObjects.makeLibraryKeyHash() is deprecated -- use obj.libraryKey instead");
Zotero.DataObjects.prototype.makeLibraryKeyHash = function (libraryID, key) {
Zotero.debug("WARNING: " + this._ZDO_Objects + ".makeLibraryKeyHash() is deprecated -- use .libraryKey instead");
return libraryID + '_' + key;
}
@ -198,13 +201,13 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
/**
* @deprecated - use .libraryKey
*/
this.getLibraryKeyHash = function (obj) {
Zotero.debug("WARNING: Zotero.DataObjects.getLibraryKeyHash() is deprecated -- use obj.libraryKey instead");
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);
}
this.parseLibraryKey = function (libraryKey) {
Zotero.DataObjects.prototype.parseLibraryKey = function (libraryKey) {
var [libraryID, key] = libraryKey.split('/');
return {
libraryID: parseInt(libraryID),
@ -216,8 +219,8 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
/**
* @deprecated - Use Zotero.DataObjects.parseLibraryKey()
*/
this.parseLibraryKeyHash = function (libraryKey) {
Zotero.debug("WARNING: Zotero.DataObjects.parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead");
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;
@ -236,7 +239,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @param {String} key
* @return {Zotero.DataObject} Zotero data object, or FALSE if not found
*/
this.getByLibraryAndKey = function (libraryID, key, options) {
Zotero.DataObjects.prototype.getByLibraryAndKey = function (libraryID, key, options) {
var id = this.getIDFromLibraryAndKey(libraryID, key);
if (!id) {
return false;
@ -252,7 +255,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @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) {
Zotero.DataObjects.prototype.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) {
var id = this.getIDFromLibraryAndKey(libraryID, key);
if (!id) {
return false;
@ -261,7 +264,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
});
this.exists = function (itemID) {
Zotero.DataObjects.prototype.exists = function (itemID) {
return !!this.getLibraryAndKeyFromID(itemID);
}
@ -269,12 +272,12 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
/**
* @return {Array} Array with libraryID and key
*/
this.getLibraryAndKeyFromID = function (id) {
Zotero.DataObjects.prototype.getLibraryAndKeyFromID = function (id) {
return this._objectKeys[id] ? this._objectKeys[id] : false;
}
this.getIDFromLibraryAndKey = function (libraryID, key) {
Zotero.DataObjects.prototype.getIDFromLibraryAndKey = function (libraryID, key) {
if (libraryID === null) {
throw new Error("libraryID cannot be NULL (did you mean 0?)");
}
@ -283,7 +286,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this.getOlder = function (libraryID, date) {
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()")
@ -295,7 +298,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this.getNewer = function (libraryID, date, ignoreFutureDates) {
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()")
@ -314,7 +317,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @param {Integer} libraryID
* @return {Promise} A promise for an array of object ids
*/
this.getUnsynced = function (libraryID) {
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]);
@ -328,7 +331,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @param {Integer} libraryID
* @return {Promise} A promise for an array of JSON objects
*/
this.getUnwrittenData = function (libraryID) {
Zotero.DataObjects.prototype.getUnwrittenData = function (libraryID) {
var sql = "SELECT data FROM syncCache SC "
+ "LEFT JOIN " + this._ZDO_table + " "
+ "USING (libraryID) "
@ -350,7 +353,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* 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) {
Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) {
ids = Zotero.flattenArguments(ids);
Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '')
@ -366,7 +369,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
});
this.reloadAll = function (libraryID) {
Zotero.DataObjects.prototype.reloadAll = function (libraryID) {
Zotero.debug("Reloading all " + this._ZDO_objects);
// Remove objects not stored in database
@ -391,7 +394,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this.registerIdentifiers = function (id, libraryID, key) {
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] = {};
@ -406,7 +409,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
*
* @param int[] ids objectIDs
*/
this.unload = function () {
Zotero.DataObjects.prototype.unload = function () {
var ids = Zotero.flattenArguments(arguments);
for (var i=0; i<ids.length; i++) {
let id = ids[i];
@ -427,7 +430,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
* @param {Boolean} [includeMatches=false] - Include all fields, even those
* that aren't different
*/
this.diff = function (data1, data2, diff, includeMatches) {
Zotero.DataObjects.prototype.diff = function (data1, data2, diff, includeMatches) {
diff.push({}, {});
var numDiffs = 0;
@ -486,43 +489,43 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this.isEditable = function (obj) {
Zotero.DataObjects.prototype.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 (!Zotero.Libraries.isEditable(libraryID)) 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;
obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)
&& !Zotero.Libraries.isFilesEditable(libraryID)
) {
return false;
}
return true;
default:
throw ("Unsupported library type '" + type + "' in Zotero.DataObjects.isEditable()");
}
}
this.editCheck = function (obj) {
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});
this.getPrimaryDataSQLPart = function (part) {
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 + "'");
@ -531,7 +534,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
this._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) {
var loaded = {};
// If library isn't an integer (presumably false or null), skip it
@ -548,7 +551,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
}
// getPrimaryDataSQL() should use "O" for the primary table alias
var sql = this.getPrimaryDataSQL();
var sql = this.primaryDataSQL;
var params = [];
if (libraryID !== false) {
sql += ' AND O.libraryID=?';
@ -614,7 +617,7 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
});
this._loadIDsAndKeys = Zotero.Promise.coroutine(function* () {
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++) {
@ -626,4 +629,3 @@ Zotero.DataObjects = function (object, objectPlural, id, table) {
this._objectIDs[row.libraryID][row.key] = row.id;
}
});
}

File diff suppressed because it is too large Load diff

View file

@ -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;

View file

@ -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))();

View file

@ -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';
}
}

View file

@ -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))();

View file

@ -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,46 +170,21 @@ Zotero.Search.prototype.loadFromRow = function (row) {
this._identified = true;
}
/*
* 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);
Zotero.Search.prototype._initSave = Zotero.Promise.coroutine(function* (env) {
if (!this.name) {
throw('Name not provided for saved search');
}
var isNew = !this.id;
return Zotero.Search._super.prototype._initSave.apply(this, arguments);
});
// 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;
}
Zotero.Search.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
var fixGaps = env.options.fixGaps;
var isNew = env.isNew;
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 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',
@ -226,7 +210,7 @@ Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
+ "VALUES (" + placeholders + ")";
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
if (!searchID) {
searchID = insertID;
searchID = env.id = insertID;
}
if (!isNew) {
@ -268,8 +252,10 @@ Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
];
yield Zotero.DB.queryAsync(sql, sqlParams);
}
});
Zotero.Search.prototype._finalizeSave = Zotero.Promise.coroutine(function* (env) {
var isNew = env.isNew;
if (isNew) {
Zotero.Notifier.trigger('add', 'search', this.id);
}
@ -277,7 +263,7 @@ Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
Zotero.Notifier.trigger('modify', 'search', this.id, this._previousData);
}
if (isNew && this.libraryID) {
if (isNew && Zotero.Libraries.isGroupLibrary(this.libraryID)) {
var groupID = Zotero.Groups.getGroupIDFromLibraryID(this.libraryID);
var group = yield Zotero.Groups.get(groupID);
group.clearSearchCache();
@ -293,20 +279,6 @@ Zotero.Search.prototype.save = Zotero.Promise.coroutine(function* (fixGaps) {
this._clearChanged();
return isNew ? this.id : true;
}.bind(this), transactionOptions);
}
catch (e) {
try {
yield this.reload();
this._clearChanged();
}
catch (e2) {
Zotero.debug(e2, 1);
}
Zotero.debug(e, 1);
throw e;
}
});
@ -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();
Zotero.Searches = function() {
this.constructor = null;
Object.defineProperty(this, "_primaryDataSQLParts", {
get: function () {
return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = {
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"
});
}
});
var _primaryDataSQLParts;
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))();

View file

@ -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);
}
}

View file

@ -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
@ -1579,6 +1619,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
*

View file

@ -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;
});