diff options
Diffstat (limited to 'dgbuilder/red/nodes/registry.js')
-rw-r--r-- | dgbuilder/red/nodes/registry.js | 693 |
1 files changed, 0 insertions, 693 deletions
diff --git a/dgbuilder/red/nodes/registry.js b/dgbuilder/red/nodes/registry.js deleted file mode 100644 index f2073aff..00000000 --- a/dgbuilder/red/nodes/registry.js +++ /dev/null @@ -1,693 +0,0 @@ -/** - * Copyright 2014 IBM Corp. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var util = require("util"); -var when = require("when"); -var whenNode = require('when/node'); -var fs = require("fs"); -var path = require("path"); -var crypto = require("crypto"); -var UglifyJS = require("uglify-js"); - -var events = require("../events"); - -var Node; -var settings; - -function filterNodeInfo(n) { - var r = { - id: n.id, - name: n.name, - types: n.types, - enabled: n.enabled - } - if (n.hasOwnProperty("loaded")) { - r.loaded = n.loaded; - } - if (n.hasOwnProperty("module")) { - r.module = n.module; - } - if (n.hasOwnProperty("err")) { - r.err = n.err.toString(); - } - return r; -} - -var registry = (function() { - var nodeConfigCache = null; - var nodeConfigs = {}; - var nodeList = []; - var nodeConstructors = {}; - var nodeTypeToId = {}; - var nodeModules = {}; - - function saveNodeList() { - var nodeList = {}; - - for (var i in nodeConfigs) { - if (nodeConfigs.hasOwnProperty(i)) { - var nodeConfig = nodeConfigs[i]; - var n = filterNodeInfo(nodeConfig); - n.file = nodeConfig.file; - delete n.loaded; - delete n.err; - delete n.file; - delete n.id; - nodeList[i] = n; - } - } - if (settings.available()) { - return settings.set("nodes",nodeList); - } else { - return when.reject("Settings unavailable"); - } - } - - return { - init: function() { - if (settings.available()) { - nodeConfigs = settings.get("nodes")||{}; - // Restore the node id property to individual entries - for (var id in nodeConfigs) { - if (nodeConfigs.hasOwnProperty(id)) { - nodeConfigs[id].id = id; - } - } - } else { - nodeConfigs = {}; - } - nodeModules = {}; - nodeTypeToId = {}; - nodeConstructors = {}; - nodeList = []; - nodeConfigCache = null; - }, - - addNodeSet: function(id,set) { - if (!set.err) { - set.types.forEach(function(t) { - nodeTypeToId[t] = id; - }); - } - - if (set.module) { - nodeModules[set.module] = nodeModules[set.module]||{nodes:[]}; - nodeModules[set.module].nodes.push(id); - } - - nodeConfigs[id] = set; - nodeList.push(id); - nodeConfigCache = null; - }, - removeNode: function(id) { - var config = nodeConfigs[id]; - if (!config) { - throw new Error("Unrecognised id: "+id); - } - delete nodeConfigs[id]; - var i = nodeList.indexOf(id); - if (i > -1) { - nodeList.splice(i,1); - } - config.types.forEach(function(t) { - delete nodeConstructors[t]; - delete nodeTypeToId[t]; - }); - config.enabled = false; - config.loaded = false; - nodeConfigCache = null; - return filterNodeInfo(config); - }, - removeModule: function(module) { - if (!settings.available()) { - throw new Error("Settings unavailable"); - } - var nodes = nodeModules[module]; - if (!nodes) { - throw new Error("Unrecognised module: "+module); - } - var infoList = []; - for (var i=0;i<nodes.nodes.length;i++) { - infoList.push(registry.removeNode(nodes.nodes[i])); - } - delete nodeModules[module]; - saveNodeList(); - return infoList; - }, - getNodeInfo: function(typeOrId) { - if (nodeTypeToId[typeOrId]) { - return filterNodeInfo(nodeConfigs[nodeTypeToId[typeOrId]]); - } else if (nodeConfigs[typeOrId]) { - return filterNodeInfo(nodeConfigs[typeOrId]); - } - return null; - }, - getNodeList: function() { - var list = []; - for (var id in nodeConfigs) { - if (nodeConfigs.hasOwnProperty(id)) { - list.push(filterNodeInfo(nodeConfigs[id])) - } - } - return list; - }, - registerNodeConstructor: function(type,constructor) { - if (nodeConstructors[type]) { - throw new Error(type+" already registered"); - } - //TODO: Ensure type is known - but doing so will break some tests - // that don't have a way to register a node template ahead - // of registering the constructor - util.inherits(constructor,Node); - nodeConstructors[type] = constructor; - events.emit("type-registered",type); - }, - - - /** - * Gets all of the node template configs - * @return all of the node templates in a single string - */ - getAllNodeConfigs: function() { - if (!nodeConfigCache) { - var result = ""; - var script = ""; - for (var i=0;i<nodeList.length;i++) { - var config = nodeConfigs[nodeList[i]]; - if (config.enabled && !config.err) { - result += config.config; - script += config.script; - } - } - if (script.length > 0) { - result += '<script type="text/javascript">'; - result += UglifyJS.minify(script, {fromString: true}).code; - result += '</script>'; - } - nodeConfigCache = result; - } - return nodeConfigCache; - }, - - getNodeConfig: function(id) { - var config = nodeConfigs[id]; - if (config) { - var result = config.config; - if (config.script) { - result += '<script type="text/javascript">'+config.script+'</script>'; - } - return result; - } else { - return null; - } - }, - - getNodeConstructor: function(type) { - var config = nodeConfigs[nodeTypeToId[type]]; - if (!config || (config.enabled && !config.err)) { - return nodeConstructors[type]; - } - return null; - }, - - clear: function() { - nodeConfigCache = null; - nodeConfigs = {}; - nodeList = []; - nodeConstructors = {}; - nodeTypeToId = {}; - }, - - getTypeId: function(type) { - return nodeTypeToId[type]; - }, - - getModuleInfo: function(type) { - return nodeModules[type]; - }, - - enableNodeSet: function(id) { - if (!settings.available()) { - throw new Error("Settings unavailable"); - } - var config = nodeConfigs[id]; - if (config) { - delete config.err; - config.enabled = true; - if (!config.loaded) { - // TODO: honour the promise this returns - loadNodeModule(config); - } - nodeConfigCache = null; - saveNodeList(); - } else { - throw new Error("Unrecognised id: "+id); - } - return filterNodeInfo(config); - }, - - disableNodeSet: function(id) { - if (!settings.available()) { - throw new Error("Settings unavailable"); - } - var config = nodeConfigs[id]; - if (config) { - // TODO: persist setting - config.enabled = false; - nodeConfigCache = null; - saveNodeList(); - } else { - throw new Error("Unrecognised id: "+id); - } - return filterNodeInfo(config); - }, - - saveNodeList: saveNodeList, - - cleanNodeList: function() { - var removed = false; - for (var id in nodeConfigs) { - if (nodeConfigs.hasOwnProperty(id)) { - if (nodeConfigs[id].module && !nodeModules[nodeConfigs[id].module]) { - registry.removeNode(id); - removed = true; - } - } - } - if (removed) { - saveNodeList(); - } - } - } -})(); - - - -function init(_settings) { - Node = require("./Node"); - settings = _settings; - registry.init(); -} - -/** - * Synchronously walks the directory looking for node files. - * Emits 'node-icon-dir' events for an icon dirs found - * @param dir the directory to search - * @return an array of fully-qualified paths to .js files - */ -function getNodeFiles(dir) { - var result = []; - var files = []; - try { - files = fs.readdirSync(dir); - } catch(err) { - return result; - } - files.sort(); - files.forEach(function(fn) { - var stats = fs.statSync(path.join(dir,fn)); - if (stats.isFile()) { - if (/\.js$/.test(fn)) { - var valid = true; - if (settings.nodesExcludes) { - for (var i=0;i<settings.nodesExcludes.length;i++) { - if (settings.nodesExcludes[i] == fn) { - valid = false; - break; - } - } - } - valid = valid && fs.existsSync(path.join(dir,fn.replace(/\.js$/,".html"))) - - if (valid) { - result.push(path.join(dir,fn)); - } - } - } else if (stats.isDirectory()) { - // Ignore /.dirs/, /lib/ /node_modules/ - if (!/^(\..*|lib|icons|node_modules|test)$/.test(fn)) { - result = result.concat(getNodeFiles(path.join(dir,fn))); - } else if (fn === "icons") { - events.emit("node-icon-dir",path.join(dir,fn)); - } - } - }); - return result; -} - -/** - * Scans the node_modules path for nodes - * @param moduleName the name of the module to be found - * @return a list of node modules: {dir,package} - */ -function scanTreeForNodesModules(moduleName) { - var dir = __dirname+"/../../nodes"; - var results = []; - var up = path.resolve(path.join(dir,"..")); - while (up !== dir) { - var pm = path.join(dir,"node_modules"); - try { - var files = fs.readdirSync(pm); - for (var i=0;i<files.length;i++) { - var fn = files[i]; - if (!registry.getModuleInfo(fn)) { - if (!moduleName || fn == moduleName) { - var pkgfn = path.join(pm,fn,"package.json"); - try { - var pkg = require(pkgfn); - if (pkg['node-red']) { - var moduleDir = path.join(pm,fn); - results.push({dir:moduleDir,package:pkg}); - } - } catch(err) { - if (err.code != "MODULE_NOT_FOUND") { - // TODO: handle unexpected error - } - } - if (fn == moduleName) { - break; - } - } - } - } - } catch(err) { - } - - dir = up; - up = path.resolve(path.join(dir,"..")); - } - return results; -} - -/** - * Loads the nodes provided in an npm package. - * @param moduleDir the root directory of the package - * @param pkg the module's package.json object - */ -function loadNodesFromModule(moduleDir,pkg) { - var nodes = pkg['node-red'].nodes||{}; - var results = []; - var iconDirs = []; - for (var n in nodes) { - if (nodes.hasOwnProperty(n)) { - var file = path.join(moduleDir,nodes[n]); - try { - results.push(loadNodeConfig(file,pkg.name,n)); - } catch(err) { - } - var iconDir = path.join(moduleDir,path.dirname(nodes[n]),"icons"); - if (iconDirs.indexOf(iconDir) == -1) { - if (fs.existsSync(iconDir)) { - events.emit("node-icon-dir",iconDir); - iconDirs.push(iconDir); - } - } - } - } - return results; -} - - -/** - * Loads a node's configuration - * @param file the fully qualified path of the node's .js file - * @param name the name of the node - * @return the node object - * { - * id: a unqiue id for the node file - * name: the name of the node file, or label from the npm module - * file: the fully qualified path to the node's .js file - * template: the fully qualified path to the node's .html file - * config: the non-script parts of the node's .html file - * script: the script part of the node's .html file - * types: an array of node type names in this file - * } - */ -function loadNodeConfig(file,module,name) { - var id = crypto.createHash('sha1').update(file).digest("hex"); - if (module && name) { - var newid = crypto.createHash('sha1').update(module+":"+name).digest("hex"); - var existingInfo = registry.getNodeInfo(id); - if (existingInfo) { - // For a brief period, id for modules were calculated incorrectly. - // To prevent false-duplicates, this removes the old id entry - registry.removeNode(id); - registry.saveNodeList(); - } - id = newid; - - } - var info = registry.getNodeInfo(id); - - var isEnabled = true; - - if (info) { - if (info.hasOwnProperty("loaded")) { - throw new Error(file+" already loaded"); - } - isEnabled = info.enabled; - } - - var node = { - id: id, - file: file, - template: file.replace(/\.js$/,".html"), - enabled: isEnabled, - loaded:false - } - - if (module) { - node.name = module+":"+name; - node.module = module; - } else { - node.name = path.basename(file) - } - try { - var content = fs.readFileSync(node.template,'utf8'); - - var types = []; - - var regExp = /<script ([^>]*)data-template-name=['"]([^'"]*)['"]/gi; - var match = null; - - while((match = regExp.exec(content)) !== null) { - types.push(match[2]); - } - node.types = types; - node.config = content; - - // TODO: parse out the javascript portion of the template - node.script = ""; - - for (var i=0;i<node.types.length;i++) { - if (registry.getTypeId(node.types[i])) { - node.err = node.types[i]+" already registered"; - break; - } - } - } catch(err) { - node.types = []; - if (err.code === 'ENOENT') { - node.err = "Error: "+file+" does not exist"; - } else { - node.err = err.toString(); - } - } - registry.addNodeSet(id,node); - return node; -} - -/** - * Loads all palette nodes - * @param defaultNodesDir optional parameter, when set, it overrides the default - * location of nodeFiles - used by the tests - * @return a promise that resolves on completion of loading - */ -function load(defaultNodesDir,disableNodePathScan) { - return when.promise(function(resolve,reject) { - // Find all of the nodes to load - var nodeFiles; - if(defaultNodesDir) { - nodeFiles = getNodeFiles(path.resolve(defaultNodesDir)); - } else { - nodeFiles = getNodeFiles(__dirname+"/../../nodes"); - } - - if (settings.nodesDir) { - var dir = settings.nodesDir; - if (typeof settings.nodesDir == "string") { - dir = [dir]; - } - for (var i=0;i<dir.length;i++) { - nodeFiles = nodeFiles.concat(getNodeFiles(dir[i])); - } - } - var nodes = []; - nodeFiles.forEach(function(file) { - try { - nodes.push(loadNodeConfig(file)); - } catch(err) { - // - } - }); - - // TODO: disabling npm module loading if defaultNodesDir set - // This indicates a test is being run - don't want to pick up - // unexpected nodes. - // Urgh. - if (!disableNodePathScan) { - // Find all of the modules containing nodes - var moduleFiles = scanTreeForNodesModules(); - moduleFiles.forEach(function(moduleFile) { - nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package)); - }); - } - var promises = []; - nodes.forEach(function(node) { - if (!node.err) { - promises.push(loadNodeModule(node)); - } - }); - - //resolve([]); - when.settle(promises).then(function(results) { - // Trigger a load of the configs to get it precached - registry.getAllNodeConfigs(); - - if (settings.available()) { - resolve(registry.saveNodeList()); - } else { - resolve(); - } - }); - }); -} - -/** - * Loads the specified node into the runtime - * @param node a node info object - see loadNodeConfig - * @return a promise that resolves to an update node info object. The object - * has the following properties added: - * err: any error encountered whilst loading the node - * - */ -function loadNodeModule(node) { - var nodeDir = path.dirname(node.file); - var nodeFn = path.basename(node.file); - if (!node.enabled) { - return when.resolve(node); - } - try { - var loadPromise = null; - var r = require(node.file); - if (typeof r === "function") { - var promise = r(require('../red')); - if (promise != null && typeof promise.then === "function") { - loadPromise = promise.then(function() { - node.enabled = true; - node.loaded = true; - return node; - }).otherwise(function(err) { - node.err = err; - return node; - }); - } - } - if (loadPromise == null) { - node.enabled = true; - node.loaded = true; - loadPromise = when.resolve(node); - } - return loadPromise; - } catch(err) { - node.err = err; - return when.resolve(node); - } -} - -function loadNodeList(nodes) { - var promises = []; - nodes.forEach(function(node) { - if (!node.err) { - promises.push(loadNodeModule(node)); - } else { - promises.push(node); - } - }); - - return when.settle(promises).then(function(results) { - return registry.saveNodeList().then(function() { - var list = results.map(function(r) { - return filterNodeInfo(r.value); - }); - return list; - }); - }); -} - -function addNode(file) { - if (!settings.available()) { - throw new Error("Settings unavailable"); - } - var nodes = []; - try { - nodes.push(loadNodeConfig(file)); - } catch(err) { - return when.reject(err); - } - return loadNodeList(nodes); -} - -function addModule(module) { - if (!settings.available()) { - throw new Error("Settings unavailable"); - } - var nodes = []; - if (registry.getModuleInfo(module)) { - return when.reject(new Error("Module already loaded")); - } - var moduleFiles = scanTreeForNodesModules(module); - if (moduleFiles.length === 0) { - var err = new Error("Cannot find module '" + module + "'"); - err.code = 'MODULE_NOT_FOUND'; - return when.reject(err); - } - moduleFiles.forEach(function(moduleFile) { - nodes = nodes.concat(loadNodesFromModule(moduleFile.dir,moduleFile.package)); - }); - return loadNodeList(nodes); -} - -module.exports = { - init:init, - load:load, - clear: registry.clear, - registerType: registry.registerNodeConstructor, - get: registry.getNodeConstructor, - getNodeInfo: registry.getNodeInfo, - getNodeModuleInfo: registry.getModuleInfo, - getNodeList: registry.getNodeList, - getNodeConfigs: registry.getAllNodeConfigs, - getNodeConfig: registry.getNodeConfig, - addNode: addNode, - removeNode: registry.removeNode, - enableNode: registry.enableNodeSet, - disableNode: registry.disableNodeSet, - - addModule: addModule, - removeModule: registry.removeModule, - cleanNodeList: registry.cleanNodeList -} |