/* ***** BEGIN LICENSE BLOCK ***** Copyright © 2013 Center for History and New Media George Mason University, Fairfax, Virginia, USA http://zotero.org This file is part of Zotero. Zotero is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Zotero is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Zotero. If not, see . ***** END LICENSE BLOCK ***** */ /** * @property {String} (readOnly) objectType * @property {String} (readOnly) libraryKey * @property {String|null} parentKey Null if no parent * @property {Integer|false|undefined} parentID False if no parent. Undefined if not applicable (e.g. search objects) */ 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; this._key = null; this._dateAdded = null; this._dateModified = null; this._version = null; this._synced = null; this._identified = false; this._loaded = {}; this._markAllDataTypeLoadStates(false); this._clearChanged(); }; Zotero.DataObject.prototype._objectType = 'dataObject'; Zotero.DataObject.prototype._dataTypes = ['primaryData']; Zotero.defineProperty(Zotero.DataObject.prototype, 'objectType', { get: function() this._objectType }); 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.defineProperty(Zotero.DataObject.prototype, 'parentKey', { get: function() this._parentKey, set: function(v) this._setParentKey(v) }); 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) { return this['_' + field]; } this._requireData('primaryData'); return null; } Zotero.DataObject.prototype._setIdentifier = function (field, value) { // If primary data is loaded, the only allowed identifier change is libraryID // (allowed mainly for the library switcher in the advanced search window), // and then only for unsaved objects (i.e., those without an id or key) if (this._loaded.primaryData) { if (field != 'libraryID' || this._id || this._key) { throw new Error("Cannot set " + field + " after object is already loaded"); } } switch (field) { case 'id': if (this._key) { throw new Error("Cannot set id if key is already set"); } value = Zotero.DataObjectUtilities.checkDataID(value); this._identified = true; break; case 'libraryID': value = Zotero.DataObjectUtilities.checkLibraryID(value); break; case 'key': if (this._libraryID === null) { throw new Error("libraryID must be set before key"); } if (this._id) { throw new Error("Cannot set key if id is already set"); } value = Zotero.DataObjectUtilities.checkKey(value); this._identified = true; } if (value === this['_' + field]) { return; } this['_' + field] = value; } /** * Get the id of the parent object * * @return {Integer|false|undefined} The id of the parent object, false if none, or undefined * on object types to which it doesn't apply (e.g., searches) */ Zotero.DataObject.prototype._getParentID = function () { if (this._parentID !== null) { return this._parentID; } if (!this._parentKey) { return false; } return this._parentID = this.ObjectsClass.getIDFromLibraryAndKey(this._libraryID, this._parentKey); } /** * Set the id of the parent object * * @param {Number|false} [id=false] * @return {Boolean} True if changed, false if stayed the same */ Zotero.DataObject.prototype._setParentID = function (id) { return this._setParentKey( id ? this.ObjectsClass.getLibraryAndKeyFromID(Zotero.DataObjectUtilities.checkDataID(id))[1] : null ); } /** * Set the key of the parent object * * @param {String|null} [key=null] * @return {Boolean} True if changed, false if stayed the same */ Zotero.DataObject.prototype._setParentKey = function(key) { key = Zotero.DataObjectUtilities.checkKey(key); if (this._parentKey == key) { return false; } this._markFieldChange('parentKey', this._parentKey); this._changed.parentKey = true; this._parentKey = key; this._parentID = null; return true; } /** * Returns all relations of the object * * @return {object} Object with predicates as keys and URI[], or URI (as string) * in the case of a single object, as values */ Zotero.DataObject.prototype.getRelations = function () { this._requireData('relations'); var relations = {}; for (let i=0; i b[0]) return 1; if (a[1] < b[1]) return -1; if (a[1] > b[1]) return 1; return 0; }; var newRelationsFlat = []; for (let predicate in newRelations) { let object = newRelations[predicate]; for (let i=0; i this._loaded[val] ); } if (dataTypes && dataTypes.length) { for (let i=0; i} 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) { if (!this.libraryID) { throw new Error("libraryID must be set before saving " + this._objectType); } 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 => { Zotero.debug(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 */ Zotero.DataObject.prototype._generateKey = function () { return Zotero.Utilities.generateObjectKey(); }