UNPKG

webgme-engine

Version:

WebGME server and Client API without a GUI

492 lines (412 loc) 17.4 kB
/*globals requireJS*/ /*eslint-env node*/ /** * @module Server:Storage:Memory * @author lattmann / https://github.com/lattmann */ 'use strict'; var Q = require('q'), REGEXP = requireJS('common/regexp'), CANON = requireJS('common/util/canon'), CONSTANT = requireJS('common/storage/constants'), TAGS = 'TAGS', SEPARATOR = '$'; /** * An in-memory implementation of backing the data for webgme. * * memory$newProject13$ = MemoryProject * memory$newProject13$#commitHash1 = "{"_id":"#commitHash1","time":1,"type":"commit"}" * memory$newProject13$#coreDataObjectHash = "{"_id":"#coreDataObjectHash", ...}" * memory$newProject13$*b1 = "{"_id":"b1","hash":"#newHash1"}" * memory$newProject13$TAGS = "{ "tag1": "#commitHash1", "tag2": "#commitHash2"} * * @param mainLogger * @constructor */ function Memory(mainLogger, gmeConfig) { var logger = mainLogger.fork('memory'), database = 'memory', // is this constant or coming from the GME config? storage = { length: 0, keys: [], data: {}, connected: false, connect: function () { this.connected = true; }, close: function () { this.connected = false; }, getItem: function (key) { return this.data[key]; }, setItem: function (key, object) { this.data[key] = object; if (this.keys.indexOf(key) === -1) { this.keys.push(key); this.length = this.keys.length; } }, removeItem: function (key) { delete this.data[key]; var index = this.keys.indexOf(key); if (index > -1) { this.keys.splice(index, 1); this.length = this.keys.length; } }, key: function (index) { return this.keys[index]; } }; this.gmeConfig = gmeConfig; function MemoryProject(projectId) { var self = this; this.projectId = projectId; if (storage.connected) { storage.setItem(database + SEPARATOR + projectId + SEPARATOR, this); } //else { // TODO: error handling //} this.closeProject = function (callback) { var deferred = Q.defer(); deferred.resolve(); return deferred.promise.nodeify(callback); }; this.loadObject = function (hash, callback) { var deferred = Q.defer(), object; if (typeof hash !== 'string') { deferred.reject(new Error('loadObject - given hash is not a string : ' + typeof hash)); } else if (!REGEXP.HASH.test(hash)) { deferred.reject(new Error('loadObject - invalid hash :' + hash)); } else { object = storage.getItem(database + SEPARATOR + projectId + SEPARATOR + hash); if (object) { object = JSON.parse(object); deferred.resolve(object); } else { deferred.reject(new Error('object does not exist ' + hash)); } } return deferred.promise.nodeify(callback); }; this.insertObject = function (object, callback) { var deferred = Q.defer(), errMsg, objectStr; if (object === null || typeof object !== 'object') { deferred.reject(new Error('object is not an object')); } else if (typeof object._id !== 'string' || !REGEXP.HASH.test(object._id)) { deferred.reject(new Error('object._id is not a valid hash.')); } else { objectStr = storage.getItem(database + SEPARATOR + projectId + SEPARATOR + object._id); if (objectStr) { if (CANON.stringify(object) === CANON.stringify(JSON.parse(objectStr))) { logger.debug('tried to insert existing hash - the two objects were equal', object._id); deferred.resolve(); } else { errMsg = 'tried to insert existing hash - the two objects were NOT equal '; logger.error(errMsg, { metadata: { newObject: CANON.stringify(object), oldObject: CANON.stringify(JSON.parse(objectStr)) } }); deferred.reject(new Error(errMsg + object._id)); } } else { try { storage.setItem(database + SEPARATOR + projectId + SEPARATOR + object._id, JSON.stringify(object)); deferred.resolve(); } catch (e) { deferred.reject(e); } } } return deferred.promise.nodeify(callback); }; this.getBranches = function (callback) { var deferred = Q.defer(), branchNames = {}, pending = 0, i, done = function () { if (i === storage.length && pending === 0) { deferred.resolve(branchNames); } }, updateBranchEntry = function (branchName) { self.getBranchHash(branchName, function (err, hash) { pending -= 1; branchNames[branchName] = hash; done(); }); }; for (i = 0; i < storage.length; i += 1) { var keyArray = storage.key(i).split(SEPARATOR); if (REGEXP.RAW_BRANCH.test(keyArray[2])) { if (keyArray[0] === database && keyArray[1] === projectId) { // TODO: double check this line, *master => master, and return with an object of branches var branchName = keyArray[2].slice(1); pending += 1; updateBranchEntry(branchName); } } } done(); return deferred.promise.nodeify(callback); }; this.getBranchHash = function (branch, callback) { var deferred = Q.defer(), hash = storage.getItem(database + SEPARATOR + projectId + SEPARATOR + '*' + branch); if (hash) { hash = JSON.parse(hash); } hash = (hash && hash.hash) || ''; deferred.resolve(hash); return deferred.promise.nodeify(callback); }; this.setBranchHash = function (branch, oldhash, newhash, callback) { var deferred = Q.defer(), hash = storage.getItem(database + SEPARATOR + projectId + SEPARATOR + '*' + branch); if (hash) { hash = JSON.parse(hash); } hash = (hash && hash.hash) || ''; if (oldhash === newhash) { if (oldhash === hash) { deferred.resolve(); } else { deferred.reject(new Error('branch hash mismatch')); } } else { if (oldhash === hash) { if (newhash === '') { storage.removeItem(database + SEPARATOR + projectId + SEPARATOR + '*' + branch); } else { storage.setItem(database + SEPARATOR + projectId + SEPARATOR + '*' + branch, JSON.stringify({ _id: branch, hash: newhash })); } deferred.resolve(); } else if (hash === '' && newhash === '') { deferred.resolve(); } else { deferred.reject(new Error('branch hash mismatch')); } } return deferred.promise.nodeify(callback); }; this.getCommits = function (before, number, callback) { var deferred = Q.defer(), finds = [], i, item, object; for (i = 0; i < storage.length; i += 1) { if (storage.key(i).indexOf(database + SEPARATOR + projectId + SEPARATOR) === 0) { item = storage.getItem(storage.key(i)); if (typeof item === 'string') { object = JSON.parse(item); if (object.type === CONSTANT.COMMIT_TYPE && object.time < before) { finds.push(object); if (finds.length === number) { break; } } } } } finds.sort(function (a, b) { return b.time - a.time; }); deferred.resolve(finds); return deferred.promise.nodeify(callback); }; this.createTag = function (name, commitHash, callback) { var deferred = Q.defer(), tags = storage.getItem(database + SEPARATOR + projectId + SEPARATOR + TAGS); if (Object.hasOwn(tags, name) === true) { deferred.reject(new Error('Tag already exists [' + name + ']')); } else { tags[name] = commitHash; deferred.resolve(); } return deferred.promise.nodeify(callback); }; this.deleteTag = function (name, callback) { var deferred = Q.defer(), tags = storage.getItem(database + SEPARATOR + projectId + SEPARATOR + TAGS); delete tags[name]; deferred.resolve(); return deferred.promise.nodeify(callback); }; this.getTags = function (callback) { var deferred = Q.defer(); deferred.resolve(JSON.parse(JSON.stringify( storage.getItem(database + SEPARATOR + projectId + SEPARATOR + TAGS) ))); return deferred.promise.nodeify(callback); }; } function openDatabase(callback) { var deferred = Q.defer(); logger.debug('openDatabase'); if (storage.connected) { logger.debug('Reusing in-memory database connection.'); // we are already connected deferred.resolve(); } else { logger.debug('Connecting to in-memory database...'); storage.connect(); logger.debug('Connected.'); deferred.resolve(); } return deferred.promise.nodeify(callback); } function closeDatabase(callback) { var deferred = Q.defer(); logger.debug('closeDatabase'); if (storage.connected) { logger.debug('Closing in-memory database and cleaning up...'); storage.close(); logger.debug('Closed.'); deferred.resolve(); } else { logger.debug('No in-memory database connection was established.'); deferred.resolve(); } return deferred.promise.nodeify(callback); } function createProject(projectId, callback) { var deferred = Q.defer(), project; logger.debug('createProject', projectId); if (storage.connected) { project = storage.getItem(database + SEPARATOR + projectId + SEPARATOR); if (project) { deferred.reject(new Error('Project already exists ' + projectId)); } else { storage.setItem(database + SEPARATOR + projectId + SEPARATOR, '{}'); storage.setItem(database + SEPARATOR + projectId + SEPARATOR + TAGS, {}); deferred.resolve(new MemoryProject(projectId)); } } else { deferred.reject(new Error('Database is not open.')); } return deferred.promise.nodeify(callback); } function deleteProject(projectId, callback) { var deferred = Q.defer(), key, keyArray, i, namesToRemove = [], existed = false; if (storage.connected) { for (i = 0; i < storage.length; i += 1) { key = storage.key(i); keyArray = key.split(SEPARATOR); if (keyArray[0] === database) { if (keyArray[1] === projectId) { namesToRemove.push(key); } } } for (i = 0; i < namesToRemove.length; i += 1) { storage.removeItem(namesToRemove[i]); } existed = namesToRemove.length > 0; deferred.resolve(existed); } else { deferred.reject(new Error('Database is not open.')); } return deferred.promise.nodeify(callback); } function openProject(projectId, callback) { var deferred = Q.defer(), project; logger.debug('openProject', projectId); if (storage.connected) { project = storage.getItem(database + SEPARATOR + projectId + SEPARATOR); if (project) { deferred.resolve(new MemoryProject(projectId)); } else { deferred.reject(new Error('Project does not exist ' + projectId)); } } else { deferred.reject(new Error('Database is not open.')); } return deferred.promise.nodeify(callback); } function _moveProject(projectId, newProjectId, remove, callback) { var deferred = Q.defer(), key, keyArray, i, namesToRemove = [], oldProjectKey, newProject, oldProject, namesToAdd = []; if (storage.connected) { newProject = storage.getItem(database + SEPARATOR + newProjectId + SEPARATOR); oldProject = storage.getItem(database + SEPARATOR + projectId + SEPARATOR); if (newProject) { deferred.reject(new Error('Project already exists ' + newProjectId)); } else if (!oldProject) { deferred.reject(new Error('Project does not exist ' + projectId)); } else { storage.setItem(database + SEPARATOR + newProjectId + SEPARATOR, '{}'); for (i = 0; i < storage.length; i += 1) { key = storage.key(i); keyArray = key.split(SEPARATOR); if (keyArray[0] === database) { if (keyArray[1] === projectId) { if (keyArray[2]) { namesToRemove.push(key); namesToAdd.push(keyArray[0] + SEPARATOR + newProjectId + SEPARATOR + keyArray[2]); } else { oldProjectKey = keyArray[0] + SEPARATOR + projectId + SEPARATOR; } } } } for (i = 0; i < namesToRemove.length; i += 1) { storage.setItem(namesToAdd[i], storage.getItem(namesToRemove[i])); if (remove) { storage.removeItem(namesToRemove[i]); } } if (remove) { storage.removeItem(oldProjectKey); } deferred.resolve(new MemoryProject(newProjectId)); } } else { deferred.reject(new Error('Database is not open.')); } return deferred.promise.nodeify(callback); } function renameProject(projectId, newProjectId, callback) { return _moveProject(projectId, newProjectId, true) .nodeify(callback); } function duplicateProject(projectId, newProjectId, callback) { return _moveProject(projectId, newProjectId, false) .nodeify(callback); } this.openDatabase = openDatabase; this.closeDatabase = closeDatabase; this.createProject = createProject; this.deleteProject = deleteProject; this.openProject = openProject; this.renameProject = renameProject; this.duplicateProject = duplicateProject; } module.exports = Memory;