/*globals define*/
/*eslint-env node, browser*/
/*eslint no-unused-vars: 0*/
/**
* This class defines the common interface for a storage-project.
*
* @author pmeijer / https://github.com/pmeijer
*/
define([
'q',
'common/storage/project/cache',
'common/storage/constants',
'common/storage/util',
'common/regexp',
], function (Q, ProjectCache, CONSTANTS, UTIL, REGEXP) {
'use strict';
/**
*
* @param {string} projectId - Id of project to be opened.
* @param {object} storageObjectsAccessor - Exposes loadObject towards the database.
* @param {GmeLogger} mainLogger - Logger instance from instantiator.
* @param {GmeConfig} gmeConfig
* @alias ProjectInterface
* @constructor
*/
function ProjectInterface(projectId, storageObjectsAccessor, mainLogger, gmeConfig) {
/**
* Unique ID of project, built up by the ownerId and projectName.
*
* @example
* 'guest+TestProject', 'organization+TestProject2'
* @type {string}
*/
this.projectId = projectId;
this.projectName = UTIL.getProjectNameFromProjectId(projectId);
this.CONSTANTS = CONSTANTS;
this.ID_NAME = CONSTANTS.MONGO_ID;
/**
* @type {GmeConfig}
*/
this.gmeConfig = gmeConfig;
/**
* @type {GmeLogger}
*/
this.logger = mainLogger.fork('Project:' + this.projectId);
this.logger.debug('ctor', projectId);
this.projectCache = new ProjectCache(storageObjectsAccessor, this.projectId, this.logger, gmeConfig);
// Functions forwarded to project cache.
/**
* Inserts the given object to project-cache.
*
* @param {module:Storage~CommitObject|module:Core~ObjectData} obj - Object to be inserted in database.
* @param {Object.<module:Core~ObjectHash, module:Core~ObjectData>} [stackedObjects] - When used by the core,
* inserts between persists are stored here.
* @func
* @private
*/
this.insertObject = this.projectCache.insertObject;
/**
* Try to create the full object from the patch object by looking for the base object in the cache.
* If the base has been found it applies the patch and inserts the result. If any step fails it simply
* ignores the insert.
*
* @param {module:Storage~CommitObject|module:Core~ObjectData} obj - Object to be inserted in database.
* @func
* @private
*/
this.insertPatchObject = this.projectCache.insertPatchObject;
/**
* Callback for loadObject.
*
* @callback ProjectInterface~loadObjectCallback
* @param {Error} err - If error occurred.
* @param {module:Storage~CommitObject|module:Core~ObjectData} object - Object loaded from database,
* commit-object or model data-blob.
*/
/**
* Loads the object with hash key from the database or
* directly from the cache if recently loaded.
* @param {string} key - Hash of object to load.
* @param {ProjectInterface~loadObjectCallback} callback - Invoked when object is loaded.
* @func
* @private
*/
this.loadObject = this.projectCache.loadObject;
/**
* Collects the objects from the server and pre-loads them into the cache
* making the load of multiple objects faster.
* @private
* @param {string} rootKey - Hash of the object at the entry point of the paths.
* @param {string[]} paths - List of paths that needs to be pre-loaded.
* @param {function} callback - Invoked when objects have been collected.
* @func
* @private
*/
this.loadPaths = this.projectCache.loadPaths;
// Public API
/**
* Makes a commit to data base. Based on the root hash and commit message a new
* {@link module:Storage.CommitObject} (with returned hash)
* is generated and insert together with the core objects to the database on the server.
*
* @example
* var persisted = core.persist(rootNode);
*
* project.makeCommit('master', ['#thePreviousCommitHash'], persisted.rootHash, persisted.objects, 'new commit')
* .then(function (result) {
* // result = {
* // status: 'SYNCED',
* // hash: '#thisCommitHash'
* // }
* })
* .catch(function (error) {
* // error.message = 'Not authorized to read project: guest+project'
* });
* @example
* project.makeCommit('master', ['#notPreviousCommitHash'], persisted.rootHash, persisted.objects, 'new commit')
* .then(function (result) {
* // result = {
* // status: 'FORKED',
* // hash: '#thisCommitHash'
* // }
* })...
* @example
* project.makeCommit(null, ['#anExistingCommitHash'], persisted.rootHash, persisted.objects, 'new commit')
* .then(function (result) {
* // result = {
* // hash: '#thisCommitHash'
* // }
* })...
* @example
* project.makeCommit('master', ['#aPreviousCommitHash'], previousRootHash, {}, 'adding a commit to master')
* .then(function (result) {
* // result = {
* // status: 'SYNCED',
* // hash: '#thisCommitHash'
* // }
* })...
* @param {string} branchName - Name of branch to update (none if null).
* @param {module:Storage~CommitHash[]} parents - Parent commit hashes.
* @param {module:Core~ObjectHash} rootHash - Hash of root object.
* @param {module:Core~DataObject} coreObjects - Core objects associated with the commit.
* @param {string} msg='n/a' - Commit message.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Storage~CommitResult} callback.result - Status about the commit and branch update.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitResult} <b>result</b>.<br>
* On error the promise will be rejected with {Error} <b>error</b>.
*/
this.makeCommit = function (branchName, parents, rootHash, coreObjects, msg, callback) {
throw new Error('makeCommit must be overridden in derived class');
};
/**
* Retrieves the metadata of the project.
* @example
* {
* _id: 'guest+example',
* owner: 'guest',
* name: 'example',
* info: {
* createdAt: '2016-12-02T17:52:25.029Z',
* viewedAt: '2017-01-30T22:45:15.269Z',
* modifiedAt: '2017-01-20T00:15:34.593Z',
* creator: 'guest',
* viewer: 'guest',
* modifier': 'guest'
* },
* hooks: {
* ConstraintCheckerHook': {
* url: 'http://127.0.0.1:8080/ConstraintCheckerHook',
* description': 'Checks if there are any meta violations in the project',
* events: ['COMMIT'],
* active: true,
* createdAt: '2017-01-19T23:22:46.834Z',
* updatedAt: '2017-01-19T23:22:46.834Z'
* }
* },
* rights: {
* read: true,
* write: true,
* delete: true
* },
* branches: {
* b1: '#998067142c7ff8067cd0c04a0ec4ef80d865606c',
* master: '#36df6f8c17b2ccf4e35a2a75b1e0adb928f82a61'
* }
* }
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {object} callback.projectInfo - An object with info about the project.
*
* @return {external:Promise} On success the promise will be resolved with
* {object} <b>projectInfo</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getProjectInfo = function (callback) {
throw new Error('getProjectInfo must be overridden in derived class');
};
/**
* Updates the head of the branch.
* @param {string} branchName - Name of branch to update.
* @param {module:Storage~CommitHash} newHash - New commit hash for branch head.
* @param {module:Storage~CommitHash} oldHash - Current state of the branch head inside the database.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Storage~CommitResult} callback.result - Status about the branch update.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitResult} <b>result</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.setBranchHash = function (branchName, newHash, oldHash, callback) {
throw new Error('setBranchHash must be overridden in derived class');
};
/**
* Retrieves the commit hash for the head of the branch.
* @param {string} branchName - Name of branch.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Storage~CommitHash} callback.commitHash - The commit hash.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitHash} <b>commitHash</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getBranchHash = function (branchName, callback) {
throw new Error('getBranchHash must be overridden in derived class');
};
/**
* Retrieves the root hash at the provided branch or commit-hash.
* @param {string} branchNameOrCommitHash - Name of branch or a commit-hash.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Core~ObjectHash} callback.rootHash - The root hash.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Core~ObjectHash} <b>rootHash</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getRootHash = function (branchNameOrCommitHash, callback) {
return this.getCommitObject(branchNameOrCommitHash)
.then(function (commitObj) {
return commitObj.root;
})
.nodeify(callback);
};
/**
* Creates a new branch with head pointing to the provided commit hash.
* @param {string} branchName - Name of branch to create.
* @param {module:Storage~CommitHash} newHash - New commit hash for branch head.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Storage~CommitResult} callback.result - Status about the branch update.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitResult} <b>result</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.createBranch = function (branchName, newHash, callback) {
throw new Error('createBranch must be overridden in derived class');
};
/**
* Deletes the branch.
* @param {string} branchName - Name of branch to create.
* @param {module:Storage~CommitHash} oldHash - Previous commit hash for branch head.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Storage~CommitResult} callback.result - Status about the branch update.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitResult} <b>result</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.deleteBranch = function (branchName, oldHash, callback) {
throw new Error('deleteBranch must be overridden in derived class');
};
/**
* Retrieves all branches and their current heads within the project.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {Object.<string, module:Storage~CommitHash>} callback.branches - An object with branch names as keys
* and their commit-hashes as values.
* @return {external:Promise} On success the promise will be resolved with
* Object.<string, {@link module:Storage~CommitHash}> <b>branches</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getBranches = function (callback) {
throw new Error('getBranches must be overridden in derived class');
};
/**
* Retrieves the commit-object at the provided branch or commit-hash.
* @param {string} branchNameOrCommitHash - Name of branch or a commit-hash.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {module:Storage~CommitObject} callback.commit - The commit-object.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitObject} <b>commitObject</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getCommitObject = function (branchNameOrCommitHash, callback) {
var self = this,
commitDeferred;
if (REGEXP.HASH.test(branchNameOrCommitHash)) {
commitDeferred = Q(branchNameOrCommitHash);
} else {
commitDeferred = this.getBranchHash(branchNameOrCommitHash);
}
return commitDeferred
.then(function (commitHash) {
return Q.ninvoke(self, 'loadObject', commitHash);
})
.nodeify(callback);
};
/**
* Retrieves an array of commits starting from a branch(es) and/or commitHash(es).
* <br> The result is ordered by the rules (applied in order)
* <br> 1. Descendants are always returned before their ancestors.
* <br> 2. By their timestamp.
* @param {string|module:Storage~CommitHash|string[]|module:Storage~CommitHash[]} start - Branch name,
* commit hash or array of these.
* @param {number} number - Number of commits to load.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {Array.<module:Storage~CommitObject>} callback.commits - The commits that match the input ordered
* as explained.
*
* @return {external:Promise} On success the promise will be resolved with
* Array.<{@link module:Storage~CommitObject}> <b>commits</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getHistory = function (start, number, callback) {
throw new Error('getHistory must be overridden in derived class');
};
/**
* Retrieves and array of the latest (sorted by timestamp) commits for the project.
* If timestamp is given it will get <b>number</b> of commits strictly before <b>before</b>.
* If commit hash is specified that commit will be included too.
* <br> N.B. due to slight time differences on different machines, ancestors may be returned before
* their descendants. Unless looking for 'headless' commits 'getHistory' is the preferred method.
* @param {number|module:Storage~CommitHash} before - Timestamp or commitHash to load history from.
* @param {number} number - Number of commits to load.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {Array.<module:Storage~CommitObject>} callback.commits - The commits that match the input, ordered
* by their time of insertion.
*
* @return {external:Promise} On success the promise will be resolved with
* Array.<{@link module:Storage~CommitObject}> <b>commits</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getCommits = function (before, number, callback) {
throw new Error('getCommits must be overridden in derived class');
};
/**
* Creates a new tag pointing to the provided commit hash.
* @param {string} tagName - Name of tag to create.
* @param {module:Storage~CommitHash} commitHash - Commit hash tag will point to.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
*
* @return {external:Promise} On success the promise will be resolved with nothing.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.createTag = function (tagName, commitHash, callback) {
throw new Error('createTag must be overridden in derived class');
};
/**
* Deletes the given tag.
* @param {string} tagName - Name of tag to delete.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
*
* @return {external:Promise} On success the promise will be resolved with nothing.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.deleteTag = function (tagName, callback) {
throw new Error('deleteTag must be overridden in derived class');
};
/**
* Retrieves all tags and their commits hashes within the project.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution.
* @param {Object.<string, module:Storage~CommitHash>} callback.tags - An object with tag names as keys and
* their commit-hashes as values.
* @return {external:Promise} On success the promise will be resolved with
* Object.<string, {@link module:Storage~CommitHash}> <b>tags</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getTags = function (callback) {
throw new Error('getTags must be overridden in derived class');
};
/**
* Retrieves the common ancestor of two commits. If no ancestor exists it will result in an error.
*
* @param {module:Storage~CommitHash} commitA - Commit hash.
* @param {module:Storage~CommitHash} commitB - Commit hash.
* @param {function} [callback] - If provided no promise will be returned.
* @param {null|Error} callback.error - The result of the execution (will be non-null if e.g. the commits do
* not exist or have no common ancestor).
* @param {module:Storage~CommitHash} callback.commitHash - The commit hash of the common ancestor.
*
* @return {external:Promise} On success the promise will be resolved with
* {@link module:Storage~CommitHash} <b>commitHash</b>.<br>
* On error the promise will be rejected with {@link Error} <b>error</b>.
*/
this.getCommonAncestorCommit = function (commitA, commitB, callback) {
throw new Error('getCommonAncestorCommit must be overridden in derived class');
};
/**
* Return the identity of the current user of this project.
* @return {string} the userId
*/
this.getUserId = function () {
throw new Error('getUserId must be overridden in derived class');
};
}
return ProjectInterface;
});