Merge branch 'master' into dev

This commit is contained in:
Nick O'Leary 2018-09-17 11:33:36 +01:00
commit 7406ab6017
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
14 changed files with 487 additions and 214 deletions

View File

@ -1,3 +1,18 @@
#### 0.19.4: Maintenance Release
- Fix race condition in non-cache lfs context Fixes #1888
- LocalFileSystem Context: Remove extra flush code
- Prevent race condition in caching mode of lfs context (#1889)
- Allow context store name to be provided in the key
- Switch node: only use promises when absolutely necessary
- Fix dbl-click handling on webkit-based browsers
- Ensure context.flow/global cannot be deleted or enumerated
- Handle context.get with multiple levels of unknown key Fixes #1883
- Fix global.get("foo.bar") for functionGlobalContext set values
- Fix node color bug (#1877)
- Merge pull request #1857 from cclauss/patch-1
- Define raw_input() in Python 3 & fix time.sleep()
#### 0.19.3: Maintenance Release #### 0.19.3: Maintenance Release
- Split node - fix complete to send msg for k/v object - Split node - fix complete to send msg for k/v object

View File

@ -826,7 +826,11 @@ RED.utils = (function() {
} }
result = nodeColorCache[type]; result = nodeColorCache[type];
} }
return result; if (result) {
return result;
} else {
return "#ddd";
}
} }
function addSpinnerOverlay(container,contain) { function addSpinnerOverlay(container,contain) {

View File

@ -1766,7 +1766,7 @@ RED.view = (function() {
clickTime = now; clickTime = now;
dblClickPrimed = (lastClickNode == mousedown_node && dblClickPrimed = (lastClickNode == mousedown_node &&
d3.event.buttons === 1 && d3.event.button === 0 &&
!d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey); !d3.event.shiftKey && !d3.event.metaKey && !d3.event.altKey && !d3.event.ctrlKey);
lastClickNode = mousedown_node; lastClickNode = mousedown_node;

View File

@ -67,6 +67,7 @@
}, },
inputs:1, // set the number of inputs - only 0 or 1 inputs:1, // set the number of inputs - only 0 or 1
outputs:1, // set the number of outputs - 0 to n outputs:1, // set the number of outputs - 0 to n
color: "#ddd", // set icon color
// set the icon (held in icons dir below where you save the node) // set the icon (held in icons dir below where you save the node)
icon: "myicon.png", // saved in icons/myicon.png icon: "myicon.png", // saved in icons/myicon.png
label: function() { // sets the default label contents label: function() { // sets the default label contents

View File

@ -21,7 +21,12 @@ import os
import subprocess import subprocess
from time import sleep from time import sleep
bounce = 25; try:
raw_input # Python 2
except NameError:
raw_input = input # Python 3
bounce = 25
if len(sys.argv) > 2: if len(sys.argv) > 2:
cmd = sys.argv[1].lower() cmd = sys.argv[1].lower()
@ -198,7 +203,7 @@ if len(sys.argv) > 2:
elif cmd == "kbd": # catch keyboard button events elif cmd == "kbd": # catch keyboard button events
try: try:
while not os.path.isdir("/dev/input/by-path"): while not os.path.isdir("/dev/input/by-path"):
time.sleep(10) sleep(10)
infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip() infile = subprocess.check_output("ls /dev/input/by-path/ | grep -m 1 'kbd'", shell=True).strip()
infile_path = "/dev/input/by-path/" + infile infile_path = "/dev/input/by-path/" + infile
EVENT_SIZE = struct.calcsize('llHHI') EVENT_SIZE = struct.calcsize('llHHI')

View File

@ -92,31 +92,80 @@ module.exports = function(RED) {
} }
function getProperty(node,msg) { function getProperty(node,msg) {
return new Promise((resolve,reject) => { if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else {
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
}
});
} else {
if (node.propertyType === 'jsonata') { if (node.propertyType === 'jsonata') {
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => { try {
if (err) { return RED.util.evaluateJSONataExpression(node.property,msg);
reject(RED._("switch.errors.invalid-expr",{error:err.message})); } catch(err) {
} else { throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
resolve(value); }
}
});
} else { } else {
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => { try {
if (err) { return RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
resolve(undefined); } catch(err) {
} else { return undefined;
resolve(value); }
}
});
} }
}); }
} }
function getV1(node,msg,rule,hasParts) { function getV1(node,msg,rule,hasParts) {
return new Promise( (resolve,reject) => { if (node.useAsyncRules) {
return new Promise( (resolve,reject) => {
if (rule.vt === 'prev') {
resolve(node.previousValue);
} else if (rule.vt === 'jsonata') {
var exp = rule.v;
if (rule.t === 'jsonata_exp') {
if (hasParts) {
exp.assign("I", msg.parts.index);
exp.assign("N", msg.parts.count);
}
}
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (rule.vt === 'json') {
resolve("json"); // TODO: ?! invalid case
} else if (rule.vt === 'null') {
resolve("null");
} else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
}
});
} else {
if (rule.vt === 'prev') { if (rule.vt === 'prev') {
resolve(node.previousValue); return node.previousValue;
} else if (rule.vt === 'jsonata') { } else if (rule.vt === 'jsonata') {
var exp = rule.v; var exp = rule.v;
if (rule.t === 'jsonata_exp') { if (rule.t === 'jsonata_exp') {
@ -125,83 +174,120 @@ module.exports = function(RED) {
exp.assign("N", msg.parts.count); exp.assign("N", msg.parts.count);
} }
} }
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => { try {
if (err) { return RED.util.evaluateJSONataExpression(exp,msg);
reject(RED._("switch.errors.invalid-expr",{error:err.message})); } catch(err) {
} else { throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
resolve(value); }
}
});
} else if (rule.vt === 'json') { } else if (rule.vt === 'json') {
resolve("json"); return "json"; // TODO: ?! invalid case
} else if (rule.vt === 'null') { } else if (rule.vt === 'null') {
resolve("null"); return "null";
} else { } else {
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) { try {
if (err) { return RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
resolve(undefined); } catch(err) {
} else { return undefined;
resolve(value); }
}
});
} }
}); }
} }
function getV2(node,msg,rule) { function getV2(node,msg,rule) {
return new Promise((resolve,reject) => { if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var v2 = rule.v2;
if (rule.v2t === 'prev') {
resolve(node.previousValue);
} else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
if (err) {
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
} else {
resolve(value);
}
});
} else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
if (err) {
resolve(undefined);
} else {
resolve(value);
}
});
} else {
resolve(v2);
}
})
} else {
var v2 = rule.v2; var v2 = rule.v2;
if (rule.v2t === 'prev') { if (rule.v2t === 'prev') {
resolve(node.previousValue); return node.previousValue;
} else if (rule.v2t === 'jsonata') { } else if (rule.v2t === 'jsonata') {
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => { try {
if (err) { return RED.util.evaluateJSONataExpression(rule.v2,msg);
reject(RED._("switch.errors.invalid-expr",{error:err.message})); } catch(err) {
} else { throw new Error(RED._("switch.errors.invalid-expr",{error:err.message}))
resolve(value); }
}
});
} else if (typeof v2 !== 'undefined') { } else if (typeof v2 !== 'undefined') {
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) { try {
if (err) { return RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
resolve(undefined); } catch(err) {
} else { return undefined;
resolve(value); }
}
});
} else { } else {
resolve(v2); return v2;
} }
}) }
} }
function applyRule(node, msg, property, state) { function applyRule(node, msg, property, state) {
return new Promise((resolve,reject) => { if (node.useAsyncRules) {
return new Promise((resolve,reject) => {
var rule = node.rules[state.currentRule]; var rule = node.rules[state.currentRule];
var v1,v2; var v1,v2;
getV1(node,msg,rule,state.hasParts).then(value => { getV1(node,msg,rule,state.hasParts).then(value => {
v1 = value; v1 = value;
}).then(()=>getV2(node,msg,rule)).then(value => { }).then(()=>getV2(node,msg,rule)).then(value => {
v2 = value; v2 = value;
}).then(() => { }).then(() => {
if (rule.t == "else") { if (rule.t == "else") {
property = state.elseflag; property = state.elseflag;
state.elseflag = true; state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return resolve(false);
} }
} else { if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(null); state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return resolve(false);
}
} else {
state.onward.push(null);
}
resolve(state.currentRule < node.rules.length - 1);
});
})
} else {
var rule = node.rules[state.currentRule];
var v1 = getV1(node,msg,rule,state.hasParts);
var v2 = getV2(node,msg,rule);
if (rule.t == "else") {
property = state.elseflag;
state.elseflag = true;
}
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
state.onward.push(msg);
state.elseflag = false;
if (node.checkall == "false") {
return false;
} }
resolve(state.currentRule < node.rules.length - 1); } else {
}); state.onward.push(null);
}) }
return state.currentRule < node.rules.length - 1
}
} }
function applyRules(node, msg, property,state) { function applyRules(node, msg, property,state) {
@ -215,7 +301,18 @@ module.exports = function(RED) {
msg.parts.hasOwnProperty("index") msg.parts.hasOwnProperty("index")
} }
} }
return applyRule(node,msg,property,state).then(hasMore => { if (node.useAsyncRules) {
return applyRule(node,msg,property,state).then(hasMore => {
if (hasMore) {
state.currentRule++;
return applyRules(node,msg,property,state);
} else {
node.previousValue = property;
return state.onward;
}
});
} else {
var hasMore = applyRule(node,msg,property,state);
if (hasMore) { if (hasMore) {
state.currentRule++; state.currentRule++;
return applyRules(node,msg,property,state); return applyRules(node,msg,property,state);
@ -223,7 +320,7 @@ module.exports = function(RED) {
node.previousValue = property; node.previousValue = property;
return state.onward; return state.onward;
} }
}); }
} }
@ -248,6 +345,14 @@ module.exports = function(RED) {
var valid = true; var valid = true;
var repair = n.repair; var repair = n.repair;
var needsCount = repair; var needsCount = repair;
this.useAsyncRules = (
this.propertyType === 'flow' ||
this.propertyType === 'global' || (
this.propertyType === 'jsonata' &&
/\$(flow|global)Context/.test(this.property)
)
);
for (var i=0; i<this.rules.length; i+=1) { for (var i=0; i<this.rules.length; i+=1) {
var rule = this.rules[i]; var rule = this.rules[i];
needsCount = needsCount || ((rule.t === "tail") || (rule.t === "jsonata_exp")); needsCount = needsCount || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
@ -258,6 +363,13 @@ module.exports = function(RED) {
rule.vt = 'str'; rule.vt = 'str';
} }
} }
this.useAsyncRules = this.useAsyncRules || (
rule.vt === 'flow' ||
rule.vt === 'global' || (
rule.vt === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v)
)
);
if (rule.vt === 'num') { if (rule.vt === 'num') {
if (!isNaN(Number(rule.v))) { if (!isNaN(Number(rule.v))) {
rule.v = Number(rule.v); rule.v = Number(rule.v);
@ -270,6 +382,9 @@ module.exports = function(RED) {
valid = false; valid = false;
} }
} }
if (rule.vt === 'flow' || rule.vt === 'global' || rule.vt === 'jsonata') {
this.useAsyncRules = true;
}
if (typeof rule.v2 !== 'undefined') { if (typeof rule.v2 !== 'undefined') {
if (!rule.v2t) { if (!rule.v2t) {
if (!isNaN(Number(rule.v2))) { if (!isNaN(Number(rule.v2))) {
@ -278,6 +393,13 @@ module.exports = function(RED) {
rule.v2t = 'str'; rule.v2t = 'str';
} }
} }
this.useAsyncRules = this.useAsyncRules || (
rule.v2t === 'flow' ||
rule.v2t === 'global' || (
rule.v2t === 'jsonata' &&
/\$(flow|global)Context/.test(rule.v2)
)
);
if (rule.v2t === 'num') { if (rule.v2t === 'num') {
rule.v2 = Number(rule.v2); rule.v2 = Number(rule.v2);
} else if (rule.v2t === 'jsonata') { } else if (rule.v2t === 'jsonata') {
@ -290,7 +412,6 @@ module.exports = function(RED) {
} }
} }
} }
if (!valid) { if (!valid) {
return; return;
} }
@ -420,18 +541,32 @@ module.exports = function(RED) {
if (needsCount && checkParts && hasParts) { if (needsCount && checkParts && hasParts) {
return addMessageToPending(msg); return addMessageToPending(msg);
} }
return getProperty(node,msg) if (node.useAsyncRules) {
.then(property => applyRules(node,msg,property)) return getProperty(node,msg)
.then(onward => { .then(property => applyRules(node,msg,property))
if (!repair || !hasParts) { .then(onward => {
node.send(onward); if (!repair || !hasParts) {
} node.send(onward);
else { }
sendGroupMessages(onward, msg); else {
} sendGroupMessages(onward, msg);
}).catch(err => { }
node.warn(err); }).catch(err => {
}); node.warn(err);
});
} else {
try {
var property = getProperty(node,msg);
var onward = applyRules(node,msg,property);
if (!repair || !hasParts) {
node.send(onward);
} else {
sendGroupMessages(onward, msg);
}
} catch(err) {
node.warn(err);
}
}
} }
function clearPending() { function clearPending() {
@ -473,7 +608,11 @@ module.exports = function(RED) {
} }
this.on('input', function(msg) { this.on('input', function(msg) {
processMessageQueue(msg); if (node.useAsyncRules) {
processMessageQueue(msg);
} else {
processMessage(msg,true);
}
}); });
this.on('close', function() { this.on('close', function() {

View File

@ -1,6 +1,6 @@
{ {
"name": "node-red", "name": "node-red",
"version": "0.19.3", "version": "0.19.4",
"description": "A visual tool for wiring the Internet of Things", "description": "A visual tool for wiring the Internet of Things",
"homepage": "http://nodered.org", "homepage": "http://nodered.org",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -17,6 +17,7 @@
var clone = require("clone"); var clone = require("clone");
var log = require("../../log"); var log = require("../../log");
var memory = require("./memory"); var memory = require("./memory");
var util = require("../../util");
var settings; var settings;
@ -209,104 +210,131 @@ function createContext(id,seed) {
insertSeedValues = function(keys,values) { insertSeedValues = function(keys,values) {
if (!Array.isArray(keys)) { if (!Array.isArray(keys)) {
if (values[0] === undefined) { if (values[0] === undefined) {
values[0] = seed[keys]; values[0] = util.getObjectProperty(seed,keys);
} }
} else { } else {
for (var i=0;i<keys.length;i++) { for (var i=0;i<keys.length;i++) {
if (values[i] === undefined) { if (values[i] === undefined) {
values[i] = seed[keys[i]]; values[i] = util.getObjectProperty(seed,keys[i]);
} }
} }
} }
} }
} }
Object.defineProperties(obj, {
get: {
value: function(key, storage, callback) {
var context;
obj.get = function(key, storage, callback) { if (!callback && typeof storage === 'function') {
var context; callback = storage;
if (!storage && !callback) { storage = undefined;
context = stores["_"]; }
} else { if (callback && typeof callback !== 'function'){
if (typeof storage === 'function') { throw new Error("Callback must be a function");
callback = storage; }
storage = "_";
} if (!Array.isArray(key)) {
if (callback && typeof callback !== 'function'){ var keyParts = util.parseContextStore(key);
throw new Error("Callback must be a function"); key = keyParts.key;
} if (!storage) {
context = getContextStorage(storage); storage = keyParts.store || "_";
}
if (callback) {
if (!seed) {
context.get(scope,key,callback);
} else {
context.get(scope,key,function() {
if (arguments[0]) {
callback(arguments[0]);
return;
} }
var results = Array.prototype.slice.call(arguments,[1]); } else {
insertSeedValues(key,results); if (!storage) {
// Put the err arg back storage = "_";
results.unshift(undefined); }
callback.apply(null,results); }
}); context = getContextStorage(storage);
}
} else { if (callback) {
// No callback, attempt to do this synchronously if (!seed) {
var results = context.get(scope,key); context.get(scope,key,callback);
if (seed) { } else {
if (Array.isArray(key)) { context.get(scope,key,function() {
insertSeedValues(key,results); if (arguments[0]) {
} else if (results === undefined){ callback(arguments[0]);
results = seed[key]; return;
}
var results = Array.prototype.slice.call(arguments,[1]);
insertSeedValues(key,results);
// Put the err arg back
results.unshift(undefined);
callback.apply(null,results);
});
}
} else {
// No callback, attempt to do this synchronously
var results = context.get(scope,key);
if (seed) {
if (Array.isArray(key)) {
insertSeedValues(key,results);
} else if (results === undefined){
results = util.getObjectProperty(seed,key);
}
}
return results;
}
}
},
set: {
value: function(key, value, storage, callback) {
var context;
if (!callback && typeof storage === 'function') {
callback = storage;
storage = undefined;
}
if (callback && typeof callback !== 'function'){
throw new Error("Callback must be a function");
}
if (!Array.isArray(key)) {
var keyParts = util.parseContextStore(key);
key = keyParts.key;
if (!storage) {
storage = keyParts.store || "_";
}
} else {
if (!storage) {
storage = "_";
}
}
context = getContextStorage(storage);
context.set(scope, key, value, callback);
}
},
keys: {
value: function(storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (seed) {
if (callback) {
context.keys(scope, function(err,keys) {
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
});
} else {
var keys = context.keys(scope);
return Array.from(new Set(seedKeys.concat(keys)).keys())
}
} else {
return context.keys(scope, callback);
} }
} }
return results;
} }
}; });
obj.set = function(key, value, storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
context.set(scope, key, value, callback);
};
obj.keys = function(storage, callback) {
var context;
if (!storage && !callback) {
context = stores["_"];
} else {
if (typeof storage === 'function') {
callback = storage;
storage = "_";
}
if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function");
}
context = getContextStorage(storage);
}
if (seed) {
if (callback) {
context.keys(scope, function(err,keys) {
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
});
} else {
var keys = context.keys(scope);
return Array.from(new Set(seedKeys.concat(keys)).keys())
}
} else {
return context.keys(scope, callback);
}
};
return obj; return obj;
} }
@ -320,9 +348,13 @@ function getContext(localId,flowId) {
} }
var newContext = createContext(contextId); var newContext = createContext(contextId);
if (flowId) { if (flowId) {
newContext.flow = getContext(flowId); Object.defineProperty(newContext, 'flow', {
value: getContext(flowId)
});
} }
newContext.global = contexts['global']; Object.defineProperty(newContext, 'global', {
value: contexts['global']
})
contexts[contextId] = newContext; contexts[contextId] = newContext;
return newContext; return newContext;
} }

