diff --git a/red/runtime/nodes/Node.js b/red/runtime/nodes/Node.js index b8cfa6532..308d30125 100644 --- a/red/runtime/nodes/Node.js +++ b/red/runtime/nodes/Node.js @@ -105,12 +105,12 @@ Node.prototype.close = function(removed) { if (promises.length > 0) { return when.settle(promises).then(function() { if (this._context) { - context.delete(this._alias||this.id,this.z); + return context.delete(this._alias||this.id,this.z); } }); } else { if (this._context) { - context.delete(this._alias||this.id,this.z); + return context.delete(this._alias||this.id,this.z); } return; } diff --git a/red/runtime/nodes/context/index.js b/red/runtime/nodes/context/index.js index 80fe2c774..4a3f6c3d5 100644 --- a/red/runtime/nodes/context/index.js +++ b/red/runtime/nodes/context/index.js @@ -70,6 +70,11 @@ function load() { return when.reject(new Error(log._("context.error-module-not-defined", {storage:pluginName}))); } } + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].open()); + } + } if(isAlias){ if(externalContexts.hasOwnProperty(plugins["default"])){ externalContexts["default"] = externalContexts[plugins["default"]]; @@ -77,11 +82,6 @@ function load() { return when.reject(new Error(log._("context.error-invalid-default-module", {storage:plugins["default"]}))); } } - for(var plugin in externalContexts){ - if(externalContexts.hasOwnProperty(plugin)){ - promises.push(externalContexts[plugin].open()); - } - } return when.all(promises); } else { noContextStorage = true; @@ -191,31 +191,35 @@ function getContext(localId,flowId) { } function deleteContext(id,flowId) { - var contextId = id; - if (flowId) { - contextId = id+":"+flowId; + if(noContextStorage){ + var promises = []; + var contextId = id; + if (flowId) { + contextId = id+":"+flowId; + } + delete contexts[contextId]; + return externalContexts["_"].delete(contextId); + }else{ + return when.resolve(); } - for(var plugin in externalContexts){ - externalContexts[plugin].delete(contextId); - } - delete contexts[contextId]; } function clean(flowConfig) { - var activeIds = {}; - var contextId; - var node; + var promises = []; + for(var plugin in externalContexts){ + if(externalContexts.hasOwnProperty(plugin)){ + promises.push(externalContexts[plugin].clean(Object.keys(flowConfig.allNodes))); + } + } for (var id in contexts) { if (contexts.hasOwnProperty(id)) { var idParts = id.split(":"); if (!flowConfig.allNodes.hasOwnProperty(idParts[0])) { - for(var plugin in externalContexts){ - externalContexts[plugin].delete(id); - } delete contexts[id]; } } } + return when.all(promises); } function close() { diff --git a/red/runtime/nodes/context/localfilesystem.js b/red/runtime/nodes/context/localfilesystem.js index ee693dfd5..2cffe7d96 100644 --- a/red/runtime/nodes/context/localfilesystem.js +++ b/red/runtime/nodes/context/localfilesystem.js @@ -78,7 +78,7 @@ LocalFileSystem.prototype.close = function(){ LocalFileSystem.prototype.get = function (scope, key) { if(!this.storages[scope]){ - return undefined; + this.storages[scope] = createStorage(this.storageBaseDir ,scope); } try{ this.storages[scope].reload(); @@ -105,28 +105,38 @@ LocalFileSystem.prototype.set = function(scope, key, value) { LocalFileSystem.prototype.keys = function(scope) { if(!this.storages[scope]){ - return []; + this.storages[scope] = createStorage(this.storageBaseDir ,scope); } return Object.keys(this.storages[scope].getData("/")); } LocalFileSystem.prototype.delete = function(scope){ + var self = this; if(this.storages[scope]){ + var promise; this.storages[scope].delete("/"); - if(scope.indexOf(":") === -1){ - fs.removeSync(path.dirname(this.storages[scope].filename)); - }else{ - try{ - fs.statSync(this.storages[scope].filename); - fs.unlinkSync(this.storages[scope].filename); - }catch(err){ - console.log("deleted"); - } - } - delete this.storages[scope]; + return fs.remove(this.storages[scope].filename).then(function(){ + delete self.storages[scope]; + return when.resolve(); + }); + }else{ + return when.resolve(); } } +LocalFileSystem.prototype.clean = function(activeNodes){ + var self = this; + return fs.readdir(self.storageBaseDir).then(function(dirs){ + return when.all(dirs.reduce(function(result, item){ + if(item !== "global" && !activeNodes.includes(item)){ + result.push(fs.remove(path.join(self.storageBaseDir,item))); + delete self.storages[item]; + } + return result; + },[])); + }); + } + module.exports = function(config){ return new LocalFileSystem(config); }; diff --git a/red/runtime/nodes/context/memory.js b/red/runtime/nodes/context/memory.js index 278773ca6..ba86d086d 100644 --- a/red/runtime/nodes/context/memory.js +++ b/red/runtime/nodes/context/memory.js @@ -58,9 +58,21 @@ Memory.prototype.keys = function(scope){ Memory.prototype.delete = function(scope){ delete this.data[scope]; - + return Promise.resolve(); }; +Memory.prototype.clean = function(activeNodes){ + for(var id in this.data){ + if(this.data.hasOwnProperty(id) && id !== "global"){ + var idParts = id.split(":"); + if(!activeNodes.includes(idParts[0])){ + delete this.data[id]; + } + } + } + return Promise.resolve(); +} + Memory.prototype.setGlobalContext= function(seed){ this.data["global"] = seed; }; diff --git a/red/runtime/nodes/flows/index.js b/red/runtime/nodes/flows/index.js index 1b2a87ac0..0f12e4746 100644 --- a/red/runtime/nodes/flows/index.js +++ b/red/runtime/nodes/flows/index.js @@ -160,11 +160,12 @@ function setFlows(_config,type,muteLog,forceStart) { activeFlowConfig = newFlowConfig; if (forceStart || started) { return stop(type,diff,muteLog).then(function() { - context.clean(activeFlowConfig); - start(type,diff,muteLog).then(function() { - events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + return context.clean(activeFlowConfig).then(function() { + start(type,diff,muteLog).then(function() { + events.emit("runtime-event",{id:"runtime-deploy",payload:{revision:flowRevision},retain: true}); + }); + return flowRevision; }); - return flowRevision; }).catch(function(err) { }) } else { diff --git a/test/red/runtime/nodes/context/index_spec.js b/test/red/runtime/nodes/context/index_spec.js index 0a4ec3174..fa048aa31 100644 --- a/test/red/runtime/nodes/context/index_spec.js +++ b/test/red/runtime/nodes/context/index_spec.js @@ -118,9 +118,10 @@ describe('context', function() { context.set("foo","abc"); context.get("foo").should.eql("abc"); - Context.delete("1","flowA"); - context = Context.get("1","flowA"); - should.not.exist(context.get("foo")); + return Context.delete("1","flowA").then(function(){ + context = Context.get("1","flowA"); + should.not.exist(context.get("foo")); + }); }); it('enumerates context keys', function() { @@ -153,12 +154,75 @@ describe('context', function() { }); describe('external context storage',function() { - describe('load modules',function(){ - afterEach(function() { - Context.clean({allNodes:{}}); + var sandbox = sinon.sandbox.create(); + var stubGet = sandbox.stub(); + var stubSet = sandbox.stub(); + var stubKeys = sandbox.stub(); + var stubDelete = sandbox.stub().returns(Promise.resolve()); + var stubClean = sandbox.stub().returns(Promise.resolve()); + var stubOpen = sandbox.stub().returns(Promise.resolve()); + var stubClose = sandbox.stub().returns(Promise.resolve()); + var stubGet2 = sandbox.stub(); + var stubSet2 = sandbox.stub(); + var stubKeys2 = sandbox.stub(); + var stubDelete2 = sandbox.stub().returns(Promise.resolve()); + var stubClean2 = sandbox.stub().returns(Promise.resolve()); + var stubOpen2 = sandbox.stub().returns(Promise.resolve()); + var stubClose2 = sandbox.stub().returns(Promise.resolve()); + var testPlugin = function(config){ + function Test(){} + Test.prototype.get = stubGet; + Test.prototype.set = stubSet; + Test.prototype.keys = stubKeys; + Test.prototype.delete = stubDelete; + Test.prototype.clean = stubClean; + Test.prototype.open = stubOpen; + Test.prototype.close = stubClose; + return new Test(config); + }; + var testPlugin2 = function(config){ + function Test2(){} + Test2.prototype.get = stubGet2; + Test2.prototype.set = stubSet2; + Test2.prototype.keys = stubKeys2; + Test2.prototype.delete = stubDelete2; + Test2.prototype.clean = stubClean2; + Test2.prototype.open = stubOpen2; + Test2.prototype.close = stubClose2; + return new Test2(config); + }; + var contextStorage={ + test:{ + module: testPlugin, + config:{} + } + }; + var contextDefaultStorage={ + default: { + module: testPlugin2, + config:{} + }, + test:{ + module: testPlugin, + config:{} + } + }; + + afterEach(function() { + sandbox.reset(); + return Context.clean({allNodes:{}}).then(function(){ return Context.close(); }); - + }); + + describe('load modules',function(){ + it('should call open()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + stubOpen.called.should.be.true(); + stubOpen2.called.should.be.true(); + }); + }); it('should load memory module', function() { Context.init({contextStorage:{memory:{module:"memory"}}}); return Context.load(); @@ -185,6 +249,18 @@ describe('context', function() { context.get("#1.num").should.eql("num3"); }); }); + it('should ignore reserved storage name `_`', function() { + Context.init({contextStorage:{_:{module:testPlugin}}}); + return Context.load().then(function(){ + var context = Context.get("1","flow"); + context.set("#_.foo","bar"); + context.get("#_.foo"); + context.keys("#_"); + stubSet.called.should.be.false(); + stubGet.called.should.be.false(); + stubKeys.called.should.be.false(); + }); + }); it('should fail when using invalid default context', function(done) { Context.init({contextStorage:{default:"noexist"}}); Context.load().then(function(){ @@ -211,63 +287,19 @@ describe('context', function() { }); }); - describe('store data',function() { - var sandbox = sinon.sandbox.create(); - var stubGet = sandbox.stub(); - var stubSet = sandbox.stub(); - var stubKeys = sandbox.stub(); - var stubDelete = sandbox.stub(); - var stubOpen = sandbox.stub().returns(Promise.resolve()); - var stubClose = sandbox.stub().returns(Promise.resolve()); - var stubGet2 = sandbox.stub(); - var stubSet2 = sandbox.stub(); - var stubKeys2 = sandbox.stub(); - var stubDelete2 = sandbox.stub(); - var stubOpen2 = sandbox.stub().returns(Promise.resolve()); - var stubClose2 = sandbox.stub().returns(Promise.resolve()); - var testPlugin = function(config){ - function Test(){} - Test.prototype.get = stubGet; - Test.prototype.set = stubSet; - Test.prototype.keys = stubKeys; - Test.prototype.delete = stubDelete; - Test.prototype.open = stubOpen; - Test.prototype.close = stubClose; - return new Test(config); - }; - var testPlugin2 = function(config){ - function Test2(){} - Test2.prototype.get = stubGet2; - Test2.prototype.set = stubSet2; - Test2.prototype.keys = stubKeys2; - Test2.prototype.delete = stubDelete2; - Test2.prototype.open = stubOpen2; - Test2.prototype.close = stubClose2; - return new Test2(config); - }; - var contextStorage={ - test:{ - module: testPlugin, - config:{} - } - }; - var contextDefaultStorage={ - default: { - module: testPlugin2, - config:{} - }, - test:{ - module: testPlugin, - config:{} - } - }; - - afterEach(function() { - sandbox.reset(); - Context.clean({allNodes:{}}); - return Context.close(); + describe('close modules',function(){ + it('should call close()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + return Context.close().then(function(){ + stubClose.called.should.be.true(); + stubClose2.called.should.be.true(); + }); + }); }); - + }); + + describe('store context',function() { it('should store local property to external context storage',function() { Context.init({contextStorage:contextStorage}); return Context.load().then(function(){ @@ -385,6 +417,31 @@ describe('context', function() { }); }); + describe('delete context',function(){ + it('should not call delete()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + Context.get("flowA"); + return Context.delete("flowA").then(function(){ + stubDelete.called.should.be.false(); + stubDelete2.called.should.be.false(); + }); + }); + }); + }); + + describe('clean context',function(){ + it('should call clean()', function() { + Context.init({contextStorage:contextDefaultStorage}); + return Context.load().then(function(){ + return Context.clean({allNodes:{}}).then(function(){ + stubClean.calledWithExactly([]).should.be.true(); + stubClean2.calledWithExactly([]).should.be.true(); + }); + }); + }); + }); + describe('key name',function() { beforeEach(function() { Context.init({contextStorage:{memory:{module:"memory"}}}); diff --git a/test/red/runtime/nodes/context/localfilesystem_spec.js b/test/red/runtime/nodes/context/localfilesystem_spec.js index fae96bcd4..77a6c440d 100644 --- a/test/red/runtime/nodes/context/localfilesystem_spec.js +++ b/test/red/runtime/nodes/context/localfilesystem_spec.js @@ -31,8 +31,10 @@ describe('localfilesystem',function() { }); afterEach(function() { - return context.close().then(function(){ - return fs.remove(resourcesDir); + return context.clean([]).then(function(){ + return context.close().then(function(){ + return fs.remove(resourcesDir); + }); }); }); @@ -118,9 +120,39 @@ describe('localfilesystem',function() { context.get("nodeX","foo").should.eql("abc"); context.get("nodeY","foo").should.eql("abc"); - context.delete("nodeX"); + return context.delete("nodeX").then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.exist(context.get("nodeY","foo")); + }) + }); + }); + + describe('#clean',function() { + it('should clean unnecessary context',function() { should.not.exist(context.get("nodeX","foo")); - should.exist(context.get("nodeY","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean([]).then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); + }); + it('should not clean active context',function() { + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean(["nodeX"]).then(function(){ + should.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); }); }); }); \ No newline at end of file diff --git a/test/red/runtime/nodes/context/memory_spec.js b/test/red/runtime/nodes/context/memory_spec.js index 953529e38..8da35be19 100644 --- a/test/red/runtime/nodes/context/memory_spec.js +++ b/test/red/runtime/nodes/context/memory_spec.js @@ -22,6 +22,13 @@ describe('memory',function() { beforeEach(function() { context = Memory({}); + return context.open(); + }); + + afterEach(function() { + return context.clean([]).then(function(){ + return context.close(); + }); }); describe('#get/set',function() { @@ -120,9 +127,39 @@ describe('memory',function() { context.get("nodeX","foo").should.eql("abc"); context.get("nodeY","foo").should.eql("abc"); - context.delete("nodeX"); + return context.delete("nodeX").then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.exist(context.get("nodeY","foo")); + }); + }); + }); + + describe('#clean',function() { + it('should clean unnecessary context',function() { should.not.exist(context.get("nodeX","foo")); - should.exist(context.get("nodeY","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean([]).then(function(){ + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); + }); + it('should not clean active context',function() { + should.not.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + context.set("nodeX","foo","abc"); + context.set("nodeY","foo","abc"); + context.get("nodeX","foo").should.eql("abc"); + context.get("nodeY","foo").should.eql("abc"); + + return context.clean(["nodeX"]).then(function(){ + should.exist(context.get("nodeX","foo")); + should.not.exist(context.get("nodeY","foo")); + }); }); });