Skip to content

Commit 2aced89

Browse files
committed
File node: recreate write stream when file deleted
Fixes node-red#1351
1 parent f0373cd commit 2aced89

2 files changed

Lines changed: 146 additions & 10 deletions

File tree

nodes/core/storage/50-file.js

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,53 @@ module.exports = function(RED) {
7272
node.wstream.end(node.data.shift());
7373
}
7474
else {
75-
if ((!node.wstream) || (!node.filename)) {
75+
// Append mode
76+
var recreateStream = !node.wstream || !node.filename;
77+
if (node.wstream && node.wstreamIno) {
78+
// There is already a stream open and we have the inode
79+
// of the file. Check the file hasn't been deleted
80+
// or deleted and recreated.
81+
try {
82+
var stat = fs.statSync(filename);
83+
// File exists - check the inode matches
84+
if (stat.ino !== node.wstreamIno) {
85+
// The file has been recreated. Close the current
86+
// stream and recreate it
87+
recreateStream = true;
88+
node.wstream.end();
89+
delete node.wstream;
90+
delete node.wstreamIno;
91+
}
92+
} catch(err) {
93+
// File does not exist
94+
recreateStream = true;
95+
node.wstream.end();
96+
delete node.wstream;
97+
delete node.wstreamIno;
98+
}
99+
}
100+
if (recreateStream) {
76101
node.wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'a', autoClose:true });
102+
node.wstream.on("open", function(fd) {
103+
try {
104+
var stat = fs.statSync(filename);
105+
node.wstreamIno = stat.ino;
106+
} catch(err) {
107+
}
108+
});
77109
node.wstream.on("error", function(err) {
78110
node.error(RED._("file.errors.appendfail",{error:err.toString()}),msg);
79111
});
80112
}
81-
if (node.filename) { node.wstream.write(node.data.shift()); }
82-
else { node.wstream.end(node.data.shift()); }
113+
if (node.filename) {
114+
// Static filename - write and reuse the stream next time
115+
node.wstream.write(node.data.shift());
116+
} else {
117+
// Dynamic filename - write and close the stream
118+
node.wstream.end(node.data.shift());
119+
delete node.wstream;
120+
delete node.wstreamIno;
121+
}
83122
}
84123
}
85124
}

test/nodes/core/storage/50-file_spec.js