View File

@ -140,6 +140,7 @@ function stringify(value) {
function LocalFileSystem(config){ function LocalFileSystem(config){
this.config = config; this.config = config;
this.storageBaseDir = getBasePath(this.config); this.storageBaseDir = getBasePath(this.config);
this.writePromise = Promise.resolve();
if (config.hasOwnProperty('cache')?config.cache:true) { if (config.hasOwnProperty('cache')?config.cache:true) {
this.cache = MemoryStore({}); this.cache = MemoryStore({});
} }
@ -213,12 +214,13 @@ LocalFileSystem.prototype.open = function(){
} }
LocalFileSystem.prototype.close = function(){ LocalFileSystem.prototype.close = function(){
if (this.cache && this._flushPendingWrites) { var self = this;
if (this.cache && this._pendingWriteTimeout) {
clearTimeout(this._pendingWriteTimeout); clearTimeout(this._pendingWriteTimeout);
delete this._pendingWriteTimeout; delete this._pendingWriteTimeout;
return this._flushPendingWrites(); this.flushInterval = 0;
} }
return Promise.resolve(); return this.writePromise;
} }
LocalFileSystem.prototype.get = function(scope, key, callback) { LocalFileSystem.prototype.get = function(scope, key, callback) {
@ -230,14 +232,31 @@ LocalFileSystem.prototype.get = function(scope, key, callback) {
} }
var storagePath = getStoragePath(this.storageBaseDir ,scope); var storagePath = getStoragePath(this.storageBaseDir ,scope);
loadFile(storagePath + ".json").then(function(data){ loadFile(storagePath + ".json").then(function(data){
var value;
if(data){ if(data){
data = JSON.parse(data); data = JSON.parse(data);
if (!Array.isArray(key)) { if (!Array.isArray(key)) {
callback(null, util.getObjectProperty(data,key)); try {
value = util.getObjectProperty(data,key);
} catch(err) {
if (err.code === "INVALID_EXPR") {
throw err;
}
value = undefined;
}
callback(null, value);
} else { } else {
var results = [undefined]; var results = [undefined];
for (var i=0;i<key.length;i++) { for (var i=0;i<key.length;i++) {
results.push(util.getObjectProperty(data,key[i])) try {
value = util.getObjectProperty(data,key[i]);
} catch(err) {
if (err.code === "INVALID_EXPR") {
throw err;
}
value = undefined;
}
results.push(value)
} }
callback.apply(null,results); callback.apply(null,results);
} }
@ -260,15 +279,17 @@ LocalFileSystem.prototype.set = function(scope, key, value, callback) {
return; return;
} else { } else {
this._pendingWriteTimeout = setTimeout(function() { this._pendingWriteTimeout = setTimeout(function() {
self._flushPendingWrites.call(self).catch(function(err) { self.writePromise = self.writePromise.then(function(){
log.error(log._("context.localfilesystem.error-write",{message:err.toString()})) return self._flushPendingWrites.call(self).catch(function(err) {
log.error(log._("context.localfilesystem.error-write",{message:err.toString()}));
});
}); });
}, this.flushInterval); }, this.flushInterval);
} }
} else if (callback && typeof callback !== 'function') { } else if (callback && typeof callback !== 'function') {
throw new Error("Callback must be a function"); throw new Error("Callback must be a function");
} else { } else {
loadFile(storagePath + ".json").then(function(data){ self.writePromise = self.writePromise.then(function() { return loadFile(storagePath + ".json") }).then(function(data){
var obj = data ? JSON.parse(data) : {} var obj = data ? JSON.parse(data) : {}
if (!Array.isArray(key)) { if (!Array.isArray(key)) {
key = [key]; key = [key];

View File

@ -32,7 +32,14 @@ Memory.prototype._getOne = function(scope, key) {
var value; var value;
var error; var error;
if(this.data[scope]){ if(this.data[scope]){
value = util.getObjectProperty(this.data[scope], key); try {
value = util.getObjectProperty(this.data[scope], key);
} catch(err) {
if (err.code === "INVALID_EXPR") {
throw err;
}
value = undefined;
}
} }
return value; return value;
} }

View File

@ -130,13 +130,19 @@ function compareObjects(obj1,obj2) {
return true; return true;
} }
function createError(code, message) {
var e = new Error(message);
e.code = code;
return e;
}
function normalisePropertyExpression(str) { function normalisePropertyExpression(str) {
// This must be kept in sync with validatePropertyExpression // This must be kept in sync with validatePropertyExpression
// in editor/js/ui/utils.js // in editor/js/ui/utils.js
var length = str.length; var length = str.length;
if (length === 0) { if (length === 0) {
throw new Error("Invalid property expression: zero-length"); throw createError("INVALID_EXPR","Invalid property expression: zero-length");
} }
var parts = []; var parts = [];
var start = 0; var start = 0;
@ -149,14 +155,14 @@ function normalisePropertyExpression(str) {
if (!inString) { if (!inString) {
if (c === "'" || c === '"') { if (c === "'" || c === '"') {
if (i != start) { if (i != start) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i); throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
} }
inString = true; inString = true;
quoteChar = c; quoteChar = c;
start = i+1; start = i+1;
} else if (c === '.') { } else if (c === '.') {
if (i===0) { if (i===0) {
throw new Error("Invalid property expression: unexpected . at position 0"); throw createError("INVALID_EXPR","Invalid property expression: unexpected . at position 0");
} }
if (start != i) { if (start != i) {
v = str.substring(start,i); v = str.substring(start,i);
@ -167,57 +173,57 @@ function normalisePropertyExpression(str) {
} }
} }
if (i===length-1) { if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression"); throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
} }
// Next char is first char of an identifier: a-z 0-9 $ _ // Next char is first char of an identifier: a-z 0-9 $ _
if (!/[a-z0-9\$\_]/i.test(str[i+1])) { if (!/[a-z0-9\$\_]/i.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1)); throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
} }
start = i+1; start = i+1;
} else if (c === '[') { } else if (c === '[') {
if (i === 0) { if (i === 0) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i); throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
} }
if (start != i) { if (start != i) {
parts.push(str.substring(start,i)); parts.push(str.substring(start,i));
} }
if (i===length-1) { if (i===length-1) {
throw new Error("Invalid property expression: unterminated expression"); throw createError("INVALID_EXPR","Invalid property expression: unterminated expression");
} }
// Next char is either a quote or a number // Next char is either a quote or a number
if (!/["'\d]/.test(str[i+1])) { if (!/["'\d]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1)); throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" at position "+(i+1));
} }
start = i+1; start = i+1;
inBox = true; inBox = true;
} else if (c === ']') { } else if (c === ']') {
if (!inBox) { if (!inBox) {
throw new Error("Invalid property expression: unexpected "+c+" at position "+i); throw createError("INVALID_EXPR","Invalid property expression: unexpected "+c+" at position "+i);
} }
if (start != i) { if (start != i) {
v = str.substring(start,i); v = str.substring(start,i);
if (/^\d+$/.test(v)) { if (/^\d+$/.test(v)) {
parts.push(parseInt(v)); parts.push(parseInt(v));
} else { } else {
throw new Error("Invalid property expression: unexpected array expression at position "+start); throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
} }
} }
start = i+1; start = i+1;
inBox = false; inBox = false;
} else if (c === ' ') { } else if (c === ' ') {
throw new Error("Invalid property expression: unexpected ' ' at position "+i); throw createError("INVALID_EXPR","Invalid property expression: unexpected ' ' at position "+i);
} }
} else { } else {
if (c === quoteChar) { if (c === quoteChar) {
if (i-start === 0) { if (i-start === 0) {
throw new Error("Invalid property expression: zero-length string at position "+start); throw createError("INVALID_EXPR","Invalid property expression: zero-length string at position "+start);
} }
parts.push(str.substring(start,i)); parts.push(str.substring(start,i));
// If inBox, next char must be a ]. Otherwise it may be [ or . // If inBox, next char must be a ]. Otherwise it may be [ or .
if (inBox && !/\]/.test(str[i+1])) { if (inBox && !/\]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected array expression at position "+start); throw createError("INVALID_EXPR","Invalid property expression: unexpected array expression at position "+start);
} else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) { } else if (!inBox && i+1!==length && !/[\[\.]/.test(str[i+1])) {
throw new Error("Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1)); throw createError("INVALID_EXPR","Invalid property expression: unexpected "+str[i+1]+" expression at position "+(i+1));
} }
start = i+1; start = i+1;
inString = false; inString = false;
@ -226,7 +232,7 @@ function normalisePropertyExpression(str) {
} }
if (inBox || inString) { if (inBox || inString) {
throw new Error("Invalid property expression: unterminated expression"); throw new createError("INVALID_EXPR","Invalid property expression: unterminated expression");
} }
if (start < length) { if (start < length) {
parts.push(str.substring(start)); parts.push(str.substring(start));

View File

@ -113,6 +113,21 @@ describe('context', function() {
context2.global.get("foo").should.equal("test"); context2.global.get("foo").should.equal("test");
}); });
it('context.flow/global are not enumerable', function() {
var context1 = Context.get("1","flowA");
Object.keys(context1).length.should.equal(0);
Object.keys(context1.flow).length.should.equal(0);
Object.keys(context1.global).length.should.equal(0);
})
it('context.flow/global cannot be deleted', function() {
var context1 = Context.get("1","flowA");
delete context1.flow;
should.exist(context1.flow);
delete context1.global;
should.exist(context1.global);
})
it('deletes context',function() { it('deletes context',function() {
var context = Context.get("1","flowA"); var context = Context.get("1","flowA");
should.not.exist(context.get("foo")); should.not.exist(context.get("foo"));
@ -191,15 +206,26 @@ describe('context', function() {
}); });
it('returns functionGlobalContext value if store value undefined', function() { it('returns functionGlobalContext value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:"bar"}}); Context.init({functionGlobalContext: {foo:"bar"}});
Context.load().then(function(){ return Context.load().then(function(){
var context = Context.get("1","flowA"); var context = Context.get("1","flowA");
var v = context.global.get('foo'); var v = context.global.get('foo');
v.should.equal('bar'); v.should.equal('bar');
}); });
}) })
it('returns functionGlobalContext sub-value if store value undefined', function() {
Context.init({functionGlobalContext: {foo:{bar:123}}});
return Context.load().then(function(){
var context = Context.get("1","flowA");
var v = context.global.get('foo.bar');
should.equal(v,123);
});
})
}); });
describe('external context storage',function() { describe('external context storage',function() {
@ -473,6 +499,23 @@ describe('context', function() {
done(); done();
}).catch(done); }).catch(done);
}); });
it('should allow the store name to be provide in the key', function(done) {
Context.init({contextStorage:contextDefaultStorage});
Context.load().then(function(){
var context = Context.get("1","flow");
var cb = function(){done("An error occurred")}
context.set("#:(test)::foo","bar");
context.get("#:(test)::foo");
stubGet2.called.should.be.false();
stubSet2.called.should.be.false();
stubSet.calledWithExactly("1:flow","foo","bar",undefined).should.be.true();
stubGet.calledWith("1:flow","foo").should.be.true();
done();
}).catch(done);
});
it('should use default as the alias of other context', function(done) { it('should use default as the alias of other context', function(done) {
Context.init({contextStorage:contextAlias}); Context.init({contextStorage:contextAlias});
Context.load().then(function(){ Context.load().then(function(){
@ -579,16 +622,16 @@ describe('context', function() {
}); });
it('should return multiple functionGlobalContext values if key is an array', function(done) { it('should return multiple functionGlobalContext values if key is an array', function(done) {
var fGC = { "foo1": 456, "foo2": 789 }; var fGC = { "foo1": 456, "foo2": {"bar":789} };
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC }); Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
Context.load().then(function(){ Context.load().then(function(){
var context = Context.get("1","flow"); var context = Context.get("1","flow");
context.global.get(["foo1","foo2","foo3"], "memory", function(err,foo1,foo2,foo3){ context.global.get(["foo1","foo2.bar","foo3"], "memory", function(err,foo1,foo2,foo3){
if (err) { if (err) {
done(err); done(err);
} else { } else {
foo1.should.be.equal(456); should.equal(foo1, 456);
foo2.should.be.equal(789); should.equal(foo2, 789);
should.not.exist(foo3); should.not.exist(foo3);
done(); done();
} }

View File

@ -672,7 +672,7 @@ describe('localfilesystem',function() {
it('should enumerate context keys in the cache',function() { it('should enumerate context keys in the cache',function() {
var globalData = {foo:"bar"}; var globalData = {foo:"bar"};
fs.outputFile(path.join(resourcesDir,defaultContextBase,"global","global.json"), JSON.stringify(globalData,null,4), "utf8").then(function(){ return fs.outputFile(path.join(resourcesDir,defaultContextBase,"global","global.json"), JSON.stringify(globalData,null,4), "utf8").then(function(){
context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 2}); context = LocalFileSystem({dir: resourcesDir, cache: true, flushInterval: 2});
return context.open() return context.open()
}).then(function(){ }).then(function(){

View File

@ -96,7 +96,7 @@ describe('memory',function() {
context.set("nodeX","three","test3"); context.set("nodeX","three","test3");
context.set("nodeX","four","test4"); context.set("nodeX","four","test4");
var values = context.get("nodeX",["one","unknown"]); var values = context.get("nodeX",["one","unknown.with.multiple.levels"]);
values.should.eql(["test1",undefined]) values.should.eql(["test1",undefined])
}) })
it('should throw error if bad key included in multiple keys', function() { it('should throw error if bad key included in multiple keys', function() {