1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Fix File node check of msg.payload to close #700

Also add feature to allow creation of directory(ies) if path to file
does not exist.
This commit is contained in:
Dave Conway-Jones 2015-07-10 14:59:23 +01:00
parent e5a0f25d94
commit 60539d890b
4 changed files with 186 additions and 54 deletions

View File

@ -60,7 +60,7 @@
"Sunday" "Sunday"
], ],
"on": "on", "on": "on",
"onstart": "Inject once at start?", "onstart": "Inject once at start ?",
"tip": "<b>Note:</b> \"interval between times\" and \"at a specific time\" will use cron.<br/>See info box for details.", "tip": "<b>Note:</b> \"interval between times\" and \"at a specific time\" will use cron.<br/>See info box for details.",
"success": "Successfully injected: __label__", "success": "Successfully injected: __label__",
"errors": { "errors": {
@ -96,7 +96,7 @@
"placeholder": { "placeholder": {
"extraparams": "extra input parameters" "extraparams": "extra input parameters"
}, },
"spawn": "Use spawn() instead of exec()?", "spawn": "Use spawn() instead of exec() ?",
"tip": "Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated." "tip": "Tip: <i>spawn</i> expects only one command word - and appended args to be comma separated."
}, },
"function": { "function": {
@ -210,7 +210,7 @@
"return": "Return" "return": "Return"
}, },
"setby": "- set by msg.method -", "setby": "- set by msg.method -",
"basicauth": "Use basic authentication?", "basicauth": "Use basic authentication ?",
"utf8": "a UTF-8 string", "utf8": "a UTF-8 string",
"binary": "a binary buffer", "binary": "a binary buffer",
"json": "a parsed JSON object", "json": "a parsed JSON object",
@ -269,8 +269,8 @@
"host": "at host", "host": "at host",
"payload": "payload(s)", "payload": "payload(s)",
"delimited": "delimited by", "delimited": "delimited by",
"close-connection": "Close connection after each message is sent?", "close-connection": "Close connection after each message is sent ?",
"decode-base64": "Decode Base64 message?", "decode-base64": "Decode Base64 message ?",
"server": "Server", "server": "Server",
"return": "Return" "return": "Return"
}, },
@ -326,7 +326,7 @@
"send": "Send a", "send": "Send a",
"toport": "to port", "toport": "to port",
"address": "Address", "address": "Address",
"decode-base64": "Decode Base64 encoded payload?" "decode-base64": "Decode Base64 encoded payload ?"
}, },
"placeholder": { "placeholder": {
"interface": "(optional) ip address of eth0", "interface": "(optional) ip address of eth0",
@ -418,7 +418,7 @@
"resultrange": "to the result range", "resultrange": "to the result range",
"from": "from", "from": "from",
"to": "to", "to": "to",
"roundresult": "Round result to the nearest integer?" "roundresult": "Round result to the nearest integer ?"
}, },
"placeholder": { "placeholder": {
"min": "e.g. 0", "min": "e.g. 0",
@ -509,9 +509,9 @@
"gpiopin": "GPIO Pin", "gpiopin": "GPIO Pin",
"selectpin": "select pin", "selectpin": "select pin",
"resistor": "Resistor?", "resistor": "Resistor?",
"readinitial": "Read initial state of pin on deploy/restart?", "readinitial": "Read initial state of pin on deploy/restart ?",
"type": "Type", "type": "Type",
"initpin": "Initialise pin state?", "initpin": "Initialise pin state ?",
"button": "Button", "button": "Button",
"pimouse": "Pi Mouse", "pimouse": "Pi Mouse",
"left": "Left", "left": "Left",
@ -571,7 +571,7 @@
"tail": { "tail": {
"label": { "label": {
"filename": "Filename", "filename": "Filename",
"splitlines": "Split lines on \\n?" "splitlines": "Split lines on \\n ?"
}, },
"errors": { "errors": {
"windowsnotsupport": "Not currently supported on Windows." "windowsnotsupport": "Not currently supported on Windows."
@ -581,7 +581,8 @@
"label": { "label": {
"filename": "Filename", "filename": "Filename",
"action": "Action", "action": "Action",
"addnewline": "Add newline (\\n) to each payload?", "addnewline": "Add newline (\\n) to each payload ?",
"createdir": "Create directory if it doesn't exist ?",
"outputas": "Output as", "outputas": "Output as",
"filelabel": "file", "filelabel": "file",
"deletelabel": "delete __file__" "deletelabel": "delete __file__"
@ -605,7 +606,8 @@
"invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.", "invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.",
"deletefail": "failed to delete file: __error__", "deletefail": "failed to delete file: __error__",
"writefail": "failed to write to file: __error__", "writefail": "failed to write to file: __error__",
"appendfail": "failed to append to file: __error__" "appendfail": "failed to append to file: __error__",
"createfail": "failed to create file: __error__"
} }
} }
} }

View File

@ -29,9 +29,14 @@
</div> </div>
<div class="form-row" id="node-appline"> <div class="form-row" id="node-appline">
<label>&nbsp;</label> <label>&nbsp;</label>
<input type="checkbox" id="node-input-appendNewline" placeholder="Name" style="display: inline-block; width: auto; vertical-align: top;"> <input type="checkbox" id="node-input-appendNewline" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-appendNewline" style="width: 70%;"><span data-i18n="file.label.addnewline"></span></label> <label for="node-input-appendNewline" style="width: 70%;"><span data-i18n="file.label.addnewline"></span></label>
</div> </div>
<div class="form-row">
<label>&nbsp;</label>
<input type="checkbox" id="node-input-createDir" style="display: inline-block; width: auto; vertical-align: top;">
<label for="node-input-createDir" style="width: 70%;"><span data-i18n="file.label.createdir"></span></label>
</div>
<div class="form-row"> <div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label> <label for="node-input-name"><i class="fa fa-tag"></i> <span data-i18n="common.label.name"></span></label>
<input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name"> <input type="text" id="node-input-name" data-i18n="[placeholder]common.label.name">
@ -76,6 +81,7 @@
name: {value:""}, name: {value:""},
filename: {value:""}, filename: {value:""},
appendNewline: {value:true}, appendNewline: {value:true},
createDir: {value:false},
overwriteFile: {value:"false"} overwriteFile: {value:"false"}
}, },
color:"BurlyWood", color:"BurlyWood",