Lines changed: 104 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ describe('file Nodes', function() {
6767

6868
it('should append to a file and add newline', function(done) {
6969
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":false}];
70+
try {
71+
fs.unlinkSync(fileToTest);
72+
}catch(err) {}
7073
helper.load(fileNode, flow, function() {
7174
var n1 = helper.getNode("fileNode1");
7275
n1.emit("input", {payload:"test2"}); // string
@@ -94,6 +97,92 @@ describe('file Nodes', function() {
9497
});
9598
});
9699

100+
it('should append to a file after it has been deleted ', function(done) {
101+
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false}];
102+
try {
103+
fs.unlinkSync(fileToTest);
104+
} catch(err) {}
105+
helper.load(fileNode, flow, function() {
106+
var n1 = helper.getNode("fileNode1");
107+
// Send two messages to the file
108+
n1.emit("input", {payload:"one"});
109+
n1.emit("input", {payload:"two"});
110+
setTimeout(function() {
111+
try {
112+
// Check they got appended as expected
113+
var f = fs.readFileSync(fileToTest).toString();
114+
f.should.equal("onetwo");
115+
116+
// Delete the file
117+
fs.unlinkSync(fileToTest);
118+
119+
// Send two more messages to the file
120+
n1.emit("input", {payload:"three"});
121+
n1.emit("input", {payload:"four"});
122+
123+
setTimeout(function() {
124+
// Check the file was updated
125+
try {
126+
var f = fs.readFileSync(fileToTest).toString();
127+
f.should.equal("threefour");
128+
fs.unlinkSync(fileToTest);
129+
done();
130+
} catch(err) {
131+
done(err);
132+
}
133+
},wait);
134+
} catch(err) {
135+
done(err);
136+
}
137+
},wait);
138+
});
139+
});
140+
141+
it('should append to a file after it has been recreated ', function(done) {
142+
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":false, "overwriteFile":false}];
143+
try {
144+
fs.unlinkSync(fileToTest);
145+
} catch(err) {}
146+
helper.load(fileNode, flow, function() {
147+
var n1 = helper.getNode("fileNode1");
148+
// Send two messages to the file
149+
n1.emit("input", {payload:"one"});
150+
n1.emit("input", {payload:"two"});
151+
setTimeout(function() {
152+
try {
153+
// Check they got appended as expected
154+
var f = fs.readFileSync(fileToTest).toString();
155+
f.should.equal("onetwo");
156+
157+
// Delete the file
158+
fs.unlinkSync(fileToTest);
159+
160+
// Recreate it
161+
fs.writeFileSync(fileToTest,"");
162+
163+
// Send two more messages to the file
164+
n1.emit("input", {payload:"three"});
165+
n1.emit("input", {payload:"four"});
166+
167+
setTimeout(function() {
168+
// Check the file was updated
169+
try {
170+
var f = fs.readFileSync(fileToTest).toString();
171+
f.should.equal("threefour");
172+
fs.unlinkSync(fileToTest);
173+
done();
174+
} catch(err) {
175+
done(err);
176+
}
177+
},wait);
178+
} catch(err) {
179+
done(err);
180+
}
181+
},wait);
182+
});
183+
});
184+
185+
97186
it('should use msg.filename if filename not set in node', function(done) {
98187
var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true}];
99188
helper.load(fileNode, flow, function() {
@@ -407,7 +496,8 @@ describe('file Nodes', function() {
407496
var n2 = helper.getNode("n2");
408497
n2.on("input", function(msg) {
409498
msg.should.have.property('payload');
410-
msg.payload.should.have.length(40).and.be.a.Buffer;
499+
Buffer.isBuffer(msg.payload).should.be.true();
500+
msg.payload.should.have.length(40);
411501
msg.payload.toString().should.equal('File message line 1\nFile message line 2\n');
412502
done();
413503
});
@@ -422,10 +512,15 @@ describe('file Nodes', function() {
422512
var n1 = helper.getNode("fileInNode1");
423513
var n2 = helper.getNode("n2");
424514
n2.on("input", function(msg) {
425-
msg.should.have.property('payload');
426-
msg.payload.should.have.length(40).and.be.a.string;
427-
msg.payload.should.equal("File message line 1\nFile message line 2\n");
428-
done();
515+
try {
516+
msg.should.have.property('payload');
517+
msg.payload.should.be.a.String();
518+
msg.payload.should.have.length(40)
519+
msg.payload.should.equal("File message line 1\nFile message line 2\n");
520+
done();
521+
} catch(err) {
522+
done(err);
523+
}
429524
});
430525
n1.receive({payload:""});
431526
});
@@ -440,7 +535,8 @@ describe('file Nodes', function() {
440535
var c = 0;
441536
n2.on("input", function(msg) {
442537
msg.should.have.property('payload');
443-
msg.payload.should.have.length(19).and.be.a.string;
538+
msg.payload.should.be.a.String();
539+
msg.payload.should.have.length(19);
444540
if (c === 0) {
445541
msg.payload.should.equal("File message line 1");
446542
c++;
@@ -466,7 +562,8 @@ describe('file Nodes', function() {
466562
var n2 = helper.getNode("n2");
467563
n2.on("input", function(msg) {
468564
msg.should.have.property('payload');
469-
msg.payload.should.have.length(40).and.be.a.Buffer;
565+
Buffer.isBuffer(msg.payload).should.be.true();
566+
msg.payload.should.have.length(40);
470567
msg.should.have.property('parts');
471568
msg.parts.should.have.property('count',1);
472569
msg.parts.should.have.property('type','buffer');

0 commit comments

Comments
 (0)