From e66b3810702d6f9dc99ae56068ae42a07f2cbeab Mon Sep 17 00:00:00 2001 From: Hiroki Uchikawa Date: Fri, 16 Mar 2018 05:52:17 +0900 Subject: [PATCH] add external context files --- red/runtime/nodes/context/external.js | 133 +++++++++++ red/runtime/nodes/context/localfilesystem.js | 81 +++++++ .../runtime/nodes/context/external_spec.js | 222 ++++++++++++++++++ .../nodes/context/localfilesystem_spec.js | 0 4 files changed, 436 insertions(+) create mode 100644 red/runtime/nodes/context/external.js create mode 100644 red/runtime/nodes/context/localfilesystem.js create mode 100644 test/red/runtime/nodes/context/external_spec.js create mode 100644 test/red/runtime/nodes/context/localfilesystem_spec.js diff --git a/red/runtime/nodes/context/external.js b/red/runtime/nodes/context/external.js new file mode 100644 index 000000000..d969fc53d --- /dev/null +++ b/red/runtime/nodes/context/external.js @@ -0,0 +1,133 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * 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 when = require("when"); +var log = require("../../log"); + +var re = /^(\$.*?)\.(.+)|^(\$.*)/; +var externalContexts; + +function parseKey(key){ + var keys = null; + var temp = re.exec(key); + if(temp){ + keys = []; + if(temp[3]){ + keys[0] = temp[3]; + keys[1] = null; + } else { + keys[0] = temp[1]; + keys[1] = temp[2]; + } + keys[0] = keys[0] === "$" ? "default" : keys[0].substring(1); + } + return keys; +} + +function hasContextStorage(plugin) { + return externalContexts.hasOwnProperty(plugin); +} + +var contextModuleInterface ={ + init: function (settings) { + var plugins = settings.contextStorage; + externalContexts = {}; + if (plugins) { + for(var pluginName in plugins){ + var plugin; + if(plugins[pluginName].hasOwnProperty("module") && plugins[pluginName].hasOwnProperty("config")) { + if(typeof plugins[pluginName].module === "string") { + try{ + plugin = require(plugins[pluginName].module); + }catch(err){ + log.error(err); + continue; + } + } else { + plugin = plugins[pluginName].module; + } + plugin.init(plugins[pluginName].config); + externalContexts[pluginName] = plugin; + } + } + } + }, + get: function(key, flowId) { + var result = parseKey(key); + if(!result){ + throw new Error("Invalid key"); + } + if(hasContextStorage(result[0])){ + return externalContexts[result[0]].get(result[1], flowId); + }else if(hasContextStorage("default")) { + // log.warn(result[1] " is got from default context storage"); + return externalContexts["default"].get(result[1], flowId); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + + }, + set: function(key, value, flowId) { + var result = parseKey(key); + if(!result){ + throw new Error("Invalid key"); + } + if(hasContextStorage(result[0])){ + externalContexts[result[0]].set(result[1], value, flowId); + }else if(hasContextStorage("default")) { + // log.warn(result[1] " is set to default context storage"); + externalContexts["default"].set(result[1], value, flowId); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + }, + keys: function(key, flowId) { + var result = parseKey(key); + if(!result){ + throw new Error("Invalid key"); + } + if(hasContextStorage(result[0])){ + return externalContexts[result[0]].keys(flowId); + }else if(hasContextStorage("default")) { + // log.warn("keys are got from default context storage"); + externalContexts["default"].keys(flowId); + }else{ + throw new Error(result[0] + " is not defined in setting.js"); + } + }, + // run: function(command,key,value,flowId) { + // //todo: run custom method in plugin + // }, + // close: function(){ + // //todo: close connections, streams, etc... + // }, + canUse: function(key){ + var result = parseKey(key); + if(!result){ + return false; + }else{ + if(hasContextStorage(result[0]) || hasContextStorage("default")){ + return true; + }else{ + return false; + } + } + }, + hasContextStorage: hasContextStorage, + parseKey: parseKey +}; + +module.exports = contextModuleInterface; \ No newline at end of file diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js new file mode 100644 index 000000000..733c619d1 --- /dev/null +++ b/red/runtime/nodes/context/localfilesystem.js @@ -0,0 +1,81 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * 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 storage = require('node-persist'); +var fs = require('fs-extra'); +var fspath = require("path"); + +var configs; +var storagePath; +var fileStorages; + +function createStorage(path) { + var fileStorage = storage.create({dir: fspath.join(storagePath,path)}); + fileStorage.initSync(); + fileStorages[path] = fileStorage; +} + +var localfilesystem = { + init: function(_configs) { + configs = _configs; + fileStorages = {}; + if (!configs.dir) { + try { + fs.statSync(fspath.join(process.env.NODE_RED_HOME,".config.json")); + storagePath = fspath.join(process.env.NODE_RED_HOME,"contexts"); + } catch(err) { + try { + // Consider compatibility for older versions + if (process.env.HOMEPATH) { + fs.statSync(fspath.join(process.env.HOMEPATH,".node-red",".config.json")); + storagePath = fspath.join(process.env.HOMEPATH,".node-red","contexts"); + } + } catch(err) { + } + if (!configs.dir) { + storagePath = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red","contexts"); + } + } + }else{ + storagePath = configs.dir; + } + + }, + stop: function() { + return when.resolve(); + }, + get: function (key, flowId) { + if(!fileStorages[flowId]){ + createStorage(flowId); + } + return fileStorages[flowId].getItemSync(key); + }, + + set: function (key, value, flowId) { + if(!fileStorages[flowId]){ + createStorage(flowId); + } + fileStorages[flowId].setItemSync(key, value); + }, + keys: function (flowId) { + if(!fileStorages[flowId]){ + createStorage(flowId); + } + return fileStorages[flowId].keys(); + } +}; + +module.exports = localfilesystem; diff --git a/test/red/runtime/nodes/context/external_spec.js b/test/red/runtime/nodes/context/external_spec.js new file mode 100644 index 000000000..726d1c666 --- /dev/null +++ b/test/red/runtime/nodes/context/external_spec.js @@ -0,0 +1,222 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * 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 should = require("should"); +var sinon = require('sinon'); +var external = require("../../../../../red/runtime/nodes/context/external"); + +describe("external", function() { + var stubModule = { + module: { + init: function(){}, + get: function(){}, + set: function(){}, + keys: function(){}, + run: function(){}, + close: function(){} + }, + config: {} + }; + describe('#init()', function() { + it('should load bundle module as default', function() { + external.init({ + contextStorage:{ + default:{ + module: "./localfilesystem", + config:{} + } + }}); + external.hasContextStorage("default").should.be.true(); + }); + + it('should load bundle module as localfile', function() { + external.init({ + contextStorage:{ + localfile:{ + module: "./localfilesystem", + config:{} + } + }}); + external.hasContextStorage("localfile").should.be.true(); + }); + + it('should not load non-existent module', function() { + external.init({ + contextStorage:{ + default:{ + module: "non-existent-module", + config:{} + } + + }}) + external.hasContextStorage("default").should.be.false(); + }); + + it('should load multiple modules', function() { + external.init({ + contextStorage:{ + default:{ + module: "./localfilesystem", + config:{} + }, + test:{ + module: { + init: function() { + return true; + } + }, + config:{} + } + } + }); + external.hasContextStorage("default").should.be.true(); + external.hasContextStorage("test").should.be.true(); + }); + + it('should load multiple modules without non-existent module', function() { + external.init({ + contextStorage:{ + nonexist:{ + module: "non-existent-module", + config:{} + }, + default:{ + module: "./localfilesystem", + config:{} + }, + test:{ + module: { + init: function() { + return true; + } + }, + config:{} + } + } + }); + external.hasContextStorage("nonexist").should.be.false(); + external.hasContextStorage("default").should.be.true(); + external.hasContextStorage("test").should.be.true(); + }); + }); + + // describe('#get()', function() { + // }); + + // describe('#set()', function() { + // }); + + // describe('#keys()', function() { + // }); + + // describe('#hasContextStorage()', function() { + // }); + + describe('#canUse()', function() { + it('should return true if specified module is loaded', function() { + external.init({ + contextStorage:{ + localfilesystem:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("$localfilesystem").should.be.true(); + external.canUse("$localfilesystem.foo").should.be.true(); + }); + it('should return false if specified module is not loaded', function() { + external.init({ + contextStorage:{ + localfilesystem:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("$file").should.be.false(); + external.canUse("$file.foo").should.be.false(); + }); + it('should return true if specified module is not loaded but default module is loaded', function() { + external.init({ + contextStorage:{ + default:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("$file").should.be.true(); + external.canUse("$file.foo").should.be.true(); + }); + it('should return false if argument does not contain module name', function() { + external.init({ + contextStorage:{ + default:{ + module: {name:"test",init: function(){return true}}, + config: {} + } + } + }); + external.canUse("file").should.be.false(); + external.canUse("file.foo").should.be.false(); + }); + }); + + describe('#parseKey()', function() { + function returnModuleAndKey(input, expectedModule, expectedKey) { + var result = external.parseKey(input); + result[0].should.eql(expectedModule); + result[1].should.eql(expectedKey); + }; + + function returnModule(input, expectedModule) { + var result = external.parseKey(input); + result[0].should.eql(expectedModule); + should(result[1]).be.null(); + }; + + it('should retrun module and key', function() { + returnModuleAndKey("$test.aaa","test","aaa"); + returnModuleAndKey("$test.aaa.bbb","test","aaa.bbb"); + returnModuleAndKey("$1.234","1","234"); + returnModuleAndKey("$$test.foo","$test","foo"); + returnModuleAndKey("$test.$foo","test","$foo"); + returnModuleAndKey("$test.$foo.$bar","test","$foo.$bar"); + returnModuleAndKey("$test..foo","test",".foo"); + returnModuleAndKey("$test..","test","."); + }); + + it('should retrun only module', function() { + returnModule("$test","test",null); + returnModule("$1","1",null); + returnModule("$$test","$test",null); + returnModule("$test.","test.",null); + }); + + it('should retrun module as default', function() { + returnModuleAndKey("$default.foo","default","foo"); + returnModuleAndKey("$.foo","default","foo"); + returnModule("$default","default"); + returnModule("$","default"); + }); + + it('should retrun null', function() { + should(external.parseKey("test.aaa")).be.null(); + should(external.parseKey("test")).be.null(); + should(external.parseKey(null)).be.null(); + }); + }); +}); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js new file mode 100644 index 000000000..e69de29bb