View File

@ -1,5 +1,5 @@
/** /**
* Copyright 2013, 2014 IBM Corp. * Copyright 2013, 2015 IBM Corp.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,37 +17,48 @@
module.exports = function(RED) { module.exports = function(RED) {
"use strict"; "use strict";
var fs = require("fs-extra"); var fs = require("fs-extra");
var os = require("os");
function FileNode(n) { function FileNode(n) {
RED.nodes.createNode(this,n); RED.nodes.createNode(this,n);
this.filename = n.filename; this.filename = n.filename;
this.appendNewline = n.appendNewline; this.appendNewline = n.appendNewline;
this.overwriteFile = n.overwriteFile.toString(); this.overwriteFile = n.overwriteFile.toString();
this.createDir = n.createDir || false;
var node = this; var node = this;
this.on("input",function(msg) { this.on("input",function(msg) {
var filename = this.filename || msg.filename || ""; var filename = node.filename || msg.filename || "";
if (msg.filename && n.filename && (n.filename !== msg.filename)) { if (!node.filename) {
node.warn(RED._("common.errors.nooverride"));
}
if (!this.filename) {
node.status({fill:"grey",shape:"dot",text:filename}); node.status({fill:"grey",shape:"dot",text:filename});
} }
if (filename === "") { if (filename === "") {
node.warn(RED._("file.errors.nofilename")); node.warn(RED._("file.errors.nofilename"));
} else if (msg.hasOwnProperty('delete')) { // remove warning at some point in future } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
node.warn(RED._("file.errors.invaliddelete"));
} else if (msg.payload && (typeof msg.payload != "undefined")) {
var data = msg.payload; var data = msg.payload;
if ((typeof data === "object")&&(!Buffer.isBuffer(data))) { if ((typeof data === "object") && (!Buffer.isBuffer(data))) {
data = JSON.stringify(data); data = JSON.stringify(data);
} }
if (typeof data === "boolean") { data = data.toString(); } if (typeof data === "boolean") { data = data.toString(); }
if ((this.appendNewline)&&(!Buffer.isBuffer(data))) { data += "\n"; } if (typeof data === "number") { data = data.toString(); }
if ((this.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL; }
if (this.overwriteFile === "true") { if (this.overwriteFile === "true") {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
fs.writeFile(filename, data, "binary", function (err) {
//fs.writeFile(filename, data, {encoding:"binary"}, function (err) { //fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
if (err) {
if ((err.code === "ENOENT") && node.createDir) {
fs.ensureFile(filename, function (err) {
if (err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); }
else {
fs.writeFile(filename, data, "binary", function (err) { fs.writeFile(filename, data, "binary", function (err) {
if (err) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); } if (err) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
});
}
});
}
else { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
}
else if (RED.settings.verbose) { node.log(RED._("file.status.wrotefile",{file:filename})); } else if (RED.settings.verbose) { node.log(RED._("file.status.wrotefile",{file:filename})); }
}); });
} }
@ -59,9 +70,21 @@ module.exports = function(RED) {
} }
else { else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer // using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
fs.appendFile(filename, data, "binary", function (err) {
//fs.appendFile(filename, data, {encoding:"binary"}, function (err) { //fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
if (err) {
if ((err.code === "ENOENT") && node.createDir) {
fs.ensureFile(filename, function (err) {
if (err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); }
else {
fs.appendFile(filename, data, "binary", function (err) { fs.appendFile(filename, data, "binary", function (err) {
if (err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); } if (err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
});
}
});
}
else { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
}
else if (RED.settings.verbose) { node.log(RED._("file.status.appendedfile",{file:filename})); } else if (RED.settings.verbose) { node.log(RED._("file.status.appendedfile",{file:filename})); }
}); });
} }
@ -82,11 +105,8 @@ module.exports = function(RED) {
options['encoding'] = this.format; options['encoding'] = this.format;
} }
this.on("input",function(msg) { this.on("input",function(msg) {
var filename = this.filename || msg.filename || ""; var filename = node.filename || msg.filename || "";
if (msg.filename && n.filename && (n.filename !== msg.filename)) { if (!node.filename) {
node.warn(RED._("file.errors.nooverride"));
}
if (!this.filename) {
node.status({fill:"grey",shape:"dot",text:filename}); node.status({fill:"grey",shape:"dot",text:filename});
} }
if (filename === "") { if (filename === "") {

View File

@ -240,6 +240,109 @@ describe('file Nodes', function() {
}); });
}); });
it('should fail to create a new directory if not asked to do so (append)', function(done) {
// Stub file write so we can make writes fail
var fileToTest2 = path.join(resourcesDir,"a","50-file-test-file.txt");
//var spy = sinon.stub(fs, 'appendFile', function(arg,arg2,arg3,arg4){ arg4(new Error("Stub error message")); });
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest2, "appendNewline":true, "overwriteFile":false}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "file";
});
//console.log(logEvents);
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("file.errors.appendfail");
done();
}
catch(e) { done(e); }
//finally { fs.appendFile.restore(); }
},wait);
n1.receive({payload:"test2"});
});
});
it('should try to create a new directory if asked to do so (append)', function(done) {
// Stub file write so we can make writes fail
var fileToTest2 = path.join(resourcesDir,"a","50-file-test-file.txt");
var spy = sinon.stub(fs, "ensureFile", function(arg1,arg2,arg3,arg4) { arg2(null); });
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest2, "appendNewline":true, "overwriteFile":false, "createDir":true}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "file";
});
//console.log(logEvents);
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("file.errors.appendfail");
done();
}
catch(e) { done(e); }
finally { fs.ensureFile.restore(); }
},wait);
n1.receive({payload:"test2"});
});
});
it('should fail to create a new directory if not asked to do so (overwrite)', function(done) {
// Stub file write so we can make writes fail
var fileToTest2 = path.join(resourcesDir,"a","50-file-test-file.txt");
//var spy = sinon.stub(fs, 'appendFile', function(arg,arg2,arg3,arg4){ arg4(new Error("Stub error message")); });
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest2, "appendNewline":false, "overwriteFile":true}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "file";
});
//console.log(logEvents);
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("file.errors.writefail");
done();
}
catch(e) { done(e); }
//finally { fs.appendFile.restore(); }
},wait);
n1.receive({payload:"test2"});
});
});
it('should try to create a new directory if asked to do so (overwrite)', function(done) {
// Stub file write so we can make writes fail
var fileToTest2 = path.join(resourcesDir,"a","50-file-test-file.txt");
var spy = sinon.stub(fs, "ensureFile", function(arg1,arg2,arg3,arg4){ arg2(null); });
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest2, "appendNewline":true, "overwriteFile":true, "createDir":true}];
helper.load(fileNode, flow, function() {
var n1 = helper.getNode("fileNode1");
setTimeout(function() {
try {
var logEvents = helper.log().args.filter(function(evt) {
return evt[0].type == "file";
});
//console.log(logEvents);
logEvents.should.have.length(1);
logEvents[0][0].should.have.a.property('msg');
logEvents[0][0].msg.toString().should.startWith("file.errors.writefail");
done();
}
catch(e) { done(e); }
finally { fs.ensureFile.restore(); }
},wait);
n1.receive({payload:"test2"});
});
});
}); });
@ -303,27 +406,28 @@ describe('file Nodes', function() {
//}); //});
//}); //});
it('should warn if msg.props try to overide', function(done) { // Commented out as we no longer need to warn of the very old deprecated behaviour
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]}, //it('should warn if msg.props try to overide', function(done) {
{id:"n2", type:"helper"}]; //var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]},
helper.load(fileNode, flow, function() { //{id:"n2", type:"helper"}];
var n1 = helper.getNode("fileInNode1"); //helper.load(fileNode, flow, function() {
var n2 = helper.getNode("n2"); //var n1 = helper.getNode("fileInNode1");
n2.on("input", function(msg) { //var n2 = helper.getNode("n2");
msg.should.have.property('payload'); //n2.on("input", function(msg) {
msg.payload.should.have.length(39).and.be.a.Buffer; //msg.should.have.property('payload');
msg.payload.toString().should.equal("File message line 1\File message line 2\n"); //msg.payload.should.have.length(39).and.be.a.Buffer;
var logEvents = helper.log().args.filter(function(evt) { //msg.payload.toString().should.equal("File message line 1\File message line 2\n");
return evt[0].type == "file in"; //var logEvents = helper.log().args.filter(function(evt) {
}); //return evt[0].type == "file in";
logEvents.should.have.length(1); //});
logEvents[0][0].should.have.a.property('msg'); //logEvents.should.have.length(1);
logEvents[0][0].msg.toString().should.startWith("file.errors.nooverride"); //logEvents[0][0].should.have.a.property('msg');
done(); //logEvents[0][0].msg.toString().should.startWith("file.errors.nooverride");
}); //done();
n1.receive({payload:"",filename:"foo.txt"}); //});
}); //n1.receive({payload:"",filename:"foo.txt"});
}); //});
//});
it('should warn if no filename set', function(done) { it('should warn if no filename set', function(done) {
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "format":""}]; var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "format":""}];