/*
***** 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();
}