www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit 7f5555aab6efb03226f38aecf524790aea00d801
parent 707a65c63e8a5ac816f38e4e175a83ce67f67609
Author: Aurimas Vinckevicius <aurimas.dev@gmail.com>
Date:   Mon, 19 Jan 2015 11:00:11 -0600

Inheritance-based Zotero.DataObjects

Diffstat:
Mchrome/content/zotero/xpcom/data/collections.js | 139++++++++++++++++++++++++++++++++++++++++---------------------------------------
Mchrome/content/zotero/xpcom/data/dataObjects.js | 1048+++++++++++++++++++++++++++++++++++++++----------------------------------------
Mchrome/content/zotero/xpcom/data/items.js | 107+++++++++++++++++++++++++++++++++++--------------------------------------------
Mchrome/content/zotero/xpcom/data/relations.js | 36+++++++++++++++++++++---------------
Mchrome/content/zotero/xpcom/search.js | 42+++++++++++++++++++++---------------------
5 files changed, 684 insertions(+), 688 deletions(-)

diff --git a/chrome/content/zotero/xpcom/data/collections.js b/chrome/content/zotero/xpcom/data/collections.js @@ -27,9 +27,10 @@ /* * Primary interface for accessing Zotero collection */ -Zotero.Collections = new function() { - Zotero.DataObjects.apply(this, ['collection']); - this.constructor.prototype = new Zotero.DataObjects(); +Zotero.Collections = function() { + this.constructor = null; + + this._ZDO_object = 'collection'; this._primaryDataSQLParts = { collectionID: "O.collectionID", @@ -45,9 +46,13 @@ Zotero.Collections = new function() { hasChildCollections: "(SELECT COUNT(*) FROM collections WHERE " + "parentCollectionID=O.collectionID) != 0 AS hasChildCollections", hasChildItems: "(SELECT COUNT(*) FROM collectionItems WHERE " - + "collectionID=O.collectionID) != 0 AS hasChildItems " + + "collectionID=O.collectionID) != 0 AS hasChildItems" }; + + this._primaryDataSQLFrom = "FROM collections O " + + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID)"; + /** * Add new collection to DB and return Collection object * @@ -74,55 +79,51 @@ Zotero.Collections = new function() { * Takes parent collectionID as optional parameter; * by default, returns root collections */ - this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parent, recursive) { - var toReturn = []; + this.getByParent = Zotero.Promise.coroutine(function* (libraryID, parentID, recursive) { + let children; - if (!parent) { - parent = null; + if (parentID) { + let parent = yield this.getAsync(parentID); + yield parent.loadChildCollections(); + children = parent.getChildCollections(); + if (!children.length) Zotero.debug('No child collections in collection ' + parentID, 5); + } else if (libraryID || libraryID === 0) { + children = this.getCollectionsInLibrary(libraryID); + if (!children.length) Zotero.debug('No child collections in library ' + libraryID, 5); + } else { + throw new Error("Either library ID or parent collection ID must be provided to getNumCollectionsByParent"); } - var sql = "SELECT collectionID AS id, collectionName AS name FROM collections C " - + "WHERE libraryID=? AND parentCollectionID " + (parent ? '= ' + parent : 'IS NULL'); - var children = yield Zotero.DB.queryAsync(sql, [libraryID]); - - if (!children) { - Zotero.debug('No child collections of collection ' + parent, 5); - return toReturn; + if (!children.length) { + return children; } // Do proper collation sort - var collation = Zotero.getLocaleCollation(); - children.sort(function (a, b) { - return collation.compareString(1, a.name, b.name); - }); + children.sort(function (a, b) Zotero.localeCompare(a.name, b.name)); + + if (!recursive) return children; + let toReturn = []; for (var i=0, len=children.length; i<len; i++) { - var obj = yield this.getAsync(children[i].id); - if (!obj) { - throw ('Collection ' + children[i].id + ' not found'); - } - + var obj = children[i]; toReturn.push(obj); - // If recursive, get descendents - if (recursive) { - var desc = obj.getDescendents(false, 'collection'); - for (var j in desc) { - var obj2 = yield this.getAsync(desc[j]['id']); - if (!obj2) { - throw new Error('Collection ' + desc[j] + ' not found'); - } - - // TODO: This is a quick hack so that we can indent subcollections - // in the search dialog -- ideally collections would have a - // getLevel() method, but there's no particularly quick way - // of calculating that without either storing it in the DB or - // changing the schema to Modified Preorder Tree Traversal, - // and I don't know if we'll actually need it anywhere else. - obj2.level = desc[j].level; - - toReturn.push(obj2); + var desc = obj.getDescendents(false, 'collection'); + for (var j in desc) { + var obj2 = yield this.getAsync(desc[j]['id']); + if (!obj2) { + throw new Error('Collection ' + desc[j] + ' not found'); } + + // TODO: This is a quick hack so that we can indent subcollections + // in the search dialog -- ideally collections would have a + // getLevel() method, but there's no particularly quick way + // of calculating that without either storing it in the DB or + // changing the schema to Modified Preorder Tree Traversal, + // and I don't know if we'll actually need it anywhere else. + obj2.level = desc[j].level; + + toReturn.push(obj2); } } @@ -130,6 +131,17 @@ Zotero.Collections = new function() { }); + this.getCollectionsInLibrary = Zotero.Promise.coroutine(function* (libraryID) { + let sql = "SELECT collectionID AS id FROM collections C " + + "WHERE libraryID=? AND parentCollectionId IS NULL"; + let ids = yield Zotero.DB.queryAsync(sql, [libraryID]); + let collections = yield this.getAsync(ids.map(function(row) row.id)); + if (!collections.length) return collections; + + return collections.sort(function (a, b) Zotero.localeCompare(a.name, b.name)); + }); + + this.getCollectionsContainingItems = function (itemIDs, asIDs) { // If an unreasonable number of items, don't try if (itemIDs.length > 100) { @@ -145,8 +157,8 @@ Zotero.Collections = new function() { } sql = sql.substring(0, sql.length - 5); return Zotero.DB.columnQueryAsync(sql, sqlParams) - .then(function (collectionIDs) { - return asIDs ? collectionIDs : Zotero.Collections.get(collectionIDs); + .then(collectionIDs => { + return asIDs ? collectionIDs : this.get(collectionIDs); }); } @@ -186,32 +198,23 @@ Zotero.Collections = new function() { }); - this.erase = function (ids) { + this.erase = function(ids) { ids = Zotero.flattenArguments(ids); - Zotero.DB.beginTransaction(); - for each(var id in ids) { - var collection = this.getAsync(id); - if (collection) { - collection.erase(); + return Zotero.DB.executeTransaction(function* () { + for each(var id in ids) { + var collection = yield this.getAsync(id); + if (collection) { + yield collection.erase(); + } + collection = undefined; } - collection = undefined; - } - - this.unload(ids); - - Zotero.DB.commitTransaction(); - } + + this.unload(ids); + }); + }; + Zotero.DataObjects.call(this); - this.getPrimaryDataSQL = function () { - // This should be the same as the query in Zotero.Collection.load(), - // just without a specific collectionID - return "SELECT " - + Object.keys(this._primaryDataSQLParts).map(key => this._primaryDataSQLParts[key]).join(", ") + " " - + "FROM collections O " - + "LEFT JOIN collections CP ON (O.parentCollectionID=CP.collectionID) " - + "WHERE 1"; - } -} - + return this; +}.bind(Object.create(Zotero.DataObjects.prototype))(); diff --git a/chrome/content/zotero/xpcom/data/dataObjects.js b/chrome/content/zotero/xpcom/data/dataObjects.js @@ -24,610 +24,608 @@ */ -Zotero.DataObjects = function (object, objectPlural, id, table) { - var self = this; - - if (!object) { - 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); - 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; - } - - Zotero.defineProperty(this, 'idColumn', { - get: function() this._ZDO_id - }); +Zotero.DataObjects = function () { + if (!this._ZDO_object) throw new Error('this._ZDO_object must be set before calling Zotero.DataObjects constructor'); + + if (!this._ZDO_objects) { + this._ZDO_objects = Zotero.DataObjectUtilities.getObjectTypePlural(this._ZDO_object); + } + if (!this._ZDO_Object) { + this._ZDO_Object = this._ZDO_object.substr(0, 1).toUpperCase() + + this._ZDO_object.substr(1); + } + if (!this._ZDO_Objects) { + this._ZDO_Objects = this._ZDO_objects.substr(0, 1).toUpperCase() + + this._ZDO_objects.substr(1); + } + + if (!this._ZDO_id) { + this._ZDO_id = this._ZDO_object + 'ID'; + } + + 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 +Zotero.defineProperty(Zotero.DataObjects.prototype, 'idColumn', { + get: function() this._ZDO_id +}); +Zotero.defineProperty(Zotero.DataObjects.prototype, 'table', { + get: function() this._ZDO_table +}); + +Zotero.defineProperty(Zotero.DataObjects.prototype, 'primaryFields', { + get: function () Object.keys(this._primaryDataSQLParts) +}, {lazy: true}); + + +Zotero.DataObjects.prototype.init = function() { + return this._loadIDsAndKeys(); +} + + +Zotero.DataObjects.prototype.isPrimaryField = function (field) { + return this.primaryFields.indexOf(field) != -1; +} + + +/** + * Retrieves one or more already-loaded items + * + * If an item hasn't been loaded, an error is thrown + * + * @param {Array|Integer} ids An individual object id or an array of object ids + * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed; + * otherwise, an array of Zotero.[Object] + */ +Zotero.DataObjects.prototype.get = function (ids) { + if (Array.isArray(ids)) { + var singleObject = false; + } + else { + var singleObject = true; + ids = [ids]; + } - // Public properties - this.table = this._ZDO_table; + var toReturn = []; + for (let i=0; i<ids.length; i++) { + let id = ids[i]; + // Check if already loaded + if (!this._objectCache[id]) { + throw new Zotero.Exception.UnloadedDataException(this._ZDO_Object + " " + id + " not yet loaded"); + } + toReturn.push(this._objectCache[id]); + } - this.init = function () { - return this._loadIDsAndKeys(); + // If single id, return the object directly + if (singleObject) { + return toReturn.length ? toReturn[0] : false; } + return toReturn; +}; - this.__defineGetter__('primaryFields', function () { - var primaryFields = Object.keys(this._primaryDataSQLParts); - - // Once primary fields have been cached, get rid of getter for speed purposes - delete this.primaryFields; - this.primaryFields = primaryFields; - - return primaryFields; - }); +/** + * Retrieves (and loads, if necessary) one or more items + * + * @param {Array|Integer} ids An individual object id or an array of object ids + * @param {Object} options 'noCache': Don't cache loaded objects + * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed; + * otherwise, an array of Zotero.[Object] + */ +Zotero.DataObjects.prototype.getAsync = Zotero.Promise.coroutine(function* (ids, options) { + var toLoad = []; + var toReturn = []; - this.isPrimaryField = function (field) { - return this.primaryFields.indexOf(field) != -1; + if (!ids) { + throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()"); } + if (Array.isArray(ids)) { + var singleObject = false; + } + else { + var singleObject = true; + ids = [ids]; + } - /** - * Retrieves one or more already-loaded items - * - * If an item hasn't been loaded, an error is thrown - * - * @param {Array|Integer} ids An individual object id or an array of object ids - * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed; - * otherwise, an array of Zotero.[Object] - */ - this.get = function (ids) { - if (Array.isArray(ids)) { - var singleObject = false; - } - else { - var singleObject = true; - ids = [ids]; - } - - var toReturn = []; - - for (let i=0; i<ids.length; i++) { - let id = ids[i]; - // Check if already loaded - if (!this._objectCache[id]) { - throw new Zotero.Exception.UnloadedDataException(this._ZDO_Object + " " + id + " not yet loaded"); - } + for (let i=0; i<ids.length; i++) { + let id = ids[i]; + // Check if already loaded + if (this._objectCache[id]) { toReturn.push(this._objectCache[id]); } - - // If single id, return the object directly - if (singleObject) { - return toReturn.length ? toReturn[0] : false; + else { + toLoad.push(id); } - - return toReturn; - }; - + } - /** - * Retrieves (and loads, if necessary) one or more items - * - * @param {Array|Integer} ids An individual object id or an array of object ids - * @param {Object} options 'noCache': Don't cache loaded objects - * @return {Zotero.[Object]|Array<Zotero.[Object]>} A Zotero.[Object], if a scalar id was passed; - * otherwise, an array of Zotero.[Object] - */ - this.getAsync = Zotero.Promise.coroutine(function* (ids, options) { + // New object to load + if (toLoad.length) { // Serialize loads if (this._loadPromise && this._loadPromise.isPending()) { yield this._loadPromise; } - var deferred = Zotero.Promise.defer(); + let deferred = Zotero.Promise.defer(); this._loadPromise = deferred.promise; - var toLoad = []; - var toReturn = []; - - if (!ids) { - throw new Error("No arguments provided to " + this._ZDO_Objects + ".get()"); - } - - if (Array.isArray(ids)) { - var singleObject = false; - } - else { - var singleObject = true; - ids = [ids]; - } - - for (let i=0; i<ids.length; i++) { - let id = ids[i]; - // Check if already loaded - if (this._objectCache[id]) { - toReturn.push(this._objectCache[id]); - } - else { - toLoad.push(id); - } - } - - // New object to load - if (toLoad.length) { - let loaded = yield this._load(null, toLoad, options); - for (let i=0; i<toLoad.length; i++) { - let id = toLoad[i]; - let obj = loaded[id]; - if (!obj) { - Zotero.debug(this._ZDO_Object + " " + id + " doesn't exist", 2); - continue; - } - toReturn.push(obj); + let loaded = yield this._load(null, toLoad, options); + for (let i=0; i<toLoad.length; i++) { + let id = toLoad[i]; + let obj = loaded[id]; + if (!obj) { + Zotero.debug(this._ZDO_Object + " " + id + " doesn't exist", 2); + continue; } + toReturn.push(obj); } - deferred.resolve(); - - // If single id, return the object directly - if (singleObject) { - return toReturn.length ? toReturn[0] : false; - } - - return toReturn; - }); - - - /** - * @deprecated - use .libraryKey - */ - this.makeLibraryKeyHash = function (libraryID, key) { - Zotero.debug("WARNING: Zotero.DataObjects.makeLibraryKeyHash() is deprecated -- use obj.libraryKey instead"); - return libraryID + '_' + key; } - - /** - * @deprecated - use .libraryKey - */ - this.getLibraryKeyHash = function (obj) { - Zotero.debug("WARNING: Zotero.DataObjects.getLibraryKeyHash() is deprecated -- use obj.libraryKey instead"); - return this.makeLibraryKeyHash(obj.libraryID, obj.key); + // If single id, return the object directly + if (singleObject) { + return toReturn.length ? toReturn[0] : false; } - - this.parseLibraryKey = function (libraryKey) { - var [libraryID, key] = libraryKey.split('/'); - return { - libraryID: parseInt(libraryID), - key: key - }; + return toReturn; +}); + + +/** + * @deprecated - use .libraryKey + */ +Zotero.DataObjects.prototype.makeLibraryKeyHash = function (libraryID, key) { + Zotero.debug("WARNING: " + this._ZDO_Objects + ".makeLibraryKeyHash() is deprecated -- use .libraryKey instead"); + return libraryID + '_' + key; +} + + +/** + * @deprecated - use .libraryKey + */ +Zotero.DataObjects.prototype.getLibraryKeyHash = function (obj) { + Zotero.debug("WARNING: " + this._ZDO_Objects + ".getLibraryKeyHash() is deprecated -- use .libraryKey instead"); + return this.makeLibraryKeyHash(obj.libraryID, obj.key); +} + + +Zotero.DataObjects.prototype.parseLibraryKey = function (libraryKey) { + var [libraryID, key] = libraryKey.split('/'); + return { + libraryID: parseInt(libraryID), + key: key + }; +} + + +/** + * @deprecated - Use Zotero.DataObjects.parseLibraryKey() + */ +Zotero.DataObjects.prototype.parseLibraryKeyHash = function (libraryKey) { + Zotero.debug("WARNING: " + this._ZDO_Objects + ".parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead"); + var [libraryID, key] = libraryKey.split('_'); + if (!key) { + return false; } - - - /** - * @deprecated - Use Zotero.DataObjects.parseLibraryKey() - */ - this.parseLibraryKeyHash = function (libraryKey) { - Zotero.debug("WARNING: Zotero.DataObjects.parseLibraryKeyHash() is deprecated -- use .parseLibraryKey() instead"); - var [libraryID, key] = libraryKey.split('_'); - if (!key) { - return false; - } - return { - libraryID: parseInt(libraryID), - key: key - }; - } - - - /** - * Retrieves an object by its libraryID and key - * - * @param {Integer} libraryID - * @param {String} key - * @return {Zotero.DataObject} Zotero data object, or FALSE if not found - */ - this.getByLibraryAndKey = function (libraryID, key, options) { - var id = this.getIDFromLibraryAndKey(libraryID, key); - if (!id) { - return false; - } - return Zotero[this._ZDO_Objects].get(id, options); + return { + libraryID: parseInt(libraryID), + key: key }; - - - /** - * Asynchronously retrieves an object by its libraryID and key - * - * @param {Integer} - libraryID - * @param {String} - key - * @return {Promise<Zotero.DataObject>} - Promise for a data object, or FALSE if not found - */ - this.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) { - var id = this.getIDFromLibraryAndKey(libraryID, key); - if (!id) { - return false; - } - return Zotero[this._ZDO_Objects].getAsync(id, options); - }); - - - this.exists = function (itemID) { - return !!this.getLibraryAndKeyFromID(itemID); +} + + +/** + * Retrieves an object by its libraryID and key + * + * @param {Integer} libraryID + * @param {String} key + * @return {Zotero.DataObject} Zotero data object, or FALSE if not found + */ +Zotero.DataObjects.prototype.getByLibraryAndKey = function (libraryID, key, options) { + var id = this.getIDFromLibraryAndKey(libraryID, key); + if (!id) { + return false; } - - - /** - * @return {Array} Array with libraryID and key - */ - this.getLibraryAndKeyFromID = function (id) { - return this._objectKeys[id] ? this._objectKeys[id] : false; + return Zotero[this._ZDO_Objects].get(id, options); +}; + + +/** + * Asynchronously retrieves an object by its libraryID and key + * + * @param {Integer} - libraryID + * @param {String} - key + * @return {Promise<Zotero.DataObject>} - Promise for a data object, or FALSE if not found + */ +Zotero.DataObjects.prototype.getByLibraryAndKeyAsync = Zotero.Promise.coroutine(function* (libraryID, key, options) { + var id = this.getIDFromLibraryAndKey(libraryID, key); + if (!id) { + return false; + } + return Zotero[this._ZDO_Objects].getAsync(id, options); +}); + + +Zotero.DataObjects.prototype.exists = function (itemID) { + return !!this.getLibraryAndKeyFromID(itemID); +} + + +/** + * @return {Array} Array with libraryID and key + */ +Zotero.DataObjects.prototype.getLibraryAndKeyFromID = function (id) { + return this._objectKeys[id] ? this._objectKeys[id] : false; +} + + +Zotero.DataObjects.prototype.getIDFromLibraryAndKey = function (libraryID, key) { + if (libraryID === null) { + throw new Error("libraryID cannot be NULL (did you mean 0?)"); + } + return (this._objectIDs[libraryID] && this._objectIDs[libraryID][key]) + ? this._objectIDs[libraryID][key] : false; +} + + +Zotero.DataObjects.prototype.getOlder = function (libraryID, date) { + if (!date || date.constructor.name != 'Date') { + throw ("date must be a JS Date in " + + "Zotero." + this._ZDO_Objects + ".getOlder()") } + var sql = "SELECT ROWID FROM " + this._ZDO_table + + " WHERE libraryID=? AND clientDateModified<?"; + return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]); +} + + +Zotero.DataObjects.prototype.getNewer = function (libraryID, date, ignoreFutureDates) { + if (!date || date.constructor.name != 'Date') { + throw ("date must be a JS Date in " + + "Zotero." + this._ZDO_Objects + ".getNewer()") + } - this.getIDFromLibraryAndKey = function (libraryID, key) { - if (libraryID === null) { - throw new Error("libraryID cannot be NULL (did you mean 0?)"); + var sql = "SELECT ROWID FROM " + this._ZDO_table + + " WHERE libraryID=? AND clientDateModified>?"; + if (ignoreFutureDates) { + sql += " AND clientDateModified<=CURRENT_TIMESTAMP"; + } + return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]); +} + + +/** + * @param {Integer} libraryID + * @return {Promise} A promise for an array of object ids + */ +Zotero.DataObjects.prototype.getUnsynced = function (libraryID) { + var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table + + " WHERE libraryID=? AND synced=0"; + return Zotero.DB.columnQueryAsync(sql, [libraryID]); +} + + +/** + * Get JSON from the sync cache that hasn't yet been written to the + * main object tables + * + * @param {Integer} libraryID + * @return {Promise} A promise for an array of JSON objects + */ +Zotero.DataObjects.prototype.getUnwrittenData = function (libraryID) { + var sql = "SELECT data FROM syncCache SC " + + "LEFT JOIN " + this._ZDO_table + " " + + "USING (libraryID) " + + "WHERE SC.libraryID=? AND " + + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM " + + "syncObjectTypes WHERE name='" + this._ZDO_object + "') " + + "AND IFNULL(O.version, 0) < SC.version"; + return Zotero.DB.columnQueryAsync(sql, [libraryID]); +} + + +/** + * Reload loaded data of loaded objects + * + * @param {Array|Number} ids - An id or array of ids + * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded + * types if not provided + * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally. + * This should be set to true for data that was + * changed externally (e.g., globally renamed tags). + */ +Zotero.DataObjects.prototype.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) { + ids = Zotero.flattenArguments(ids); + + Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '') + + this._ZDO_objects + ' ' + ids); + + for (let i=0; i<ids.length; i++) { + if (this._objectCache[ids[i]]) { + yield this._objectCache[ids[i]].reload(dataTypes, reloadUnchanged); } - return (this._objectIDs[libraryID] && this._objectIDs[libraryID][key]) - ? this._objectIDs[libraryID][key] : false; } - - this.getOlder = function (libraryID, date) { - if (!date || date.constructor.name != 'Date') { - throw ("date must be a JS Date in " - + "Zotero." + this._ZDO_Objects + ".getOlder()") + return true; +}); + + +Zotero.DataObjects.prototype.reloadAll = function (libraryID) { + Zotero.debug("Reloading all " + this._ZDO_objects); + + // Remove objects not stored in database + var sql = "SELECT ROWID FROM " + this._ZDO_table; + var params = []; + if (libraryID !== undefined) { + sql += ' WHERE libraryID=?'; + params.push(libraryID); + } + return Zotero.DB.columnQueryAsync(sql, params) + .then(function (ids) { + for (var id in this._objectCache) { + if (!ids || ids.indexOf(parseInt(id)) == -1) { + delete this._objectCache[id]; + } } - var sql = "SELECT ROWID FROM " + this._ZDO_table - + " WHERE libraryID=? AND clientDateModified<?"; - return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]); + // Reload data + this._loadedLibraries[libraryID] = false; + return this._load(libraryID); + }); +} + + +Zotero.DataObjects.prototype.registerIdentifiers = function (id, libraryID, key) { + Zotero.debug("Registering " + this._ZDO_object + " " + id + " as " + libraryID + "/" + key); + if (!this._objectIDs[libraryID]) { + this._objectIDs[libraryID] = {}; } - - - this.getNewer = function (libraryID, date, ignoreFutureDates) { - if (!date || date.constructor.name != 'Date') { - throw ("date must be a JS Date in " - + "Zotero." + this._ZDO_Objects + ".getNewer()") + this._objectIDs[libraryID][key] = id; + this._objectKeys[id] = [libraryID, key]; +} + + +/** + * Clear object from internal array + * + * @param int[] ids objectIDs + */ +Zotero.DataObjects.prototype.unload = function () { + var ids = Zotero.flattenArguments(arguments); + for (var i=0; i<ids.length; i++) { + let id = ids[i]; + let [libraryID, key] = this.getLibraryAndKeyFromID(id); + if (key) { + delete this._objectIDs[libraryID][key]; + delete this._objectKeys[id]; + } + delete this._objectCache[id]; + } +} + + +/** + * @param {Object} data1 - JSON of first object + * @param {Object} data2 - JSON of second object + * @param {Array} diff - Empty array to put diff data in + * @param {Boolean} [includeMatches=false] - Include all fields, even those + * that aren't different + */ +Zotero.DataObjects.prototype.diff = function (data1, data2, diff, includeMatches) { + diff.push({}, {}); + var numDiffs = 0; + + var skipFields = ['collectionKey', 'itemKey', 'searchKey']; + + for (var field in data1) { + if (skipFields.indexOf(field) != -1) { + continue; } - var sql = "SELECT ROWID FROM " + this._ZDO_table - + " WHERE libraryID=? AND clientDateModified>?"; - if (ignoreFutureDates) { - sql += " AND clientDateModified<=CURRENT_TIMESTAMP"; + if (data1[field] === false && (data2[field] === false || data2[field] === undefined)) { + continue; } - return Zotero.DB.columnQuery(sql, [libraryID, Zotero.Date.dateToSQL(date, true)]); - } - - - /** - * @param {Integer} libraryID - * @return {Promise} A promise for an array of object ids - */ - this.getUnsynced = function (libraryID) { - var sql = "SELECT " + this._ZDO_id + " FROM " + this._ZDO_table - + " WHERE libraryID=? AND synced=0"; - return Zotero.DB.columnQueryAsync(sql, [libraryID]); - } - - - /** - * Get JSON from the sync cache that hasn't yet been written to the - * main object tables - * - * @param {Integer} libraryID - * @return {Promise} A promise for an array of JSON objects - */ - this.getUnwrittenData = function (libraryID) { - var sql = "SELECT data FROM syncCache SC " - + "LEFT JOIN " + this._ZDO_table + " " - + "USING (libraryID) " - + "WHERE SC.libraryID=? AND " - + "syncObjectTypeID IN (SELECT syncObjectTypeID FROM " - + "syncObjectTypes WHERE name='" + this._ZDO_object + "') " - + "AND IFNULL(O.version, 0) < SC.version"; - return Zotero.DB.columnQueryAsync(sql, [libraryID]); - } - - - /** - * Reload loaded data of loaded objects - * - * @param {Array|Number} ids - An id or array of ids - * @param {Array} [dataTypes] - Data types to reload (e.g., 'primaryData'), or all loaded - * types if not provided - * @param {Boolean} [reloadUnchanged=false] - Reload even data that hasn't changed internally. - * This should be set to true for data that was - * changed externally (e.g., globally renamed tags). - */ - this.reload = Zotero.Promise.coroutine(function* (ids, dataTypes, reloadUnchanged) { - ids = Zotero.flattenArguments(ids); - Zotero.debug('Reloading ' + (dataTypes ? dataTypes + ' for ' : '') - + this._ZDO_objects + ' ' + ids); + var changed = data1[field] !== data2[field]; - for (let i=0; i<ids.length; i++) { - if (this._objectCache[ids[i]]) { - yield this._objectCache[ids[i]].reload(dataTypes, reloadUnchanged); - } + if (includeMatches || changed) { + diff[0][field] = data1[field] !== false ? data1[field] : ''; + diff[1][field] = (data2[field] !== false && data2[field] !== undefined) + ? data2[field] : ''; } - return true; - }); - - - this.reloadAll = function (libraryID) { - Zotero.debug("Reloading all " + this._ZDO_objects); - - // Remove objects not stored in database - var sql = "SELECT ROWID FROM " + this._ZDO_table; - var params = []; - if (libraryID !== undefined) { - sql += ' WHERE libraryID=?'; - params.push(libraryID); + if (changed) { + numDiffs++; } - return Zotero.DB.columnQueryAsync(sql, params) - .then(function (ids) { - for (var id in this._objectCache) { - if (!ids || ids.indexOf(parseInt(id)) == -1) { - delete this._objectCache[id]; - } - } - - // Reload data - this._loadedLibraries[libraryID] = false; - return this._load(libraryID); - }); } - - this.registerIdentifiers = function (id, libraryID, key) { - Zotero.debug("Registering " + this._ZDO_object + " " + id + " as " + libraryID + "/" + key); - if (!this._objectIDs[libraryID]) { - this._objectIDs[libraryID] = {}; - } - this._objectIDs[libraryID][key] = id; - this._objectKeys[id] = [libraryID, key]; - } - - - /** - * Clear object from internal array - * - * @param int[] ids objectIDs - */ - this.unload = function () { - var ids = Zotero.flattenArguments(arguments); - for (var i=0; i<ids.length; i++) { - let id = ids[i]; - let [libraryID, key] = this.getLibraryAndKeyFromID(id); - if (key) { - delete this._objectIDs[libraryID][key]; - delete this._objectKeys[id]; - } - delete this._objectCache[id]; + // DEBUG: some of this is probably redundant + for (var field in data2) { + if (skipFields.indexOf(field) != -1) { + continue; } - } - - - /** - * @param {Object} data1 - JSON of first object - * @param {Object} data2 - JSON of second object - * @param {Array} diff - Empty array to put diff data in - * @param {Boolean} [includeMatches=false] - Include all fields, even those - * that aren't different - */ - this.diff = function (data1, data2, diff, includeMatches) { - diff.push({}, {}); - var numDiffs = 0; - var skipFields = ['collectionKey', 'itemKey', 'searchKey']; + if (diff[0][field] !== undefined) { + continue; + } - for (var field in data1) { - if (skipFields.indexOf(field) != -1) { - continue; - } - - if (data1[field] === false && (data2[field] === false || data2[field] === undefined)) { - continue; - } - - var changed = data1[field] !== data2[field]; - - if (includeMatches || changed) { - diff[0][field] = data1[field] !== false ? data1[field] : ''; - diff[1][field] = (data2[field] !== false && data2[field] !== undefined) - ? data2[field] : ''; - } - - if (changed) { - numDiffs++; - } + if (data2[field] === false && (data1[field] === false || data1[field] === undefined)) { + continue; } - // DEBUG: some of this is probably redundant - for (var field in data2) { - if (skipFields.indexOf(field) != -1) { - continue; - } - - if (diff[0][field] !== undefined) { - continue; - } - - if (data2[field] === false && (data1[field] === false || data1[field] === undefined)) { - continue; - } - - var changed = data1[field] !== data2[field]; - - if (includeMatches || changed) { - diff[0][field] = (data1[field] !== false && data1[field] !== undefined) - ? data1[field] : ''; - diff[1][field] = data2[field] !== false ? data2[field] : ''; - } - - if (changed) { - numDiffs++; - } + var changed = data1[field] !== data2[field]; + + if (includeMatches || changed) { + diff[0][field] = (data1[field] !== false && data1[field] !== undefined) + ? data1[field] : ''; + diff[1][field] = data2[field] !== false ? data2[field] : ''; } - return numDiffs; + if (changed) { + numDiffs++; + } } + return numDiffs; +} + + +Zotero.DataObjects.prototype.isEditable = function (obj) { + var libraryID = obj.libraryID; + if (!libraryID) { + return true; + } - this.isEditable = function (obj) { - var libraryID = obj.libraryID; - if (!libraryID) { - return true; - } - var type = Zotero.Libraries.getType(libraryID); - switch (type) { - case 'user': - return true; - - case 'group': - var groupID = Zotero.Groups.getGroupIDFromLibraryID(libraryID); - var group = Zotero.Groups.get(groupID); - if (!group.editable) { - return false; - } - if (obj.objectType == 'item' && obj.isAttachment() - && (obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_URL || - obj.attachmentLinkMode == Zotero.Attachments.LINK_MODE_IMPORTED_FILE)) { - return group.filesEditable; - } - return true; - - default: - throw ("Unsupported library type '" + type + "' in Zotero.DataObjects.isEditable()"); - } + if (!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) + && !Zotero.Libraries.isFilesEditable(libraryID) + ) { + return false; } + return true; +} + + +Zotero.DataObjects.prototype.editCheck = function (obj) { + if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable(obj)) { + throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library"); + } +} + +Zotero.defineProperty(Zotero.DataObjects.prototype, "primaryDataSQL", { + get: function () { + return "SELECT " + + Object.keys(this._primaryDataSQLParts).map((val) => this._primaryDataSQLParts[val]).join(', ') + + this.primaryDataSQLFrom; + } +}, {lazy: true}); + +Zotero.DataObjects.prototype._primaryDataSQLWhere = "WHERE 1"; + +Zotero.DataObjects.prototype.getPrimaryDataSQLPart = function (part) { + var sql = this._primaryDataSQLParts[part]; + if (!sql) { + throw new Error("Invalid primary data SQL part '" + part + "'"); + } + return sql; +} + + +Zotero.DataObjects.prototype._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) { + var loaded = {}; - this.editCheck = function (obj) { - if (!Zotero.Sync.Server.updatesInProgress && !Zotero.Sync.Storage.updatesInProgress && !this.isEditable(obj)) { - throw ("Cannot edit " + this._ZDO_object + " in read-only Zotero library"); - } + // If library isn't an integer (presumably false or null), skip it + if (parseInt(libraryID) != libraryID) { + libraryID = false; } + if (libraryID === false && !ids) { + throw new Error("Either libraryID or ids must be provided"); + } - this.getPrimaryDataSQLPart = function (part) { - var sql = this._primaryDataSQLParts[part]; - if (!sql) { - throw new Error("Invalid primary data SQL part '" + part + "'"); - } - return sql; + if (libraryID !== false && this._loadedLibraries[libraryID]) { + return loaded; } + // getPrimaryDataSQL() should use "O" for the primary table alias + var sql = this.primaryDataSQL; + var params = []; + if (libraryID !== false) { + sql += ' AND O.libraryID=?'; + params.push(libraryID); + } + if (ids) { + sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')'; + } - this._load = Zotero.Promise.coroutine(function* (libraryID, ids, options) { - var loaded = {}; - - // If library isn't an integer (presumably false or null), skip it - if (parseInt(libraryID) != libraryID) { - libraryID = false; - } - - if (libraryID === false && !ids) { - throw new Error("Either libraryID or ids must be provided"); - } - - if (libraryID !== false && this._loadedLibraries[libraryID]) { - return loaded; - } - - // getPrimaryDataSQL() should use "O" for the primary table alias - var sql = this.getPrimaryDataSQL(); - var params = []; - if (libraryID !== false) { - sql += ' AND O.libraryID=?'; - params.push(libraryID); - } - if (ids) { - sql += ' AND O.' + this._ZDO_id + ' IN (' + ids.join(',') + ')'; - } - - var t = new Date(); - yield Zotero.DB.queryAsync( - sql, - params, - { - onRow: function (row) { - var id = row.getResultByIndex(this._ZDO_id); - var columns = Object.keys(this._primaryDataSQLParts); - var rowObj = {}; - for (let i=0; i<columns.length; i++) { - rowObj[columns[i]] = row.getResultByIndex(i); - } - var obj; - - // Existing object -- reload in place - if (this._objectCache[id]) { - this._objectCache[id].loadFromRow(rowObj, true); - obj = this._objectCache[id]; - } - // Object doesn't exist -- create new object and stuff in cache - else { - obj = new Zotero[this._ZDO_Object]; - obj.loadFromRow(rowObj, true); - if (!options || !options.noCache) { - this._objectCache[id] = obj; - } - } - loaded[id] = obj; - }.bind(this) - } - ); - Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms"); - - if (!ids) { - this._loadedLibraries[libraryID] = true; - - // If loading all objects, remove cached objects that no longer exist - for (let i in this._objectCache) { - let obj = this._objectCache[i]; - if (libraryID !== false && obj.libraryID !== libraryID) { - continue; + var t = new Date(); + yield Zotero.DB.queryAsync( + sql, + params, + { + onRow: function (row) { + var id = row.getResultByIndex(this._ZDO_id); + var columns = Object.keys(this._primaryDataSQLParts); + var rowObj = {}; + for (let i=0; i<columns.length; i++) { + rowObj[columns[i]] = row.getResultByIndex(i); } - if (!loaded[obj.id]) { - this.unload(obj.id); + var obj; + + // Existing object -- reload in place + if (this._objectCache[id]) { + this._objectCache[id].loadFromRow(rowObj, true); + obj = this._objectCache[id]; } + // Object doesn't exist -- create new object and stuff in cache + else { + obj = new Zotero[this._ZDO_Object]; + obj.loadFromRow(rowObj, true); + if (!options || !options.noCache) { + this._objectCache[id] = obj; + } + } + loaded[id] = obj; + }.bind(this) + } + ); + Zotero.debug("Loaded " + this._ZDO_objects + " in " + ((new Date) - t) + "ms"); + + if (!ids) { + this._loadedLibraries[libraryID] = true; + + // If loading all objects, remove cached objects that no longer exist + for (let i in this._objectCache) { + let obj = this._objectCache[i]; + if (libraryID !== false && obj.libraryID !== libraryID) { + continue; } - - if (this._postLoad) { - this._postLoad(libraryID, ids); + if (!loaded[obj.id]) { + this.unload(obj.id); } } - return loaded; - }); - - - this._loadIDsAndKeys = Zotero.Promise.coroutine(function* () { - var sql = "SELECT ROWID AS id, libraryID, key FROM " + this._ZDO_table; - var rows = yield Zotero.DB.queryAsync(sql); - for (let i=0; i<rows.length; i++) { - let row = rows[i]; - this._objectKeys[row.id] = [row.libraryID, row.key]; - if (!this._objectIDs[row.libraryID]) { - this._objectIDs[row.libraryID] = {}; - } - this._objectIDs[row.libraryID][row.key] = row.id; + if (this._postLoad) { + this._postLoad(libraryID, ids); } - }); -} + } + + return loaded; +}); + + +Zotero.DataObjects.prototype._loadIDsAndKeys = Zotero.Promise.coroutine(function* () { + var sql = "SELECT ROWID AS id, libraryID, key FROM " + this._ZDO_table; + var rows = yield Zotero.DB.queryAsync(sql); + for (let i=0; i<rows.length; i++) { + let row = rows[i]; + this._objectKeys[row.id] = [row.libraryID, row.key]; + if (!this._objectIDs[row.libraryID]) { + this._objectIDs[row.libraryID] = {}; + } + this._objectIDs[row.libraryID][row.key] = row.id; + } +}); diff --git a/chrome/content/zotero/xpcom/data/items.js b/chrome/content/zotero/xpcom/data/items.js @@ -27,17 +27,16 @@ /* * Primary interface for accessing Zotero items */ -Zotero.Items = new function() { - Zotero.DataObjects.apply(this, ['item']); - this.constructor.prototype = new Zotero.DataObjects(); +Zotero.Items = function() { + this.constructor = null; - // Privileged methods - this.add = add; - this.getSortTitle = getSortTitle; + this._ZDO_object = 'item'; - Object.defineProperty(this, "_primaryDataSQLParts", { + // This needs to wait until all Zotero components are loaded to initialize, + // but otherwise it can be just a simple property + Zotero.defineProperty(this, "_primaryDataSQLParts", { get: function () { - return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = { + return { itemID: "O.itemID", itemTypeID: "O.itemTypeID", dateAdded: "O.dateAdded", @@ -88,18 +87,17 @@ Zotero.Items = new function() { attachmentContentType: "IA.contentType AS attachmentContentType", attachmentPath: "IA.path AS attachmentPath", attachmentSyncState: "IA.syncState AS attachmentSyncState" - }); + }; } - }); + }, {lazy: true}); - // Private members - var _primaryDataSQLParts; - var _cachedFields = {}; - var _firstCreatorSQL = ''; - var _sortCreatorSQL = ''; - var _emptyTrashIdleObserver = null; - var _emptyTrashTimer = null; + this._primaryDataSQLFrom = "FROM items O " + + "LEFT JOIN itemAttachments IA USING (itemID) " + + "LEFT JOIN items IAP ON (IA.parentItemID=IAP.itemID) " + + "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) " + + "LEFT JOIN items INoP ON (INo.parentItemID=INoP.itemID) " + + "LEFT JOIN deletedItems DI ON (O.itemID=DI.itemID)"; /** * Return items marked as deleted @@ -215,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))(); diff --git a/chrome/content/zotero/xpcom/data/relations.js b/chrome/content/zotero/xpcom/data/relations.js @@ -23,18 +23,15 @@ ***** END LICENSE BLOCK ***** */ -Zotero.Relations = new function () { - Zotero.DataObjects.apply(this, ['relation']); - this.constructor.prototype = new Zotero.DataObjects(); +Zotero.Relations = function () { + this.constructor = null; - this.__defineGetter__('relatedItemPredicate', function () "dc:relation"); - this.__defineGetter__('linkedObjectPredicate', function () "owl:sameAs"); - this.__defineGetter__('deletedItemPredicate', function () 'dc:isReplacedBy'); + this._ZDO_object = 'relation'; + this._ZDO_idOnly = true; - var _namespaces = { - dc: 'http://purl.org/dc/elements/1.1/', - owl: 'http://www.w3.org/2002/07/owl#' - }; + Zotero.defineProperty(this, 'relatedItemPredicate', {value: 'dc:relation'}); + Zotero.defineProperty(this, 'linkedObjectPredicate', {value: 'owl:sameAs'}); + Zotero.defineProperty(this, 'deletedItemPredicate', {value: 'dc:isReplacedBy'}); this.get = function (id) { if (typeof id != 'number') { @@ -52,7 +49,7 @@ Zotero.Relations = new function () { */ this.getByURIs = Zotero.Promise.coroutine(function* (subject, predicate, object) { if (predicate) { - predicate = _getPrefixAndValue(predicate).join(':'); + predicate = this._getPrefixAndValue(predicate).join(':'); } if (!subject && !predicate && !object) { @@ -141,7 +138,7 @@ Zotero.Relations = new function () { this.add = Zotero.Promise.coroutine(function* (libraryID, subject, predicate, object) { - predicate = _getPrefixAndValue(predicate).join(':'); + predicate = this._getPrefixAndValue(predicate).join(':'); var relation = new Zotero.Relation; if (!libraryID) { @@ -272,11 +269,15 @@ Zotero.Relations = new function () { return relation; } + this._namespaces = { + dc: 'http://purl.org/dc/elements/1.1/', + owl: 'http://www.w3.org/2002/07/owl#' + }; - function _getPrefixAndValue(uri) { + this._getPrefixAndValue = function(uri) { var [prefix, value] = uri.split(':'); if (prefix && value) { - if (!_namespaces[prefix]) { + if (!this._namespaces[prefix]) { throw ("Invalid prefix '" + prefix + "' in Zotero.Relations._getPrefixAndValue()"); } return [prefix, value]; @@ -290,4 +291,8 @@ Zotero.Relations = new function () { } throw ("Invalid namespace in URI '" + uri + "' in Zotero.Relations._getPrefixAndValue()"); } -} + + Zotero.DataObjects.call(this); + + return this; +}.bind(Object.create(Zotero.DataObjects.prototype))(); +\ No newline at end of file diff --git a/chrome/content/zotero/xpcom/search.js b/chrome/content/zotero/xpcom/search.js @@ -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(); - - Object.defineProperty(this, "_primaryDataSQLParts", { - get: function () { - return _primaryDataSQLParts ? _primaryDataSQLParts : (_primaryDataSQLParts = { - savedSearchID: "O.savedSearchID", - name: "O.savedSearchName", - libraryID: "O.libraryID", - key: "O.key", - version: "O.version", - synced: "O.synced" - }); - } - }); - - - var _primaryDataSQLParts; +Zotero.Searches = function() { + this.constructor = null; + + this._ZDO_object = 'search'; + this._ZDO_id = 'savedSearch'; + this._ZDO_table = 'savedSearches'; + + this._primaryDataSQLParts = { + savedSearchID: "O.savedSearchID", + name: "O.savedSearchName", + libraryID: "O.libraryID", + key: "O.key", + version: "O.version", + synced: "O.synced" + } this.init = Zotero.Promise.coroutine(function* () { - yield this.constructor.prototype.init.apply(this); + yield Zotero.DataObjects.prototype.init.apply(this); yield Zotero.SearchConditions.init(); }); @@ -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))();