UNPKG

webgme-engine

Version:

WebGME server and Client API without a GUI

1,102 lines (954 loc) 44.3 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: plugin/PluginBase.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: plugin/PluginBase.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/*globals define, requirejs*/ /*eslint-env node, browser*/ /** * This is the base class that plugins should inherit from. * (Using the PluginGenerator - the generated plugin will do that.) * * @author lattmann / https://github.com/lattmann * @author pmeijer / https://github.com/pmeijer */ (function (factory) { if (typeof define === 'function' &amp;&amp; define.amd) { define([ 'q', 'plugin/PluginConfig', 'plugin/PluginResultBase', 'plugin/PluginResult', 'plugin/InterPluginResult', 'plugin/PluginMessage', 'plugin/PluginNodeDescription', 'plugin/util', 'common/storage/constants' ], factory); } else if (typeof module === 'object' &amp;&amp; module.exports) { module.exports = factory( require('q'), require('./PluginConfig'), require('./PluginResultBase'), require('./PluginResult'), require('./InterPluginResult'), require('./PluginMessage'), require('./PluginNodeDescription'), require('./util'), require('../common/storage/constants') ); } }(function (Q, PluginConfig, PluginResultBase, PluginResult, InterPluginResult, PluginMessage, PluginNodeDescription, pluginUtil, STORAGE_CONSTANTS) { 'use strict'; /** * Initializes a new instance of a plugin object, which should be a derived class. * * @constructor * @alias PluginBase */ function PluginBase() { // set by initialize /** * @type {PluginMetadata} */ this.pluginMetadata = null; /** * @type {GmeConfig} */ this.gmeConfig = null; /** * @type {GmeLogger} */ this.logger = null; /** * @type {BlobClient} */ this.blobClient = null; this._currentConfig = null; // set by configure /** * @type {Core} */ this.core = null; /** * @type {ProjectInterface} */ this.project = null; this.projectName = null; this.projectId = null; this.branchName = null; this.branchHash = null; this.commitHash = null; this.currentHash = null; /** * @type {module:Core~Node} */ this.rootNode = null; /** * @type {module:Core~Node} */ this.activeNode = null; /** * @type {module:Core~Node[]} */ this.activeSelection = []; /** * The namespace the META nodes are coming from (set by invoker). * The default is the full meta, i.e. the empty string namespace. * For example, if a project has a library A with a library B. The possible namespaces are: * '', 'A' and 'A.B'. * @type {string} */ this.namespace = ''; /** * The resolved META nodes based on the active namespace. Index by the fully qualified meta node names * with the namespace stripped off at the start. * * For example, if a project has a library A with a library B. If the project and the libraries all have * two meta nodes named a and b. Depending on the namespace the META will have the following keys: * * 1) namespace = '' -> ['a', 'b', 'A.a', 'A.b', 'A.B.a', 'A.B.b'] * 2) namespace = 'A' -> ['a', 'b', 'B.a', 'B.b'] * 3) namespace = 'A.B' -> ['a', 'b'] * * (N.B. 'a' and 'b' in example 3) are pointing to the meta nodes defined in A.B.) * * @type {Object&lt;string, module:Core~Node>} */ this.META = null; /** * @type {PluginResultBase} */ this.result = null; this.isConfigured = false; this.callDepth = 0; this.notificationHandlers = []; this.invokedPlugins = []; } //&lt;editor-fold desc="Methods must be overridden by the derived classes"> /** * Main function for the plugin to execute. This will perform the execution. * Notes: * &lt;br>- Do NOT use console.log use this.logger.[error,warning,info,debug] instead. * &lt;br>- Do NOT put any user interaction logic UI, etc. inside this function. * &lt;br>- callback always have to be called even if error happened. * * @param {function} callback - the result callback * @param {null|Error} callback.err - status of the call * @param {PluginResult} callback.result - plugin result */ PluginBase.prototype.main = function (/*callback*/) { throw new Error('implement this function in the derived class'); }; /** * Readable name of this plugin that can contain spaces. * * @returns {string} */ PluginBase.prototype.getId = function () { if (this.pluginMetadata) { return this.pluginMetadata.id; } else { throw new Error('pluginMetadata is not defined - implement this function in the derived class'); } }; /** * Readable name of this plugin that can contain spaces. * * @returns {string} */ PluginBase.prototype.getName = function () { if (this.pluginMetadata) { return this.pluginMetadata.name; } else { throw new Error('pluginMetadata is not defined - implement this function in the derived class'); } }; //&lt;/editor-fold> //&lt;editor-fold desc="Methods could be overridden by the derived classes"> /** * Current version of this plugin using semantic versioning. * @returns {string} */ PluginBase.prototype.getVersion = function () { return this.pluginMetadata ? this.pluginMetadata.version : '0.1.0'; }; /** * A detailed description of this plugin and its purpose. It can be one or more sentences. * * @returns {string} */ PluginBase.prototype.getDescription = function () { return this.pluginMetadata ? this.pluginMetadata.description : ''; }; /** * Configuration structure with names, descriptions, minimum, maximum values, default values and * type definitions. * * Example: * * [{ * "name": "logChildrenNames", * "displayName": "Log Children Names", * "description": '', * "value": true, // this is the 'default config' * "valueType": "boolean", * "readOnly": false * },{ * "name": "logLevel", * "displayName": "Logger level", * "description": '', * "value": "info", * "valueType": "string", * "valueItems": [ * "debug", * "info", * "warn", * "error" * ], * "readOnly": false * },{ * "name": "maxChildrenToLog", * "displayName": "Maximum children to log", * "description": 'Set this parameter to blabla', * "value": 4, * "minValue": 1, * "valueType": "number", * "readOnly": false * }] * * @returns {object[]} */ PluginBase.prototype.getConfigStructure = function () { return this.pluginMetadata ? this.pluginMetadata.configStructure : []; }; //&lt;/editor-fold> //&lt;editor-fold desc="Methods that can be used by the derived classes"> /** * Updates the current success flag with a new value. * * NewValue = OldValue &amp;&amp; Value * * @param {boolean} value - apply this flag on current success value * @param {string|null} message - optional detailed message */ PluginBase.prototype.updateSuccess = function (value, message) { var prevSuccess = this.result.getSuccess(); var newSuccessValue = prevSuccess &amp;&amp; value; this.result.setSuccess(newSuccessValue); var msg = ''; if (message) { msg = ' - ' + message; } this.logger.debug('Success was updated from ' + prevSuccess + ' to ' + newSuccessValue + msg); }; /** * WebGME can export the META types as path and this method updates the generated domain specific types with * webgme node objects. These can be used to define the base class of new objects created through the webgme API. * * @param {object} generatedMETA */ PluginBase.prototype.updateMETA = function (generatedMETA) { var name; for (name in this.META) { if (Object.hasOwn(this.META, name)) { generatedMETA[name] = this.META[name]; } } // TODO: check if names are not the same // TODO: log if META is out of date }; /** * Checks if the given node is of the given meta-type. * Usage: &lt;tt>self.isMetaTypeOf(aNode, self.META['FCO']);&lt;/tt> * @param {module:Core~Node} node - Node to be checked for type. * @param {module:Core~Node} metaNode - Node object defining the meta type. * @returns {boolean} - True if the given object was of the META type. */ PluginBase.prototype.isMetaTypeOf = function (node, metaNode) { if (metaNode) { return this.core.isTypeOf(node, metaNode); } return false; }; /** * Finds and returns the node object defining the meta type for the given node. * @param {module:Core~Node} node - Node to be checked for type. * @returns {module:Core~Node} - Node object defining the meta type of node. */ PluginBase.prototype.getMetaType = function (node) { return this.core.getMetaType(node); }; /** * Returns true if node is a direct instance of a meta-type node (or a meta-type node itself). * @param {module:Core~Node} node - Node to be checked. * @returns {boolean} */ PluginBase.prototype.baseIsMeta = function (node) { var self = this, baseName, namespace, baseNode = self.core.getBase(node); if (!baseNode) { // FCO does not have a base node, by definition function returns true. return true; } baseName = self.core.getAttribute(baseNode, 'name'); namespace = self.core.getNamespace(baseNode).substr(self.namespace.length); if (namespace) { baseName = namespace + '.' + baseName; } return Object.hasOwn(self.META, baseName) &amp;&amp; self.core.getGuid(self.META[baseName]) === self.core.getGuid(baseNode); }; /** * Gets the current configuration of the plugin that was set by the user and plugin manager. * * @returns {PluginConfig} */ PluginBase.prototype.getCurrentConfig = function () { return this._currentConfig; }; /** * Creates a new message for the user and adds it to the result. * * @param {module:Core~Node|object} node - webgme object which is related to the message * @param {string} message - feedback to the user * @param {string} severity - severity level of the message: 'debug', 'info' (default), 'warning', 'error'. */ PluginBase.prototype.createMessage = function (node, message, severity) { var severityLevel = severity || 'info'; var descriptor = new PluginNodeDescription({ name: node ? this.core.getAttribute(node, 'name') : '', id: node ? this.core.getPath(node) : '' }); var pluginMessage = new PluginMessage({ commitHash: this.currentHash, activeNode: descriptor, message: message, severity: severityLevel }); this.result.addMessage(pluginMessage); }; /** * Sends a notification back to the invoker of the plugin, can be used to notify about progress. * @param {string|object} message - Message string or object containing message. * @param {string} message.message - If object it must contain a message. * @param {number} [message.progress] - Approximate progress (in %) of the plugin at time of sending. * @param {string} [message.severity='info'] - Severity level ('success', 'info', 'warn', 'error') * @param {boolean} [message.toBranch=false] - If true, and the plugin is running on the server on a branch - * will broadcast to all sockets in the branch room. * @param {function(Error)} [callback] - optional callback invoked when message has been emitted from server. * @param {null|Error} callback.err - status of the call */ PluginBase.prototype.sendNotification = function (message, callback) { var self = this, cnt = self.notificationHandlers.length, notification = {}, data = { type: STORAGE_CONSTANTS.PLUGIN_NOTIFICATION, notification: notification, projectId: self.projectId, branchName: self.branchName, pluginName: self.getName(), pluginId: self.getId(), pluginVersion: self.getVersion() }; if (typeof message === 'string') { notification.message = message; notification.severity = notification.severity || 'info'; } else { data.notification = message; } callback = callback || function (err) { if (err) { self.logger.error(err); } }; function emitToHandlers() { if (cnt === 0) { callback(null); return; } cnt -= 1; self.notificationHandlers[cnt](data, function (err) { if (err) { callback(err); } else { emitToHandlers(); } }); } emitToHandlers(); }; /** * Saves all current changes if there is any to a new commit. * If the commit result is either 'FORKED' or 'CANCELED', it creates a new branch. * * N.B. This is a utility function for saving/persisting data. The plugin has access to the project and core * instances and may persist and make the commit as define its own behavior for e.g. 'FORKED' commits. * To report the commits in the PluginResult make sure to invoke this.addCommitToResult with the given status. * * @param {string|null} message - commit message * @param {function} [callback] - the result callback * @param {null|Error} callback.err - status of the call * @param {module:Storage~CommitResult} callback.commitResult - status of the commit made * @return {external:Promise} If no callback is given, the result will be provided in a promise */ PluginBase.prototype.save = function (message, callback) { var self = this, persisted, commitMessage = '[Plugin] ' + self.getName() + ' (v' + self.getVersion() + ') updated the model.'; commitMessage = message ? commitMessage + ' - ' + message : commitMessage; if (this.callDepth > 0) { self.logger.debug('Call-depth is greater than zero, will not persist "', this.callDepth, '"'); self.result.addCommitMessage(commitMessage); return Q.resolve({ hash: self.currentHash, // TODO: Do we need a status? Which one? SYNCED so it can proceed? }).nodeify(callback); } self.logger.debug('Saving project'); persisted = self.core.persist(self.rootNode); if (Object.keys(persisted.objects).length === 0) { self.logger.debug('save invoked with no changes, will still proceed'); } return self.project.makeCommit(self.branchName, [self.currentHash], persisted.rootHash, persisted.objects, commitMessage) .then(function (commitResult) { if (commitResult.status === STORAGE_CONSTANTS.SYNCED) { self.currentHash = commitResult.hash; self.logger.debug('"' + self.branchName + '" was updated to the new commit.'); self.addCommitToResult(STORAGE_CONSTANTS.SYNCED); return commitResult; } else if (commitResult.status === STORAGE_CONSTANTS.FORKED) { self.currentHash = commitResult.hash; return self._createFork(); } else if (commitResult.status === STORAGE_CONSTANTS.CANCELED) { // Plugin running in the browser and the client has made changes since plugin was invoked. // Since the commitData was never sent to the server, a commit w/o branch is made before forking. return self.project.makeCommit(null, [self.currentHash], persisted.rootHash, persisted.objects, commitMessage) .then(function (commitResult) { self.currentHash = commitResult.hash; // This is needed in case hash is randomly generated. return self._createFork(); }); } else if (commitResult.status === STORAGE_CONSTANTS.MERGED) { self.currentHash = commitResult.mergeHash; self.addCommitToResult(STORAGE_CONSTANTS.MERGED); // N.B. If the plugin makes multiple saves, it should fast-forward after a merged commit. // Otherwise each new commit will have to be merge as well. return commitResult; } else if (!self.branchName) { self.currentHash = commitResult.hash; self.addCommitToResult(commitResult.status); return commitResult; } else { throw new Error('setBranchHash returned unexpected status' + commitResult.status); } }) .nodeify(callback); }; PluginBase.prototype._createFork = function (callback) { // User can set self.forkName, but must make sure it is unique. var self = this, oldBranchName = self.branchName, forkName = self.forkName || self.branchName + '_' + Date.now(); self.logger.warn('Plugin got forked from "' + self.branchName + '". ' + 'Trying to create a new branch "' + forkName + '".'); return self.project.createBranch(forkName, self.currentHash) .then(function (forkResult) { if (forkResult.status === STORAGE_CONSTANTS.SYNCED) { self.branchName = forkName; self.logger.debug('"' + self.branchName + '" was updated to the new commit.' + '(Successive saves will try to save to this new branch.)'); self.addCommitToResult(STORAGE_CONSTANTS.FORKED); return {status: STORAGE_CONSTANTS.FORKED, forkName: forkName, hash: forkResult.hash}; } else if (forkResult.status === STORAGE_CONSTANTS.FORKED) { self.branchName = null; self.addCommitToResult(STORAGE_CONSTANTS.FORKED); throw new Error('Plugin got forked from "' + oldBranchName + '". ' + 'And got forked from "' + forkName + '" too.'); } else { throw new Error('createBranch returned unexpected status' + forkResult.status); } }) .nodeify(callback); }; /** * If plugin is started from a branch - it will reload the instance's nodes and update the currentHash to * the current hash of the branch. * * N.B. Use this with caution, for instance manually referenced nodes in a plugin will still be part of the * previous commit. Additionally if the namespaces have changed between commits - the this.META might end up * being empty. * @param {function} [callback] - the result callback * @param {null|Error} callback.err - status of the call * @param {boolean} callback.didUpdate - true if there was a change and it updated the state to it * @return {external:Promise} If no callback is given, the result will be provided in a promise */ PluginBase.prototype.fastForward = function (callback) { var self = this, options; if (this.callDepth > 0) { self.logger.warn('callDepth is greater than zero, will not fast-forward "', this.callDepth, '"'); return Q.resolve(false).nodeify(callback); } return self.project.getBranchHash(self.branchName) .then(function (branchHash) { if (branchHash === '') { throw new Error('Branch does not exist [' + self.branchName + ']'); } else if (branchHash === self.currentHash) { return false; } else { options = { activeNode: self.core.getPath(self.activeNode), activeSelection: self.activeSelection.forEach(function (node) { return self.core.getPath(node); }), namespace: self.namespace }; return pluginUtil.loadNodesAtCommitHash( self.project, self.core, branchHash, self.logger, options ); } }) .then(function (result) { var didUpdate; if (result === false) { didUpdate = false; } else { self.currentHash = result.commitHash; self.rootNode = result.rootNode; self.activeNode = result.activeNode; self.activeSelection = result.activeSelection; self.META = result.META; didUpdate = true; } return didUpdate; }) .nodeify(callback); }; /** * Adds the commit to the results. N.B. if you're using your own save method - make sure to update * this.currentHash and this.branchName accordingly before adding the commit. * * @param {string} status - Status of the commit 'SYNCED', 'FORKED', 'CANCELED', null. */ PluginBase.prototype.addCommitToResult = function (status) { var newCommit = { commitHash: this.currentHash, branchName: this.branchName, status: status }; this.result.addCommit(newCommit); this.logger.debug('newCommit added', newCommit); }; /** * Checks if the activeNode has registered the plugin. * * @param {string} pluginId - Id of plugin * @returns {Error} - returns undefined if valid and an Error if not. */ PluginBase.prototype.isInvalidActiveNode = function (pluginId) { var validPlugins = this.core.getRegistry(this.activeNode, 'validPlugins') || ''; this.logger.debug('validPlugins for activeNode', validPlugins); if (validPlugins.split(' ').indexOf(pluginId) === -1) { return new Error('Plugin not registered as validPlugin for active node, validPlugins "' + validPlugins + '"'); } }; /** * Loads all the nodes in the subtree starting from node and returns a map from paths to nodes. * @param {module:Core~Node} [node=self.rootNode] - Optional node to preload nodes from, * by default all will be loaded. * @param {function} [callback] - the result callback * @param {null|Error} callback.err - status of the call * @param {object} callback.nodeMap - keys are paths and values are nodes * @return {external:Promise} If no callback is given, the result will be provided in a promise */ PluginBase.prototype.loadNodeMap = function (node, callback) { var self = this; return self.core.loadSubTree(node || self.rootNode) .then(function (nodeArr) { var nodes = {}, i; for (i = 0; i &lt; nodeArr.length; i += 1) { nodes[self.core.getPath(nodeArr[i])] = nodeArr[i]; } return nodes; }) .nodeify(callback); }; /** * Retrieves the identity of the current user of the opened project (the user who invoked the plugin). * @return {string} the userId */ PluginBase.prototype.getUserId = function () { return this.project.getUserId(); }; /** * Initializes and invokes the given plugin (at pluginId). * Things to note: * 1. If the invoked plugin calls save - it will not persist nor make a commit. The message will be recorded in * the InterPluginResult. * 2. Artifacts and files saved will be added to the blob-storage. Invoked plugins can expose the content by adding * it to itself - the instance will be available in the InterPluginResult. * * @param {string} pluginId - Id of plugin that should be invoked * @param {object} [context] - Optional context for the invoked plugin * @param {object} [context.namespace=this.namespace] - Namespace (relative this.namespace) * @param {module:Core~Node} [context.activeNode=this.activeNode] - Active node of invoked plugin * @param {Array&lt;module:Core~Node>} [context.activeSelection=this.activeSelection] - Active selection * of invoked plugin * @param {object} [context.pluginConfig] - Specific configuration parameters that should be used for the * invocation. * If not provided will first check if the currentConfig of this plugin contains this plugin as dependency within * the array this._currentConfig._dependencies. Finally it will fall back to the default config of the plugin. * @param {function} [callback] - the result callback * @param {null|Error} callback.err - status of the call * @param {InterPluginResult} callback.result - result from the invoked plugin * @return {external:Promise} If no callback is given, the result will be provided in a promise */ PluginBase.prototype.invokePlugin = function (pluginId, context, callback) { var self = this, deferred = Q.defer(), pluginInstance; context = context || {}; function getPluginClass() { var requireDeferred = Q.defer(), pluginPath = 'plugin/' + pluginId + '/' + pluginId + '/' + pluginId; requirejs([pluginPath], function (PluginClass) { self.logger.debug('requirejs plugin from path: ' + pluginPath); requireDeferred.resolve(PluginClass); }, function (err) { requireDeferred.reject(err); } ); return requireDeferred.promise; } getPluginClass() .then(function (PluginClass) { var pluginConfig, cfgKey; pluginInstance = new PluginClass(); pluginInstance.initialize(self.logger.fork(pluginId), self.blobClient.getNewInstance(), self.gmeConfig); pluginInstance.result = new InterPluginResult(pluginInstance); ['core', 'project', 'branch', 'projectName', 'projectId', 'branchName', 'branchHash', 'commitHash', 'currentHash', 'rootNode', 'notificationHandlers'] .forEach(function (sameField) { pluginInstance[sameField] = self[sameField]; }); pluginInstance.activeNode = context.activeNode || self.activeNode; pluginInstance.activeSelection = context.activeSelection || self.activeSelection; if (context.namespace) { pluginInstance.namespace = self.namespace === '' ? context.namespace : self.namespace + '.' + context.namespace; pluginInstance.META = pluginUtil .getMetaNodesMap(pluginInstance.core, pluginInstance.rootNode, pluginInstance.logger, pluginInstance.namespace); } else { pluginInstance.namespace = self.namespace; pluginInstance.META = self.META; } // Plugin config // 1. Get the default config for the plugin instance. pluginConfig = pluginInstance.getDefaultConfig(); // 2. If the current-plugin has a sub-config for this plugin (from the default UI) - add those. if (Object.hasOwn(self._currentConfig, '_dependencies') &amp;&amp; Object.hasOwn(self._currentConfig._dependencies, pluginId) &amp;&amp; Object.hasOwn(self._currentConfig._dependencies[pluginId], 'pluginConfig')) { for (cfgKey in self._currentConfig._dependencies[pluginId].pluginConfig) { pluginConfig[cfgKey] = self._currentConfig._dependencies[pluginId].pluginConfig[cfgKey]; } } // 3. Finally use the specific config passed here. if (context.pluginConfig) { for (cfgKey in context.pluginConfig) { pluginConfig[cfgKey] = context.pluginConfig[cfgKey]; } } pluginInstance.setCurrentConfig(pluginConfig); pluginInstance.isConfigured = true; pluginInstance.callDepth = self.callDepth + 1; self.invokedPlugins.push(pluginInstance); return Q.ninvoke(pluginInstance, 'main'); }) .then(function (res) { var i; for (i = 0; i &lt; self.invokedPlugins.length; i += 1) { if (pluginInstance === self.invokedPlugins[i]) { self.invokedPlugins.splice(i, 1); } } deferred.resolve(res || pluginInstance.result); }) .catch(function (err) { deferred.reject(err); }); return deferred.promise.nodeify(callback); }; /** * Adds a file to the blob storage and adds it to the plugin-result. * @param {string} name - file name. * @param {string|Buffer|ArrayBuffer} data - file content. * @param {function} [callback] - if provided no promise will be returned. * @param {null|Error} callback.err - status of the call * @param {string} callback.metadataHash - the "id" of the uploaded file * @return {external:Promise} If no callback is given, the result will be provided in a promise */ PluginBase.prototype.addFile = function (name, content, callback) { var self = this; return this.blobClient.putFile(name, content) .then(function (metadataHash) { self.result.addArtifact(metadataHash); return metadataHash; }) .nodeify(callback); }; /** * Adds multiple files to the blob storage and bundles them as an artifact of which the hash is added to the * plugin-result. * @param {string} name - name of the file bundle. * @param {object.&lt;string, string|Buffer|ArrayBuffer>} files - Keys are file names and values the content. * @param {function} [callback] - if provided no promise will be returned. * @param {null|Error} callback.err - status of the call. * @param {string} callback.metadataHash - the "id" of the uploaded artifact. * @return {external:Promise} If no callback is given, the result will be provided in a promise. */ PluginBase.prototype.addArtifact = function (name, files, callback) { var self = this, artifact = this.blobClient.createArtifact(name); return artifact.addFilesAsSoftLinks(files) .then(function () { return artifact.save(); }) .then(function (metadataHash) { self.result.addArtifact(metadataHash); return metadataHash; }) .nodeify(callback); }; /** * Retrieves the file from blob storage. * @param {string} metadataHash - the "id" of the file to retrieve. * @param {null|Error} callback.err - status of the call. * @param {string} callback.content - the file content. * @return {external:Promise} If no callback is given, the result will be provided in a promise. */ PluginBase.prototype.getFile = function (metadataHash, callback) { return this.blobClient.getObjectAsString(metadataHash).nodeify(callback); }; /** * Retrieves the file from blob storage in binary format. * @param {string} metadataHash - the "id" of the file to retrieve. * @param {string} [subpath] - optional file-like path to sub-object if complex blob * @param {null|Error} callback.err - status of the call. * @param {Buffer} callback.content - the file content. * @return {external:Promise} If no callback is given, the result will be provided in a promise. */ PluginBase.prototype.getBinFile = function (metadataHash, subpath, callback) { return this.blobClient.getObject(metadataHash, callback || null, subpath || null); }; /** * Retrieves all the files in the artifact from the blob storage. * @param {string} metadataHash - the "id" of the artifact to retrieve. * @param {null|Error} callback.err - status of the call. * @param {object.&lt;string, string>} callback.files - Keys are file names, and values the content. * @return {external:Promise} If no callback is given, the result will be provided in a promise. */ PluginBase.prototype.getArtifact = function (metadataHash, callback) { var self = this, result = {}; return this.blobClient.getMetadata(metadataHash) .then(function (metadata) { var promises = Object.keys(metadata.content) .map(function (fileName) { return self.blobClient.getObjectAsString(metadata.content[fileName].content) .then(function (content) { result[fileName] = content; }); }); return Q.all(promises); }) .then(function () { return result; }) .nodeify(callback); }; //&lt;/editor-fold> //&lt;editor-fold desc="Methods that are used by the Plugin Manager. Derived classes should not use these methods"> /** * Initializes the plugin with objects that can be reused within the same plugin instance. * * @param {GmeLogger} logger - logging capability to console (or file) based on PluginManager configuration * @param {BlobClient} blobClient - virtual file system where files can be generated then saved as a zip file. * @param {GmeConfig} gmeConfig - global configuration for webGME. */ PluginBase.prototype.initialize = function (logger, blobClient, gmeConfig) { if (logger) { this.logger = logger; } else { this.logger = console; } if (!gmeConfig) { // TODO: Remove this check at some point throw new Error('gmeConfig was not provided to Plugin.initialize!'); } this.blobClient = blobClient; this.gmeConfig = gmeConfig; this._currentConfig = null; // initialize default configuration this.setCurrentConfig(this.getDefaultConfig()); this.isConfigured = false; }; /** * Configures this instance of the plugin for a specific execution. This function is called before the main by * the PluginManager. * Initializes the result with a new object. * * @param {object} config - specific context: project, branch, core, active object and active selection. */ PluginBase.prototype.configure = function (config) { var self = this; this.core = config.core; this.project = config.project; this.branch = config.branch; // This is only for client side. this.projectName = config.projectName; this.projectId = config.projectId; this.branchName = config.branchName; this.branchHash = config.branchName ? config.commitHash : null; this.commitHash = config.commitHash; this.currentHash = config.commitHash; this.rootNode = config.rootNode; this.activeNode = config.activeNode; this.activeSelection = config.activeSelection; this.namespace = config.namespace || ''; this.META = this.META = config.META; this.result = new PluginResult(); this.result.setProjectId(this.projectId); this.addCommitToResult(STORAGE_CONSTANTS.SYNCED); this.isConfigured = true; setTimeout(function () { self.sendNotification({ toBranch: false, message: 'Plugin initialized.', progress: 0, type: STORAGE_CONSTANTS.PLUGIN_NOTIFICATION_TYPE.INITIATED }); }, 0); }; /** * Gets the default configuration based on the configuration structure for this plugin. * * @returns {PluginConfig} */ PluginBase.prototype.getDefaultConfig = function () { var configStructure = this.getConfigStructure(), defaultConfig = new PluginConfig(); for (var i = 0; i &lt; configStructure.length; i += 1) { defaultConfig[configStructure[i].name] = configStructure[i].value; } return defaultConfig; }; /** * Sets the current configuration of the plugin. * * @param {PluginConfig} newConfig - this is the actual configuration and NOT the configuration structure. */ PluginBase.prototype.setCurrentConfig = function (newConfig) { this._currentConfig = newConfig; }; /** * Gets the metadata for the plugin. * * @returns {PluginMetaData} */ PluginBase.prototype.getMetadata = function () { return this.pluginMetadata; }; /** * Gets the ids of the directly defined dependencies of the plugin * * @returns {string[]} */ PluginBase.prototype.getPluginDependencies = function () { if (this.pluginMetadata &amp;&amp; this.pluginMetadata.dependencies) { return this.pluginMetadata.dependencies .map(function (data) { return data.id; }); } else { return []; } }; /** * Aborts the execution of a plugin. */ PluginBase.prototype.onAbort = function () { throw new Error('onAbort function is not implemented!'); }; /** * Can send a message to the plugin. * @param {string} messageType - string identifier of the message. * @param {object} content - object that holds arbitrary content of the message. */ PluginBase.prototype.onMessage = function (messageType, content) { if (this.logger) { this.logger.warn('Message [' + messageType + '] was received but no message handling is implemented!'); this.logger.debug('Unhandled [' + messageType + '] with content:', content); } }; //&lt;/editor-fold> return PluginBase; })); </code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="Server_GMEAuth.html">Server:GMEAuth</a></li><li><a href="Server_SafeStorage.html">Server:SafeStorage</a></li><li><a href="Server_UserProject.html">Server:UserProject</a></li><li><a href="module-Core.html">Core</a></li><li><a href="module-Storage.html">Storage</a></li><li><a href="module-crosscuts.html">crosscuts</a></li><li><a href="module-serialization.html">serialization</a></li></ul><h3>Externals</h3><ul><li><a href="external-Promise.html">Promise</a></li></ul><h3>Classes</h3><ul><li><a href="AddOnBase.html">AddOnBase</a></li><li><a href="AddOnUpdateResult.html">AddOnUpdateResult</a></li><li><a href="Artifact.html">Artifact</a></li><li><a href="BlobClient.html">BlobClient</a></li><li><a href="BlobMetadata.html">BlobMetadata</a></li><li><a href="BlobRunPluginClient.html">BlobRunPluginClient</a></li><li><a href="Client.html">Client</a></li><li><a href="Core.html">Core</a></li><li><a href="ExecutorClient.html">ExecutorClient</a></li><li><a href="GMENode.html">GMENode</a></li><li><a href="GmeLogger.html">GmeLogger</a></li><li><a href="InterPluginResult.html">InterPluginResult</a></li><li><a href="JobInfo.html">JobInfo</a></li><li><a href="OutputInfo.html">OutputInfo</a></li><li><a href="PluginBase.html">PluginBase</a></li><li><a href="PluginConfig.html">PluginConfig</a></li><li><a href="PluginMessage.html">PluginMessage</a></li><li><a href="PluginNodeDescription.html">PluginNodeDescription</a></li><li><a href="PluginResult.html">PluginResult</a></li><li><a href="Project.html">Project</a></li><li><a href="ProjectInterface.html">ProjectInterface</a></li><li><a href="Server_GMEAuth-GMEAuth.html">GMEAuth</a></li><li><a href="Server_SafeStorage-SafeStorage.html">SafeStorage</a></li><li><a href="Server_UserProject-UserProject.html">UserProject</a></li><li><a href="WebsocketRouter.html">WebsocketRouter</a></li><li><a href="WebsocketRouterUser.html">WebsocketRouterUser</a></li></ul><h3>Events</h3><ul><li><a href="Client.html#event:BRANCH_CHANGED">BRANCH_CHANGED</a></li><li><a href="Client.html#event:BRANCH_CLOSED">BRANCH_CLOSED</a></li><li><a href="Client.html#event:BRANCH_OPENED">BRANCH_OPENED</a></li><li><a href="Client.html#event:BRANCH_STATUS_CHANGED">BRANCH_STATUS_CHANGED</a></li><li><a href="Client.html#event:CONNECTED_USERS_CHANGED">CONNECTED_USERS_CHANGED</a></li><li><a href="Client.html#event:NETWORK_STATUS_CHANGED">NETWORK_STATUS_CHANGED</a></li><li><a href="Client.html#event:NOTIFICATION">NOTIFICATION</a></li><li><a href="Client.html#event:PLUGIN_FINISHED">PLUGIN_FINISHED</a></li><li><a href="Client.html#event:PLUGIN_INITIATED">PLUGIN_INITIATED</a></li><li><a href="Client.html#event:PLUGIN_NOTIFICATION">PLUGIN_NOTIFICATION</a></li><li><a href="Client.html#event:PROJECT_CLOSED">PROJECT_CLOSED</a></li><li><a href="Client.html#event:PROJECT_OPENED">PROJECT_OPENED</a></li></ul><h3><a href="global.html">Global</a></h3> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.2</a> on Fri Jun 21 2024 09:43:40 GMT-0400 (Eastern Daylight Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>