diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json
index 96b3ddd7c..d50a2b029 100644
--- a/nodes/core/locales/en-US/messages.json
+++ b/nodes/core/locales/en-US/messages.json
@@ -60,7 +60,7 @@
"Sunday"
],
"on": "on",
- "onstart": "Inject once at start?",
+ "onstart": "Inject once at start ?",
"tip": "Note: \"interval between times\" and \"at a specific time\" will use cron.
See info box for details.",
"success": "Successfully injected: __label__",
"errors": {
@@ -96,7 +96,7 @@
"placeholder": {
"extraparams": "extra input parameters"
},
- "spawn": "Use spawn() instead of exec()?",
+ "spawn": "Use spawn() instead of exec() ?",
"tip": "Tip: spawn expects only one command word - and appended args to be comma separated."
},
"function": {
@@ -210,7 +210,7 @@
"return": "Return"
},
"setby": "- set by msg.method -",
- "basicauth": "Use basic authentication?",
+ "basicauth": "Use basic authentication ?",
"utf8": "a UTF-8 string",
"binary": "a binary buffer",
"json": "a parsed JSON object",
@@ -269,8 +269,8 @@
"host": "at host",
"payload": "payload(s)",
"delimited": "delimited by",
- "close-connection": "Close connection after each message is sent?",
- "decode-base64": "Decode Base64 message?",
+ "close-connection": "Close connection after each message is sent ?",
+ "decode-base64": "Decode Base64 message ?",
"server": "Server",
"return": "Return"
},
@@ -326,7 +326,7 @@
"send": "Send a",
"toport": "to port",
"address": "Address",
- "decode-base64": "Decode Base64 encoded payload?"
+ "decode-base64": "Decode Base64 encoded payload ?"
},
"placeholder": {
"interface": "(optional) ip address of eth0",
@@ -418,7 +418,7 @@
"resultrange": "to the result range",
"from": "from",
"to": "to",
- "roundresult": "Round result to the nearest integer?"
+ "roundresult": "Round result to the nearest integer ?"
},
"placeholder": {
"min": "e.g. 0",
@@ -509,9 +509,9 @@
"gpiopin": "GPIO Pin",
"selectpin": "select pin",
"resistor": "Resistor?",
- "readinitial": "Read initial state of pin on deploy/restart?",
+ "readinitial": "Read initial state of pin on deploy/restart ?",
"type": "Type",
- "initpin": "Initialise pin state?",
+ "initpin": "Initialise pin state ?",
"button": "Button",
"pimouse": "Pi Mouse",
"left": "Left",
@@ -571,7 +571,7 @@
"tail": {
"label": {
"filename": "Filename",
- "splitlines": "Split lines on \\n?"
+ "splitlines": "Split lines on \\n ?"
},
"errors": {
"windowsnotsupport": "Not currently supported on Windows."
@@ -581,7 +581,8 @@
"label": {
"filename": "Filename",
"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",
"filelabel": "file",
"deletelabel": "delete __file__"
@@ -605,7 +606,8 @@
"invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.",
"deletefail": "failed to delete 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__"
}
}
}
diff --git a/nodes/core/storage/50-file.html b/nodes/core/storage/50-file.html
index 3cb12dd96..3de507688 100644
--- a/nodes/core/storage/50-file.html
+++ b/nodes/core/storage/50-file.html
@@ -29,9 +29,14 @@
-
+
+
+
+
+
+
@@ -76,6 +81,7 @@
name: {value:""},
filename: {value:""},
appendNewline: {value:true},
+ createDir: {value:false},
overwriteFile: {value:"false"}
},
color:"BurlyWood",
diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js
index 910c3b4ea..4f4c82d74 100644
--- a/nodes/core/storage/50-file.js
+++ b/nodes/core/storage/50-file.js
@@ -1,5 +1,5 @@
/**
- * Copyright 2013, 2014 IBM Corp.
+ * Copyright 2013, 2015 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,37 +17,48 @@
module.exports = function(RED) {
"use strict";
var fs = require("fs-extra");
+ var os = require("os");
function FileNode(n) {
RED.nodes.createNode(this,n);
this.filename = n.filename;
this.appendNewline = n.appendNewline;
this.overwriteFile = n.overwriteFile.toString();
+ this.createDir = n.createDir || false;
var node = this;
+
this.on("input",function(msg) {
- var filename = this.filename || msg.filename || "";
- if (msg.filename && n.filename && (n.filename !== msg.filename)) {
- node.warn(RED._("common.errors.nooverride"));
- }
- if (!this.filename) {
+ var filename = node.filename || msg.filename || "";
+ if (!node.filename) {
node.status({fill:"grey",shape:"dot",text:filename});
}
if (filename === "") {
node.warn(RED._("file.errors.nofilename"));
- } else if (msg.hasOwnProperty('delete')) { // remove warning at some point in future
- node.warn(RED._("file.errors.invaliddelete"));
- } else if (msg.payload && (typeof msg.payload != "undefined")) {
+ } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) {
var data = msg.payload;
- if ((typeof data === "object")&&(!Buffer.isBuffer(data))) {
+ if ((typeof data === "object") && (!Buffer.isBuffer(data))) {
data = JSON.stringify(data);
}
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") {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while
- //fs.writeFile(filename, data, {encoding:"binary"}, function (err) {
fs.writeFile(filename, data, "binary", function (err) {
- if (err) { node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); }
+ //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) {
+ 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})); }
});
}
@@ -59,9 +70,21 @@ module.exports = function(RED) {
}
else {
// using "binary" not {encoding:"binary"} to be 0.8 compatible for a while longer
- //fs.appendFile(filename, data, {encoding:"binary"}, function (err) {
fs.appendFile(filename, data, "binary", function (err) {
- if (err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }
+ //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) {
+ 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})); }
});
}
@@ -82,11 +105,8 @@ module.exports = function(RED) {
options['encoding'] = this.format;
}
this.on("input",function(msg) {
- var filename = this.filename || msg.filename || "";
- if (msg.filename && n.filename && (n.filename !== msg.filename)) {
- node.warn(RED._("file.errors.nooverride"));
- }
- if (!this.filename) {
+ var filename = node.filename || msg.filename || "";
+ if (!node.filename) {
node.status({fill:"grey",shape:"dot",text:filename});
}
if (filename === "") {
diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js
index 9b1b34860..ae1bff84d 100644
--- a/test/nodes/core/storage/50-file_spec.js
+++ b/test/nodes/core/storage/50-file_spec.js
@@ -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) {
- var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]},
- {id:"n2", type:"helper"}];
- helper.load(fileNode, flow, function() {
- var n1 = helper.getNode("fileInNode1");
- var n2 = helper.getNode("n2");
- n2.on("input", function(msg) {
- msg.should.have.property('payload');
- msg.payload.should.have.length(39).and.be.a.Buffer;
- msg.payload.toString().should.equal("File message line 1\File message line 2\n");
- 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[0][0].msg.toString().should.startWith("file.errors.nooverride");
- done();
- });
- n1.receive({payload:"",filename:"foo.txt"});
- });
- });
+// Commented out as we no longer need to warn of the very old deprecated behaviour
+ //it('should warn if msg.props try to overide', function(done) {
+ //var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]},
+ //{id:"n2", type:"helper"}];
+ //helper.load(fileNode, flow, function() {
+ //var n1 = helper.getNode("fileInNode1");
+ //var n2 = helper.getNode("n2");
+ //n2.on("input", function(msg) {
+ //msg.should.have.property('payload');
+ //msg.payload.should.have.length(39).and.be.a.Buffer;
+ //msg.payload.toString().should.equal("File message line 1\File message line 2\n");
+ //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[0][0].msg.toString().should.startWith("file.errors.nooverride");
+ //done();
+ //});
+ //n1.receive({payload:"",filename:"foo.txt"});
+ //});
+ //});
it('should warn if no filename set', function(done) {
var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "format":""}];