diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 7783dffe6..86795796e 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -72,14 +72,53 @@ module.exports = function(RED) { node.wstream.end(node.data.shift()); } else { - if ((!node.wstream) || (!node.filename)) { + // Append mode + var recreateStream = !node.wstream || !node.filename; + if (node.wstream && node.wstreamIno) { + // There is already a stream open and we have the inode + // of the file. Check the file hasn't been deleted + // or deleted and recreated. + try { + var stat = fs.statSync(filename); + // File exists - check the inode matches + if (stat.ino !== node.wstreamIno) { + // The file has been recreated. Close the current + // stream and recreate it + recreateStream = true; + node.wstream.end(); + delete node.wstream; + delete node.wstreamIno; + } + } catch(err) { + // File does not exist + recreateStream = true; + node.wstream.end(); + delete node.wstream; + delete node.wstreamIno; + } + } + if (recreateStream) { node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true }); + node.wstream.on("open", function(fd) { + try { + var stat = fs.statSync(filename); + node.wstreamIno = stat.ino; + } catch(err) { + } + }); node.wstream.on("error", function(err) { node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg); }); } - if (node.filename) { node.wstream.write(node.data.shift()); } - else { node.wstream.end(node.data.shift()); } + if (node.filename) { + // Static filename - write and reuse the stream next time + node.wstream.write(node.data.shift()); + } else { + // Dynamic filename - write and close the stream + node.wstream.end(node.data.shift()); + delete node.wstream; + delete node.wstreamIno; + } } } } diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index 63bb70a6b..278bfda8c 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -67,6 +67,9 @@ describe('file Nodes', function() { it('should append to a file and add newline', function(done) { var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}]; + try { + fs.unlinkSync(fileToTest); + }catch(err) {} helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileNode1"); n1.emit("input", {payload:"test2"}); // string @@ -94,6 +97,92 @@ describe('file Nodes', function() { }); }); + it('should append to a file after it has been deleted ', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false}]; + try { + fs.unlinkSync(fileToTest); + } catch(err) {} + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + // Send two messages to the file + n1.emit("input", {payload:"one"}); + n1.emit("input", {payload:"two"}); + setTimeout(function() { + try { + // Check they got appended as expected + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("onetwo"); + + // Delete the file + fs.unlinkSync(fileToTest); + + // Send two more messages to the file + n1.emit("input", {payload:"three"}); + n1.emit("input", {payload:"four"}); + + setTimeout(function() { + // Check the file was updated + try { + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("threefour"); + fs.unlinkSync(fileToTest); + done(); + } catch(err) { + done(err); + } + },wait); + } catch(err) { + done(err); + } + },wait); + }); + }); + + it('should append to a file after it has been recreated ', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false}]; + try { + fs.unlinkSync(fileToTest); + } catch(err) {} + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + // Send two messages to the file + n1.emit("input", {payload:"one"}); + n1.emit("input", {payload:"two"}); + setTimeout(function() { + try { + // Check they got appended as expected + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("onetwo"); + + // Delete the file + fs.unlinkSync(fileToTest); + + // Recreate it + fs.writeFileSync(fileToTest,""); + + // Send two more messages to the file + n1.emit("input", {payload:"three"}); + n1.emit("input", {payload:"four"}); + + setTimeout(function() { + // Check the file was updated + try { + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("threefour"); + fs.unlinkSync(fileToTest); + done(); + } catch(err) { + done(err); + } + },wait); + } catch(err) { + done(err); + } + },wait); + }); + }); + + it('should use msg.filename if filename not set in node', function(done) { var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true}]; helper.load(fileNode, flow, function() { @@ -407,7 +496,8 @@ describe('file Nodes', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload'); - msg.payload.should.have.length(40).and.be.a.Buffer; + Buffer.isBuffer(msg.payload).should.be.true(); + msg.payload.should.have.length(40); msg.payload.toString().should.equal('File message line 1\nFile message line 2\n'); done(); }); @@ -422,10 +512,15 @@ describe('file Nodes', 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(40).and.be.a.string; - msg.payload.should.equal("File message line 1\nFile message line 2\n"); - done(); + try { + msg.should.have.property('payload'); + msg.payload.should.be.a.String(); + msg.payload.should.have.length(40) + msg.payload.should.equal("File message line 1\nFile message line 2\n"); + done(); + } catch(err) { + done(err); + } }); n1.receive({payload:""}); }); @@ -440,7 +535,8 @@ describe('file Nodes', function() { var c = 0; n2.on("input", function(msg) { msg.should.have.property('payload'); - msg.payload.should.have.length(19).and.be.a.string; + msg.payload.should.be.a.String(); + msg.payload.should.have.length(19); if (c === 0) { msg.payload.should.equal("File message line 1"); c++; @@ -466,7 +562,8 @@ describe('file Nodes', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('payload'); - msg.payload.should.have.length(40).and.be.a.Buffer; + Buffer.isBuffer(msg.payload).should.be.true(); + msg.payload.should.have.length(40); msg.should.have.property('parts'); msg.parts.should.have.property('count',1); msg.parts.should.have.property('type','buffer');