webgme-engine
Version:
WebGME server and Client API without a GUI
331 lines (301 loc) • 13.5 kB
JavaScript
/*globals define*/
/*eslint-env node, browser*/
/**
* @author lattmann / https://github.com/lattmann
*/
define([
'common/storage/constants',
'common/util/jsonPatcher',
'q',
'common/regexp',
'common/util/key'
], function (CONSTANTS, jsonPatcher, Q, REGEXP, generateKey) {
'use strict';
/**
* @param {ProjectInterface} project
* @param {object} parameters - If more than one is given, the order of precedence is:
* branchName, commitHash, tagName and rootHash.
* @param {string} [parameters.rootHash] - The hash of the tree root.
* @param {string} [parameters.commitHash] - The tree associated with the commitHash.
* @param {string} [parameters.tagName] - The tree at the given tag.
* @param {string} [parameters.branchName] - The tree at the given branch.
* @param {function} [callback]
* @returns {Promise}
*/
function getRootHash(project, parameters, callback) {
var deferred = Q.defer();
if (parameters.branchName) {
Q.ninvoke(project, 'getBranchHash', parameters.branchName)
.then(function (commitHash) {
parameters.commitHash = commitHash;
return Q.ninvoke(project, 'loadObject', commitHash);
})
.then(function (commitObject) {
parameters.rootHash = commitObject.root;
deferred.resolve(commitObject.root);
})
.catch(deferred.reject);
} else if (parameters.commitHash) {
Q.ninvoke(project, 'loadObject', parameters.commitHash)
.then(function (commitObject) {
parameters.rootHash = commitObject.root;
deferred.resolve(commitObject.root);
})
.catch(deferred.reject);
} else if (parameters.tagName) {
Q.ninvoke(project, 'getTags')
.then(function (tags) {
if (tags[parameters.tagName]) {
parameters.commitHash = tags[parameters.tagName];
return Q.ninvoke(project, 'loadObject', tags[parameters.tagName]);
} else {
throw new Error('Unknown tag name [' + parameters.tagName + ']');
}
})
.then(function (commitObject) {
parameters.rootHash = commitObject.root;
deferred.resolve(commitObject.root);
})
.catch(deferred.reject);
} else if (parameters.rootHash) {
deferred.resolve(parameters.rootHash);
} else {
deferred.reject(new Error('No valid input was given to search for rootHash'));
}
return deferred.promise.nodeify(callback);
}
function _collectObjects(project, objectHashArray) {
var deferred = Q.defer(),
promises = [],
objects = [],
i;
for (i = 0; i < objectHashArray.length; i += 1) {
promises.push(Q.ninvoke(project, 'loadObject', objectHashArray[i]));
}
Q.allSettled(promises)
.then(function (results) {
var error = null,
i;
for (i = 0; i < results.length; i += 1) {
if (results[i].state === 'fulfilled') {
objects.push(results[i].value);
} else {
error = error || results[i].reason || new Error('unable to load');
}
}
if (error) {
deferred.reject(error);
} else {
deferred.resolve(objects);
}
});
return deferred.promise;
}
function _collectObjectAndAssetHashes(project, rootHash) {
var deferred = Q.defer(),
objects = {},
assets = {},
queue = [rootHash],
task,
error = null,
working = false,
timerId;
timerId = setInterval(function () {
if (!working) {
task = queue.shift();
if (task === undefined) {
clearInterval(timerId);
if (error) {
deferred.reject(error);
} else {
deferred.resolve({objects: Object.keys(objects), assets: Object.keys(assets)});
}
return;
}
if (!objects[task]) {
working = true;
project.loadObject(task, function (err, object) {
var key;
error = error || err;
if (!err && object) {
objects[task] = true;
if (object) {
//now put every sub-object on top of the queue
for (key in object) {
if (typeof object[key] === 'string' && REGEXP.HASH.test(object[key])) {
queue.push(object[key]);
}
}
//looking for assets
if (object.atr) {
for (key in object.atr) {
//TODO why can't we inlcude BlobConfig???
if (typeof object.atr[key] === 'string' &&
REGEXP.BLOB_HASH.test(object.atr[key])) {
assets[object.atr[key]] = true;
}
}
}
//checking if the node has a sharded overlay, we do not load the shards, yet
if (object.ovr && object.ovr.sharded === true) {
for (key in object.ovr) {
if (typeof object.ovr[key] === 'string' &&
REGEXP.HASH.test(object.ovr[key])) {
objects[object.ovr[key]] = true;
}
}
}
}
}
working = false;
});
}
}
}, 1);
return deferred.promise;
}
return {
CONSTANTS: CONSTANTS,
getProjectFullNameFromProjectId: function (projectId) {
if (projectId) {
return projectId.replace(CONSTANTS.PROJECT_ID_SEP, CONSTANTS.PROJECT_DISPLAYED_NAME_SEP);
}
},
getProjectDisplayedNameFromProjectId: function (projectId) {
if (projectId) {
return projectId.replace(CONSTANTS.PROJECT_ID_SEP, ' ' + CONSTANTS.PROJECT_DISPLAYED_NAME_SEP + ' ');
}
},
getProjectIdFromProjectFullName: function (projectFullName) {
if (projectFullName) {
return projectFullName.replace(CONSTANTS.PROJECT_DISPLAYED_NAME_SEP, CONSTANTS.PROJECT_ID_SEP);
}
},
getProjectIdFromOwnerIdAndProjectName: function (userId, projectName) {
return userId + CONSTANTS.PROJECT_ID_SEP + projectName;
},
getProjectNameFromProjectId: function (projectId) {
if (projectId) {
return projectId.substring(projectId.indexOf(CONSTANTS.PROJECT_ID_SEP) + 1);
}
},
getOwnerFromProjectId: function (projectId) {
if (projectId) {
return projectId.substring(0, projectId.indexOf(CONSTANTS.PROJECT_ID_SEP));
}
},
getHashTaggedHash: function (hash) {
if (typeof hash === 'string') {
return hash[0] === '#' ? hash : '#' + hash;
}
return hash;
},
getPatchObject: function (oldData, newData) {
var patchObject = {
type: 'patch',
base: oldData[CONSTANTS.MONGO_ID],
patch: jsonPatcher.create(oldData, newData)
};
patchObject[CONSTANTS.MONGO_ID] = newData[CONSTANTS.MONGO_ID];
return patchObject;
},
coreObjectHasOldAndNewData: function (coreObj) {
return !!(coreObj.oldHash && coreObj.newHash && coreObj.oldData && coreObj.newData);
},
getChangedNodes: jsonPatcher.getChangedNodes,
applyPatch: jsonPatcher.apply,
checkHashConsistency: function (gmeConfig, dataObj, hash) {
var result;
if (gmeConfig.storage.keyType === 'rand160Bits') {
// Random hashes should not be checked.
result = true;
} else if (gmeConfig.storage.disableHashChecks === true) {
// Configured to not check.
result = true;
} else {
dataObj[CONSTANTS.MONGO_ID] = '';
result = hash === '#' + generateKey(dataObj, gmeConfig);
}
return result;
},
/**
* Extracts a serializable json representation of a project tree.
* To specify starting point set one of the four options. If more than one is set the order of precedence is:
* branchName, commitHash, tagName and rootHash.
*
* @param {ProjectInterface} project
* @param {object} parameters - Specifies which project tree should be serialized:
* @param {string} [parameters.rootHash] - The hash of the tree root.
* @param {string} [parameters.commitHash] - The tree associated with the commitHash.
* @param {string} [parameters.tagName] - The tree at the given tag.
* @param {string} [parameters.branchName] - The tree at the given branch.
* @param {string} [parameters.kind] - If not given will assign the one in project.
* @param {function} callback
*/
getProjectJson: function (project, parameters, callback) {
var deferred = Q.defer(),
rawJson;
getRootHash(project, parameters || {})
.then(function (rootHash) {
return Q.all([
_collectObjectAndAssetHashes(project, rootHash),
project.getProjectInfo()
]);
})
.then(function (res) {
var hashes = res[0],
info = res[1];
rawJson = {
rootHash: parameters.rootHash,
projectId: project.projectId,
kind: typeof parameters.kind === 'string' ? parameters.kind : info.info.kind,
branchName: parameters.branchName,
commitHash: parameters.commitHash,
hashes: hashes,
objects: null
};
return _collectObjects(project, hashes.objects);
})
.then(function (objects) {
rawJson.objects = objects;
deferred.resolve(rawJson);
})
.catch(deferred.reject);
return deferred.promise.nodeify(callback);
},
/**
* Inserts a serialized project tree into the storage and associates it with a commitHash.
*
* @param {ProjectInterface} project
* @param {object} [options]
* @param {string} [options.branch] - Name of branch to update
* @param {string} [options.parentCommit] - Array of parents for new commit
* @param {string} [options.commitMessage=%defaultCommitMessage%] information about the insertion
* @param {function(Error, hashes)} callback
*/
insertProjectJson: function (project, projectJson, options, callback) {
var deferred = Q.defer(),
toPersist = {},
rootHash = projectJson.rootHash,
defaultCommitMessage = 'Importing contents of [' +
projectJson.projectId + '@' + rootHash + ']',
objects = projectJson.objects,
i;
for (i = 0; i < objects.length; i += 1) {
// we have to patch the object right before import, for smoother usage experience
toPersist[objects[i]._id] = objects[i];
}
options = options || {};
options.branch = options.branch || null;
options.parentCommit = options.parentCommit || [];
project.makeCommit(options.branch, options.parentCommit,
rootHash, toPersist, options.commitMessage || defaultCommitMessage)
.then(function (commitResult) {
deferred.resolve(commitResult);
})
.catch(deferred.reject);
return deferred.promise.nodeify(callback);
},
getRootHash: getRootHash
};
});