').appendTo(row);
if (entry.empty) {
container.addClass('red-ui-search-empty');
- container.text("No remotes");
+ container.text(RED._("sidebar.project.projectSettings.noRemotes"));
return;
} else {
$('
').appendTo(container);
@@ -1240,7 +1240,7 @@ RED.projects.settings = (function() {
.click(function(e) {
e.preventDefault();
var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
- var notification = RED.notify("Are you sure you want to delete the remote '"+entry.name+"'?", {
+ var notification = RED.notify(RED._("sidebar.project.projectSettings.deleteRemoteConfrim", { name: entry.name }), {
type: "warning",
modal: true,
fixed: true,
@@ -1252,7 +1252,7 @@ RED.projects.settings = (function() {
notification.close();
}
},{
- text: 'Delete remote',
+ text: RED._("sidebar.project.projectSettings.deleteRemote"),
click: function() {
notification.close();
@@ -1315,10 +1315,10 @@ RED.projects.settings = (function() {
// var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\.git)?(?:\/?|\#[\d\w\.\-_]+?)$/.test(remoteURLInput.val());
var validRepo = repo.length > 0 && !/\s/.test(repo);
if (/^https?:\/\/[^/]+@/i.test(repo)) {
- remoteURLLabel.text("Do not include the username/password in the url");
+ remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule2"));
validRepo = false;
} else {
- remoteURLLabel.text("https://, ssh:// or file://");
+ remoteURLLabel.text(RED._("sidebar.project.projectSettings.urlRule"));
}
saveButton.attr('disabled',(!validName || !validRepo))
remoteNameInput.toggleClass('input-error',remoteNameInputChanged&&!validName);
@@ -1332,22 +1332,22 @@ RED.projects.settings = (function() {
var remoteNameInputChanged = false;
var remoteURLInputChanged = false;
- $('
+
diff --git a/nodes/core/hardware/36-rpi-gpio.js b/nodes/core/hardware/36-rpi-gpio.js
index cc1a8ab2a..0a21f578a 100644
--- a/nodes/core/hardware/36-rpi-gpio.js
+++ b/nodes/core/hardware/36-rpi-gpio.js
@@ -6,35 +6,36 @@ module.exports = function(RED) {
var fs = require('fs');
var gpioCommand = __dirname+'/nrgpio';
+ var allOK = true;
try {
var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
- if (cpuinfo.indexOf(": BCM") === -1) { throw "Info : "+RED._("rpi-gpio.errors.ignorenode"); }
- } catch(err) {
- throw "Info : "+RED._("rpi-gpio.errors.ignorenode");
- }
-
- try {
- fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
- // /usr/lib/python2.7/dist-packages/RPi/GPIO
- } catch(err) {
+ if (cpuinfo.indexOf(": BCM") === -1) {
+ allOK = false;
+ RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
+ }
try {
- fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
- }
- catch(err) {
+ fs.statSync("/usr/share/doc/python-rpi.gpio"); // test on Raspbian
+ // /usr/lib/python2.7/dist-packages/RPi/GPIO
+ } catch(err) {
try {
- fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
- }
- catch(err) {
- RED.log.warn(RED._("rpi-gpio.errors.libnotfound"));
- throw "Warning : "+RED._("rpi-gpio.errors.libnotfound");
+ fs.statSync("/usr/lib/python2.7/site-packages/RPi/GPIO"); // test on Arch
+ } catch(err) {
+ try {
+ fs.statSync("/usr/lib/python2.7/dist-packages/RPi/GPIO"); // test on Hypriot
+ } catch(err) {
+ RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.libnotfound"));
+ allOK = false;
+ }
}
}
- }
-
- if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
- RED.log.error(RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
- throw "Error : "+RED._("rpi-gpio.errors.mustbeexecutable");
+ if ( !(1 & parseInt((fs.statSync(gpioCommand).mode & parseInt("777", 8)).toString(8)[0]) )) {
+ RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.needtobeexecutable",{command:gpioCommand}));
+ allOK = false;
+ }
+ } catch(err) {
+ allOK = false;
+ RED.log.warn("rpi-gpio : "+RED._("rpi-gpio.errors.ignorenode"));
}
// the magic to make python print stuff immediately
@@ -61,48 +62,62 @@ module.exports = function(RED) {
}
}
- if (node.pin !== undefined) {
- node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
- node.running = true;
- node.status({fill:"green",shape:"dot",text:"common.status.ok"});
+ if (allOK === true) {
+ if (node.pin !== undefined) {
+ node.child = spawn(gpioCommand, ["in",node.pin,node.intype,node.debounce]);
+ node.running = true;
+ node.status({fill:"green",shape:"dot",text:"common.status.ok"});
- node.child.stdout.on('data', function (data) {
- var d = data.toString().trim().split("\n");
- for (var i = 0; i < d.length; i++) {
- if (d[i] === '') { return; }
- if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
- node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
+ node.child.stdout.on('data', function (data) {
+ var d = data.toString().trim().split("\n");
+ for (var i = 0; i < d.length; i++) {
+ if (d[i] === '') { return; }
+ if (node.running && node.buttonState !== -1 && !isNaN(Number(d[i])) && node.buttonState !== d[i]) {
+ node.send({ topic:"pi/"+node.pin, payload:Number(d[i]) });
+ }
+ node.buttonState = d[i];
+ node.status({fill:"green",shape:"dot",text:d[i]});
+ if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
}
- node.buttonState = d[i];
- node.status({fill:"green",shape:"dot",text:d[i]});
- if (RED.settings.verbose) { node.log("out: "+d[i]+" :"); }
- }
- });
+ });
- node.child.stderr.on('data', function (data) {
- if (RED.settings.verbose) { node.log("err: "+data+" :"); }
- });
+ node.child.stderr.on('data', function (data) {
+ if (RED.settings.verbose) { node.log("err: "+data+" :"); }
+ });
- node.child.on('close', function (code) {
- node.running = false;
- node.child = null;
- if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
- if (node.done) {
- node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
- node.done();
- }
- else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
- });
+ node.child.on('close', function (code) {
+ node.running = false;
+ node.child = null;
+ if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
+ if (node.done) {
+ node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
+ node.done();
+ }
+ else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
+ });
- node.child.on('error', function (err) {
- if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
- else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
- else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
- });
+ node.child.on('error', function (err) {
+ if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
+ else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
+ else { node.error(RED._("rpi-gpio.errors.error",{error:err.errno})) }
+ });
+ }
+ else {
+ node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
+ }
}
else {
- node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
+ node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
+ if (node.read === true) {
+ var val;
+ if (node.intype == "up") { val = 1; }
+ if (node.intype == "down") { val = 0; }
+ setTimeout(function(){
+ node.send({ topic:"pi/"+node.pin, payload:val });
+ node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:val})});
+ },250);
+ }
}
node.on("close", function(done) {
@@ -155,20 +170,83 @@ module.exports = function(RED) {
else { node.warn(RED._("rpi-gpio.errors.invalidinput")+": "+out); }
}
- if (node.pin !== undefined) {
- if (node.set && (node.out === "out")) {
- node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
- node.status({fill:"green",shape:"dot",text:node.level});
- } else {
- node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
- node.status({fill:"green",shape:"dot",text:"common.status.ok"});
- }
- node.running = true;
+ if (allOK === true) {
+ if (node.pin !== undefined) {
+ if (node.set && (node.out === "out")) {
+ node.child = spawn(gpioCommand, [node.out,node.pin,node.level]);
+ node.status({fill:"green",shape:"dot",text:node.level});
+ } else {
+ node.child = spawn(gpioCommand, [node.out,node.pin,node.freq]);
+ node.status({fill:"green",shape:"dot",text:"common.status.ok"});
+ }
+ node.running = true;
- node.on("input", inputlistener);
+ node.on("input", inputlistener);
+
+ node.child.stdout.on('data', function (data) {
+ if (RED.settings.verbose) { node.log("out: "+data+" :"); }
+ });
+
+ node.child.stderr.on('data', function (data) {
+ if (RED.settings.verbose) { node.log("err: "+data+" :"); }
+ });
+
+ node.child.on('close', function (code) {
+ node.child = null;
+ node.running = false;
+ if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
+ if (node.done) {
+ node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
+ node.done();
+ }
+ else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
+ });
+
+ node.child.on('error', function (err) {
+ if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
+ else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
+ else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
+ });
+
+ }
+ else {
+ node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
+ }
+ }
+ else {
+ node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
+ node.on("input", function(msg){
+ node.status({fill:"grey",shape:"dot",text:RED._("rpi-gpio.status.na",{value:msg.payload.toString()})});
+ });
+ }
+
+ node.on("close", function(done) {
+ node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
+ delete pinsInUse[node.pin];
+ if (node.child != null) {
+ node.done = done;
+ node.child.stdin.write("close "+node.pin);
+ node.child.kill('SIGKILL');
+ }
+ else { done(); }
+ });
+
+ }
+ RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
+
+ function PiMouseNode(n) {
+ RED.nodes.createNode(this,n);
+ this.butt = n.butt || 7;
+ var node = this;
+
+ if (allOK === true) {
+ node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
+ node.status({fill:"green",shape:"dot",text:"common.status.ok"});
node.child.stdout.on('data', function (data) {
- if (RED.settings.verbose) { node.log("out: "+data+" :"); }
+ data = Number(data);
+ if (data !== 0) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
+ else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
});
node.child.stderr.on('data', function (data) {
@@ -192,69 +270,19 @@ module.exports = function(RED) {
else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
});
+ node.on("close", function(done) {
+ node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
+ if (node.child != null) {
+ node.done = done;
+ node.child.kill('SIGINT');
+ node.child = null;
+ }
+ else { done(); }
+ });
}
else {
- node.warn(RED._("rpi-gpio.errors.invalidpin")+": "+node.pin);
+ node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
}
-
- node.on("close", function(done) {
- node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
- delete pinsInUse[node.pin];
- if (node.child != null) {
- node.done = done;
- node.child.stdin.write("close "+node.pin);
- node.child.kill('SIGKILL');
- }
- else { done(); }
- });
-
- }
- RED.nodes.registerType("rpi-gpio out",GPIOOutNode);
-
- function PiMouseNode(n) {
- RED.nodes.createNode(this,n);
- this.butt = n.butt || 7;
- var node = this;
-
- node.child = spawn(gpioCommand+".py", ["mouse",node.butt]);
- node.status({fill:"green",shape:"dot",text:"common.status.ok"});
-
- node.child.stdout.on('data', function (data) {
- data = Number(data);
- if (data === 1) { node.send({ topic:"pi/mouse", button:data, payload:1 }); }
- else { node.send({ topic:"pi/mouse", button:data, payload:0 }); }
- });
-
- node.child.stderr.on('data', function (data) {
- if (RED.settings.verbose) { node.log("err: "+data+" :"); }
- });
-
- node.child.on('close', function (code) {
- node.child = null;
- node.running = false;
- if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
- if (node.done) {
- node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
- node.done();
- }
- else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
- });
-
- node.child.on('error', function (err) {
- if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
- else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
- else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
- });
-
- node.on("close", function(done) {
- node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
- if (node.child != null) {
- node.done = done;
- node.child.kill('SIGINT');
- node.child = null;
- }
- else { done(); }
- });
}
RED.nodes.registerType("rpi-mouse",PiMouseNode);
@@ -262,39 +290,40 @@ module.exports = function(RED) {
RED.nodes.createNode(this,n);
var node = this;
- node.child = spawn(gpioCommand+".py", ["kbd","0"]);
- node.status({fill:"green",shape:"dot",text:"common.status.ok"});
+ if (allOK === true) {
+ node.child = spawn(gpioCommand+".py", ["kbd","0"]);
+ node.status({fill:"green",shape:"dot",text:"common.status.ok"});
- node.child.stdout.on('data', function (data) {
- var b = data.toString().trim().split(",");
- var act = "up";
- if (b[1] === "1") { act = "down"; }
- if (b[1] === "2") { act = "repeat"; }
- node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
- });
+ node.child.stdout.on('data', function (data) {
+ var b = data.toString().trim().split(",");
+ var act = "up";
+ if (b[1] === "1") { act = "down"; }
+ if (b[1] === "2") { act = "repeat"; }
+ node.send({ topic:"pi/key", payload:Number(b[0]), action:act });
+ });
- node.child.stderr.on('data', function (data) {
- if (RED.settings.verbose) { node.log("err: "+data+" :"); }
- });
+ node.child.stderr.on('data', function (data) {
+ if (RED.settings.verbose) { node.log("err: "+data+" :"); }
+ });
- node.child.on('close', function (code) {
- node.running = false;
- node.child = null;
- if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
- if (node.done) {
- node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
- node.done();
- }
- else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
- });
+ node.child.on('close', function (code) {
+ node.running = false;
+ node.child = null;
+ if (RED.settings.verbose) { node.log(RED._("rpi-gpio.status.closed")); }
+ if (node.done) {
+ node.status({fill:"grey",shape:"ring",text:"rpi-gpio.status.closed"});
+ node.done();
+ }
+ else { node.status({fill:"red",shape:"ring",text:"rpi-gpio.status.stopped"}); }
+ });
- node.child.on('error', function (err) {
- if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
- else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
- else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
- });
+ node.child.on('error', function (err) {
+ if (err.errno === "ENOENT") { node.error(RED._("rpi-gpio.errors.commandnotfound")); }
+ else if (err.errno === "EACCES") { node.error(RED._("rpi-gpio.errors.commandnotexecutable")); }
+ else { node.error(RED._("rpi-gpio.errors.error")+': ' + err.errno); }
+ });
- node.on("close", function(done) {
+ node.on("close", function(done) {
node.status({});
if (node.child != null) {
node.done = done;
@@ -303,24 +332,30 @@ module.exports = function(RED) {
}
else { done(); }
});
+ }
+ else {
+ node.status({fill:"grey",shape:"dot",text:"node-red:rpi-gpio.status.not-available"});
+ }
}
RED.nodes.registerType("rpi-keyboard",PiKeyboardNode);
var pitype = { type:"" };
- exec(gpioCommand+" info", function(err,stdout,stderr) {
- if (err) {
- RED.log.info(RED._("rpi-gpio.errors.version"));
- }
- else {
- try {
- var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
- pitype.type = info["TYPE"];
+ if (allOK === true) {
+ exec(gpioCommand+" info", function(err,stdout,stderr) {
+ if (err) {
+ RED.log.info(RED._("rpi-gpio.errors.version"));
}
- catch(e) {
- RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
+ else {
+ try {
+ var info = JSON.parse( stdout.trim().replace(/\'/g,"\"") );
+ pitype.type = info["TYPE"];
+ }
+ catch(e) {
+ RED.log.info(RED._("rpi-gpio.errors.sawpitype"),stdout.trim());
+ }
}
- }
- });
+ });
+ }
RED.httpAdmin.get('/rpi-gpio/:id', RED.auth.needsPermission('rpi-gpio.read'), function(req,res) {
res.json(pitype);
diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json
index 74ec0bcd4..d59f3d1bf 100644
--- a/nodes/core/locales/en-US/messages.json
+++ b/nodes/core/locales/en-US/messages.json
@@ -786,8 +786,8 @@
"na": "N/A : __value__"
},
"errors": {
- "ignorenode": "Ignoring Raspberry Pi specific node",
- "version": "Version command failed",
+ "ignorenode": "Raspberry Pi specific node set inactive",
+ "version": "Failed to get version from Pi",
"sawpitype": "Saw Pi Type",
"libnotfound": "Cannot find Pi RPi.GPIO python library",
"alreadyset": "GPIO pin __pin__ already set as type: __type__",
diff --git a/nodes/core/storage/50-file.html b/nodes/core/storage/50-file.html
index cdb001eb6..eabf56a76 100644
--- a/nodes/core/storage/50-file.html
+++ b/nodes/core/storage/50-file.html
@@ -123,9 +123,8 @@
},
color:"BurlyWood",
inputs:1,
- outputs:0,
- icon: "file.png",
- align: "right",
+ outputs:1,
+ icon: "file-out.png",
label: function() {
if (this.overwriteFile === "delete") {
return this.name||this._("file.label.deletelabel",{file:this.filename});
@@ -159,7 +158,7 @@
outputLabels: function(i) {
return (this.format === "utf8") ? "UTF8 string" : "binary buffer";
},
- icon: "file.png",
+ icon: "file-in.png",
label: function() {
return this.name||this.filename||this._("file.label.filelabel");
},
diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js
index a0e49f6fc..76cedb136 100644
--- a/nodes/core/storage/50-file.js
+++ b/nodes/core/storage/50-file.js
@@ -39,14 +39,20 @@ module.exports = function(RED) {
node.tout = null;
},333);
}
- if (filename === "") { node.warn(RED._("file.errors.nofilename")); }
- else if (node.overwriteFile === "delete") {
+ if (filename === "") {
+ node.warn(RED._("file.errors.nofilename"));
+ } else if (node.overwriteFile === "delete") {
fs.unlink(filename, function (err) {
- if (err) { node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg); }
- else if (RED.settings.verbose) { node.log(RED._("file.status.deletedfile",{file:filename})); }
+ if (err) {
+ node.error(RED._("file.errors.deletefail",{error:err.toString()}),msg);
+ } else {
+ if (RED.settings.verbose) {
+ node.log(RED._("file.status.deletedfile",{file:filename}));
+ }
+ node.send(msg);
+ }
});
- }
- else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
+ } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var dir = path.dirname(filename);
if (node.createDir) {
try {
@@ -64,15 +70,21 @@ module.exports = function(RED) {
if (typeof data === "boolean") { data = data.toString(); }
if (typeof data === "number") { data = data.toString(); }
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
- node.data.push(Buffer.from(data));
+ node.data.push({msg:msg,data:Buffer.from(data)});
while (node.data.length > 0) {
if (node.overwriteFile === "true") {
- node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
- node.wstream.on("error", function(err) {
- node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
- });
- node.wstream.end(node.data.shift());
+ (function(packet) {
+ node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true });
+ node.wstream.on("error", function(err) {
+ node.error(RED._("file.errors.writefail",{error:err.toString()}),msg);
+ });
+ node.wstream.on("open", function() {
+ node.wstream.end(packet.data, function() {
+ node.send(packet.msg);
+ });
+ })
+ })(node.data.shift());
}
else {
// Append mode
@@ -115,10 +127,17 @@ module.exports = function(RED) {
}
if (node.filename) {
// Static filename - write and reuse the stream next time
- node.wstream.write(node.data.shift());
+ var packet = node.data.shift()
+ node.wstream.write(packet.data, function() {
+ node.send(packet.msg);
+ });
+
} else {
// Dynamic filename - write and close the stream
- node.wstream.end(node.data.shift());
+ var packet = node.data.shift()
+ node.wstream.end(packet.data, function() {
+ node.send(packet.msg);
+ });
delete node.wstream;
delete node.wstreamIno;
}
diff --git a/red/api/editor/locales/en-US/editor.json b/red/api/editor/locales/en-US/editor.json
index b2285f566..65681850f 100644
--- a/red/api/editor/locales/en-US/editor.json
+++ b/red/api/editor/locales/en-US/editor.json
@@ -240,6 +240,7 @@
"output": "outputs:",
"deleteSubflow": "delete subflow",
"info": "Description",
+ "category": "Category",
"format":"markdown format",
"errors": {
"noNodesSelected": "
Cannot create subflow: no nodes selected",
@@ -318,6 +319,7 @@
"noInfo": "no information available",
"filter": "filter nodes",
"search": "search modules",
+ "addCategory": "Add new...",
"label": {
"subflows": "subflows",
"input": "input",
@@ -467,8 +469,47 @@
"description": "Description",
"dependencies": "Dependencies",
"settings": "Settings",
+ "noSummaryAvailable": "No summary available",
"editDescription": "Edit project description",
"editDependencies": "Edit project dependencies",
+ "editReadme": "Edit README.md",
+ "projectSettings": {
+ "edit": "edit",
+ "none": "None",
+ "install": "install",
+ "removeFromProject": "remove from project",
+ "addToProject": "add to project",
+ "none": "None",
+ "files": "Files",
+ "flow": "Flow",
+ "credentials": "Credentials",
+ "invalidEncryptionKey": "Invalid encryption key",
+ "encryptionEnabled": "Encryption enabled",
+ "encryptionDisabled": "Encryption disabled",
+ "resetTheEncryptionKey": "Reset the encryption key:",
+ "setTheEncryptionKey": "Set the encryption key:",
+ "changeTheEncryptionKey": "Change the encryption key:",
+ "currentKey": "Current key",
+ "newKey": "New key",
+ "credentialsAlert": "This will delete all existing credentials",
+ "versionControl": "Version Control",
+ "branches": "Branches",
+ "noBranches": "No branches",
+ "deleteConfirm": "Are you sure you want to delete the local branch '__name__'? This cannot be undone.",
+ "unmergedConfirm": "The local branch '__name__' has unmerged changes that will be lost. Are you sure you want to delete it?",
+ "deleteUnmergedBranch": "Delete unmerged branch",
+ "gitRemotes": "Git remotes",
+ "addRemote": "add remote",
+ "addRemote2": "Add remote",
+ "remoteName": "Remote name",
+ "nameRule": "Must contain only A-Z 0-9 _ -",
+ "url": "URL",
+ "urlRule": "https://, ssh:// or file://",
+ "urlRule2": "Do not include the username/password in the URL",
+ "noRemotes": "No remotes",
+ "deleteRemoteConfrim": "Are you sure you want to delete the remote '__name__'?",
+ "deleteRemote": "Delete remote"
+ },
"userSettings": {
"committerDetail": "Committer Details",
"committerTip": "Leave blank to use system default",
diff --git a/red/api/editor/locales/ja/editor.json b/red/api/editor/locales/ja/editor.json
index d24dcc711..d18938123 100644
--- a/red/api/editor/locales/ja/editor.json
+++ b/red/api/editor/locales/ja/editor.json
@@ -459,8 +459,46 @@
"description": "詳細",
"dependencies": "依存関係",
"settings": "設定",
+ "noSummaryAvailable": "サマリが存在しません",
"editDescription": "プロジェクトの詳細を編集",
"editDependencies": "プロジェクトの依存関係を編集",
+ "editReadme": "README.mdを編集",
+ "projectSettings": {
+ "edit": "編集",
+ "none": "なし",
+ "install": "インストール",
+ "removeFromProject": "プロジェクトから削除",
+ "addToProject": "プロジェクトへ追加",
+ "files": "ファイル",
+ "flow": "フロー",
+ "credentials": "認証情報",
+ "invalidEncryptionKey": "不正な暗号化キー",
+ "encryptionEnabled": "暗号化が有効になっています",
+ "encryptionDisabled": "暗号化が無効になっています",
+ "setTheEncryptionKey": "暗号化キーを設定:",
+ "resetTheEncryptionKey": "暗号化キーを初期化:",
+ "changeTheEncryptionKey": "暗号化キーを変更:",
+ "currentKey": "現在のキー",
+ "newKey": "新規のキー",
+ "credentialsAlert": "既存の認証情報は全て削除されます",
+ "versionControl": "バージョン管理",
+ "branches": "ブランチ",
+ "noBranches": "ブランチなし",
+ "deleteConfirm": "本当にローカルブランチ'__name__'を削除しますか?削除すると元に戻すことはできません。",
+ "unmergedConfirm": "ローカルブランチ'__name__'にはマージされていない変更があります。この変更は削除されます。本当に削除しますか?",
+ "deleteUnmergedBranch": "マージされていないブランチを削除",
+ "gitRemotes": "Gitリモート",
+ "addRemote": "リモートを追加",
+ "addRemote2": "リモートを追加",
+ "remoteName": "リモート名",
+ "nameRule": "A-Z 0-9 _ - のみを含む",
+ "url": "URL",
+ "urlRule": "https://、ssh:// または file://",
+ "urlRule2": "URLにユーザ名、パスワードを含んではいけません",
+ "noRemotes": "リモートなし",
+ "deleteRemoteConfrim": "本当にリモート'__name__'を削除しますか?",
+ "deleteRemote": "リモートを削除"
+ },
"userSettings": {
"committerDetail": "コミッター詳細",
"committerTip": "システムのデフォルトを使用する場合、空白のままにしてください",
diff --git a/red/runtime/storage/index.js b/red/runtime/storage/index.js
index e0ef6d6b6..a2593eff1 100644
--- a/red/runtime/storage/index.js
+++ b/red/runtime/storage/index.js
@@ -25,6 +25,8 @@ var storageModule;
var settingsAvailable;
var sessionsAvailable;
+var libraryFlowsCachedResult = null;
+
function moduleSelector(aSettings) {
var toReturn;
if (aSettings.storageModule) {
@@ -156,7 +158,14 @@ var storageModuleInterface = {
if (storageModule.hasOwnProperty("getAllFlows")) {
return storageModule.getAllFlows();
} else {
- return listFlows("/");
+ if (libraryFlowsCachedResult) {
+ return Promise.resolve(libraryFlowsCachedResult);
+ } else {
+ return listFlows("/").then(function(result) {
+ libraryFlowsCachedResult = result;
+ return result;
+ });
+ }
}
},
getFlow: function(fn) {
@@ -178,6 +187,7 @@ var storageModuleInterface = {
err.code = "forbidden";
return when.reject(err);
}
+ libraryFlowsCachedResult = null;
if (storageModule.hasOwnProperty("saveFlow")) {
return storageModule.saveFlow(fn, data);
} else {
diff --git a/test/nodes/core/hardware/36-rpi-gpio_spec.js b/test/nodes/core/hardware/36-rpi-gpio_spec.js
new file mode 100644
index 000000000..c11ea254c
--- /dev/null
+++ b/test/nodes/core/hardware/36-rpi-gpio_spec.js
@@ -0,0 +1,89 @@
+/**
+ * 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 rpi = require("../../../../nodes/core/hardware/36-rpi-gpio.js");
+var helper = require("node-red-node-test-helper");
+var fs = require("fs");
+
+describe('RPI GPIO Node', function() {
+
+ before(function(done) {
+ helper.startServer(done);
+ });
+
+ after(function(done) {
+ helper.stopServer(done);
+ });
+
+ afterEach(function() {
+ helper.unload();
+ });
+
+ var checkIgnore = function(done) {
+ setTimeout(function() {
+ try {
+ var logEvents = helper.log().args.filter(function(evt) {
+ return ((evt[0].level == 30) && (evt[0].msg.indexOf("rpi-gpio")===0));
+ });
+ logEvents[0][0].should.have.a.property('msg');
+ logEvents[0][0].msg.toString().should.startWith("rpi-gpio : rpi-gpio.errors.ignorenode");
+ done();
+ } catch(err) {
+ done(err);
+ }
+ },25);
+ }
+
+ it('should load Input node', function(done) {
+ var flow = [{id:"n1", type:"rpi-gpio in", name:"rpi-gpio in" }];
+ helper.load(rpi, flow, function() {
+ var n1 = helper.getNode("n1");
+ n1.should.have.property('name', 'rpi-gpio in');
+ try {
+ var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
+ if (cpuinfo.indexOf(": BCM") === 1) {
+ done(); // It's ON a PI ... should really do more tests !
+ } else {
+ checkIgnore(done);
+ }
+ }
+ catch(e) {
+ checkIgnore(done);
+ }
+ });
+ });
+
+ it('should load Output node', function(done) {
+ var flow = [{id:"n1", type:"rpi-gpio out", name:"rpi-gpio out" }];
+ helper.load(rpi, flow, function() {
+ var n1 = helper.getNode("n1");
+ n1.should.have.property('name', 'rpi-gpio out');
+ try {
+ var cpuinfo = fs.readFileSync("/proc/cpuinfo").toString();
+ if (cpuinfo.indexOf(": BCM") === 1) {
+ done(); // It's ON a PI ... should really do more tests !
+ } else {
+ checkIgnore(done);
+ }
+ }
+ catch(e) {
+ checkIgnore(done);
+ }
+ });
+ });
+
+});