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.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) {
@ -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,168 +248,139 @@ Zotero.Collection.prototype.getChildItems = function (asIDs, includeDeleted) {
return objs;
}
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 (!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');
}
env.parent = newParent.id;
}
else {
env.parent = null;
}
return true;
});
Zotero.Collection.prototype.save = Zotero.Promise.coroutine(function* () {
try {
Zotero.Collections.editCheck(this);
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();
if (!this.name) {
throw new Error('Collection name is empty');
var sql = "REPLACE INTO collections (" + columns.join(', ') + ") "
+ "VALUES (" + placeholders + ")";
var insertID = yield Zotero.DB.queryAsync(sql, sqlValues);
if (!collectionID) {
collectionID = env.id = insertID;
}
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);
// 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);
}
catch (e) {
try {
yield this.reload();
this._clearChanged();
}
catch (e2) {
Zotero.debug(e2, 1);
}
Zotero.debug(e, 1);
throw e;
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
));
}
if (this.parentKey) {
parentIDs.push(this.ObjectsClass.getIDFromLibraryAndKey(
this.libraryID, this.parentKey
));
}
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;
}

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

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]

File diff suppressed because it is too large Load Diff

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,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');
}
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;
}
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);
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;
}
catch (e) {
try {
yield this.reload();
this._clearChanged();
}
catch (e2) {
Zotero.debug(e2, 1);
}
Zotero.debug(e, 1);
throw e;
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);
}
});
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();
Zotero.Searches = function() {
this.constructor = null;
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"
});
}
});
this._ZDO_object = 'search';
this._ZDO_id = 'savedSearch';
this._ZDO_table = 'savedSearches';
var _primaryDataSQLParts;
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))();

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