Inheritance-based Zotero.DataObjects

This commit is contained in:
Aurimas Vinckevicius 2015-01-19 11:00:11 -06:00
parent 707a65c63e
commit 7f5555aab6
5 changed files with 711 additions and 716 deletions

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

File diff suppressed because it is too large Load diff

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,8 +213,7 @@ Zotero.Items = new function() {
};
this._cachedFields = {};
this.cacheFields = Zotero.Promise.coroutine(function* (libraryID, fields, items) {
if (items && items.length == 0) {
return;
@ -246,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);
@ -403,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()
}
}
@ -428,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?
@ -480,7 +477,7 @@ Zotero.Items = new function() {
}
yield item.save();
});
}.bind(this));
};
@ -535,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) {
@ -551,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
);
@ -572,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();
}
}
}
@ -581,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);
}
@ -624,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();
}
}
@ -655,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;
@ -759,6 +743,7 @@ Zotero.Items = new function() {
/*
* Generate SQL to retrieve sortCreator field
*/
var _sortCreatorSQL = '';
function _getSortCreatorSQL() {
if (_sortCreatorSQL) {
return _sortCreatorSQL;
@ -878,7 +863,7 @@ Zotero.Items = new function() {
}
function getSortTitle(title) {
this.getSortTitle = function(title) {
if (title === false || title === undefined) {
return '';
}
@ -887,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

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

@ -1637,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();
});
@ -1730,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))();