From 1a226c4dc6a6da2ee91ee3d6fbedbb8d98110ada Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 21 Sep 2018 21:07:44 +0900 Subject: [PATCH 1/7] fix multiple input message processing of file node --- nodes/core/storage/50-file.js | 32 ++++++++++++++++-- test/nodes/core/storage/50-file_spec.js | 43 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 76cedb136..abe591c9d 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -29,8 +29,9 @@ module.exports = function(RED) { var node = this; node.wstream = null; node.data = []; + node.msgQueue = []; - this.on("input",function(msg) { + function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; if ((!node.filename) && (!node.tout)) { node.tout = setTimeout(function() { @@ -41,6 +42,7 @@ module.exports = function(RED) { } if (filename === "") { node.warn(RED._("file.errors.nofilename")); + done(); } else if (node.overwriteFile === "delete") { fs.unlink(filename, function (err) { if (err) { @@ -51,6 +53,7 @@ module.exports = function(RED) { } node.send(msg); } + done(); }); } else if (msg.hasOwnProperty("payload") && (typeof msg.payload !== "undefined")) { var dir = path.dirname(filename); @@ -59,6 +62,7 @@ module.exports = function(RED) { fs.ensureDirSync(dir); } catch(err) { node.error(RED._("file.errors.createfail",{error:err.toString()}),msg); + done(); return; } } @@ -82,6 +86,7 @@ module.exports = function(RED) { node.wstream.on("open", function() { node.wstream.end(packet.data, function() { node.send(packet.msg); + done(); }); }) })(node.data.shift()); @@ -130,6 +135,7 @@ module.exports = function(RED) { var packet = node.data.shift() node.wstream.write(packet.data, function() { node.send(packet.msg); + done(); }); } else { @@ -137,14 +143,34 @@ module.exports = function(RED) { var packet = node.data.shift() node.wstream.end(packet.data, function() { node.send(packet.msg); + delete node.wstream; + delete node.wstreamIno; + done(); }); - delete node.wstream; - delete node.wstreamIno; } } } } + } + + this.on("input", function(msg) { + var msgQueue = node.msgQueue; + function processQ() { + var msg = msgQueue[0]; + processMsg(msg, function() { + msgQueue.shift(); + if (msgQueue.length > 0) { + processQ(); + } + }); + } + if (msgQueue.push(msg) > 1) { + // pending write exists + return; + } + processQ(); }); + this.on('close', function() { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index e61c3fb68..cba22cd54 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -540,6 +540,49 @@ describe('file Nodes', function() { }); }); + it('should write to multiple files', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, "createDir":true, wires: [["helperNode1"]]}, + {id:"helperNode1", type:"helper"}]; + var tmp_path = path.join(resourcesDir, "tmp"); + var len = 1024*1024*10; + var file_count = 5; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + var n2 = helper.getNode("helperNode1"); + var count = 0; + n2.on("input", function(msg) { + try { + count++; + if (count == file_count) { + for(var i = 0; i < file_count; i++) { + var name = path.join(tmp_path, String(i)); + var f = fs.readFileSync(name); + f.should.have.length(len); + f[0].should.have.equal(i); + } + fs.removeSync(tmp_path); + done(); + } + } + catch (e) { + try { + fs.removeSync(tmp_path); + } + catch (e1) { + } + done(e); + } + }); + for(var i = 0; i < file_count; i++) { + var data = new Buffer(len); + data.fill(i); + var name = path.join(tmp_path, String(i)); + var msg = {payload:data, filename:name}; + n1.receive(msg); + } + }); + }); + }); From 61681bb1d613e1913986cdc591c7816fcfc600d9 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Fri, 21 Sep 2018 23:02:45 +0900 Subject: [PATCH 2/7] lift processQ function --- nodes/core/storage/50-file.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index abe591c9d..70d9984af 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -153,22 +153,23 @@ module.exports = function(RED) { } } + function processQ(queue) { + var msg = queue[0]; + processMsg(msg, function() { + queue.shift(); + if (queue.length > 0) { + processQ(queue); + } + }); + } + this.on("input", function(msg) { var msgQueue = node.msgQueue; - function processQ() { - var msg = msgQueue[0]; - processMsg(msg, function() { - msgQueue.shift(); - if (msgQueue.length > 0) { - processQ(); - } - }); - } if (msgQueue.push(msg) > 1) { // pending write exists return; } - processQ(); + processQ(msgQueue); }); this.on('close', function() { From a345089c8b5c8c0016952150523bfbfb55bee5cf Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Wed, 26 Sep 2018 12:39:12 +0900 Subject: [PATCH 3/7] wait closing while penging messages exist --- nodes/core/storage/50-file.js | 22 ++- test/nodes/core/storage/50-file_spec.js | 202 +++++++++++++++--------- 2 files changed, 144 insertions(+), 80 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 70d9984af..853f93186 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -30,6 +30,7 @@ module.exports = function(RED) { node.wstream = null; node.data = []; node.msgQueue = []; + node.closing = false; function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; @@ -170,12 +171,31 @@ module.exports = function(RED) { return; } processQ(msgQueue); + if (node.closing) { + closeNode(); + } }); - this.on('close', function() { + function closeNode() { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } node.status({}); + node.closing = false; + } + + this.on('close', function() { + if (node.closing) { + // already closing + return; + } + node.closing = true; + if (node.msgQueue.length > 0) { + // close after queue processed + return; + } + else { + closeNode(); + } }); } RED.nodes.registerType("file",FileNode); diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index cba22cd54..b60933649 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -46,14 +46,14 @@ describe('file Nodes', function() { it('should be loaded', function(done) { var flow = [{id:"fileNode1", type:"file", name: "fileNode", "filename":fileToTest, "appendNewline":true, "overwriteFile":true}]; helper.load(fileNode, flow, function() { - try { + try { var fileNode1 = helper.getNode("fileNode1"); fileNode1.should.have.property('name', 'fileNode'); done(); - } - catch (e) { - done(e); - } + } + catch (e) { + done(e); + } }); }); @@ -64,16 +64,16 @@ describe('file Nodes', function() { var n1 = helper.getNode("fileNode1"); var n2 = helper.getNode("helperNode1"); n2.on("input", function(msg) { - try { - var f = fs.readFileSync(fileToTest); - f.should.have.length(4); - fs.unlinkSync(fileToTest); - msg.should.have.property("payload", "test"); - done(); - } - catch (e) { - done(e); - } + try { + var f = fs.readFileSync(fileToTest); + f.should.have.length(4); + fs.unlinkSync(fileToTest); + msg.should.have.property("payload", "test"); + done(); + } + catch (e) { + done(e); + } }); n1.receive({payload:"test"}); }); @@ -93,26 +93,26 @@ describe('file Nodes', function() { var data = ["test2", true, 999, [2]]; n2.on("input", function (msg) { - try { - msg.should.have.property("payload"); - data.should.containDeep([msg.payload]); - if (count === 3) { + try { + msg.should.have.property("payload"); + data.should.containDeep([msg.payload]); + if (count === 3) { var f = fs.readFileSync(fileToTest).toString(); if (os.type() !== "Windows_NT") { - f.should.have.length(19); - f.should.equal("test2\ntrue\n999\n[2]\n"); + f.should.have.length(19); + f.should.equal("test2\ntrue\n999\n[2]\n"); } else { - f.should.have.length(23); - f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n"); + f.should.have.length(23); + f.should.equal("test2\r\ntrue\r\n999\r\n[2]\r\n"); } done(); - } - count++; - } - catch (e) { - done(e); - } + } + count++; + } + catch (e) { + done(e); + } }); n1.receive({payload:"test2"}); // string @@ -142,37 +142,37 @@ describe('file Nodes', function() { var count = 0; n2.on("input", function (msg) { - try { - msg.should.have.property("payload"); - data.should.containDeep([msg.payload]); - try { + try { + msg.should.have.property("payload"); + data.should.containDeep([msg.payload]); + try { if (count === 1) { - // Check they got appended as expected - var f = fs.readFileSync(fileToTest).toString(); - f.should.equal("onetwo"); + // Check they got appended as expected + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("onetwo"); - // Delete the file - fs.unlinkSync(fileToTest); - setTimeout(function() { + // Delete the file + fs.unlinkSync(fileToTest); + setTimeout(function() { // Send two more messages to the file n1.receive({payload:"three"}); n1.receive({payload:"four"}); - }, wait); + }, wait); } if (count === 3) { - var f = fs.readFileSync(fileToTest).toString(); - f.should.equal("threefour"); - fs.unlinkSync(fileToTest); - done(); + var f = fs.readFileSync(fileToTest).toString(); + f.should.equal("threefour"); + fs.unlinkSync(fileToTest); + done(); } - } catch(err) { + } catch(err) { done(err); - } - count++; - } - catch (e) { - done(e); - } + } + count++; + } + catch (e) { + done(e); + } }); // Send two messages to the file @@ -197,7 +197,7 @@ describe('file Nodes', function() { n2.on("input", function (msg) { try { msg.should.have.property("payload"); - data.should.containDeep([msg.payload]); + data.should.containDeep([msg.payload]); if (count == 1) { // Check they got appended as expected var f = fs.readFileSync(fileToTest).toString(); @@ -256,25 +256,25 @@ describe('file Nodes', function() { var n2 = helper.getNode("helperNode1"); n2.on("input", function (msg) { - try { - msg.should.have.property("payload", "fine"); - msg.should.have.property("filename", fileToTest); + try { + msg.should.have.property("payload", "fine"); + msg.should.have.property("filename", fileToTest); - var f = fs.readFileSync(fileToTest).toString(); - if (os.type() !== "Windows_NT") { + var f = fs.readFileSync(fileToTest).toString(); + if (os.type() !== "Windows_NT") { f.should.have.length(5); f.should.equal("fine\n"); - } - else { + } + else { f.should.have.length(6); f.should.equal("fine\r\n"); - } - done(); - } - catch (e) { - done(e); - } - }); + } + done(); + } + catch (e) { + done(e); + } + }); n1.receive({payload:"fine", filename:fileToTest}); }); @@ -551,27 +551,27 @@ describe('file Nodes', function() { var n2 = helper.getNode("helperNode1"); var count = 0; n2.on("input", function(msg) { - try { + try { count++; if (count == file_count) { for(var i = 0; i < file_count; i++) { var name = path.join(tmp_path, String(i)); - var f = fs.readFileSync(name); - f.should.have.length(len); + var f = fs.readFileSync(name); + f.should.have.length(len); f[0].should.have.equal(i); } fs.removeSync(tmp_path); - done(); + done(); } - } - catch (e) { + } + catch (e) { try { fs.removeSync(tmp_path); } catch (e1) { } - done(e); - } + done(e); + } }); for(var i = 0; i < file_count; i++) { var data = new Buffer(len); @@ -583,6 +583,50 @@ describe('file Nodes', function() { }); }); + it('should write to multiple files if node is closed', function(done) { + var flow = [{id:"fileNode1", type:"file", name: "fileNode", "appendNewline":true, "overwriteFile":true, "createDir":true, wires: [["helperNode1"]]}, + {id:"helperNode1", type:"helper"}]; + var tmp_path = path.join(resourcesDir, "tmp"); + var len = 1024*1024*10; + var file_count = 5; + helper.load(fileNode, flow, function() { + var n1 = helper.getNode("fileNode1"); + var n2 = helper.getNode("helperNode1"); + var count = 0; + n2.on("input", function(msg) { + try { + count++; + if (count == file_count) { + for(var i = 0; i < file_count; i++) { + var name = path.join(tmp_path, String(i)); + var f = fs.readFileSync(name); + f.should.have.length(len); + f[0].should.have.equal(i); + } + fs.removeSync(tmp_path); + done(); + } + } + catch (e) { + try { + fs.removeSync(tmp_path); + } + catch (e1) { + } + done(e); + } + }); + for(var i = 0; i < file_count; i++) { + var data = new Buffer(len); + data.fill(i); + var name = path.join(tmp_path, String(i)); + var msg = {payload:data, filename:name}; + n1.receive(msg); + } + n1.close(); + }); + }); + }); @@ -615,7 +659,7 @@ describe('file Nodes', function() { it('should read in a file and output a buffer', function(done) { var flow = [{id:"fileInNode1", type:"file in", name:"fileInNode", "filename":fileToTest, "format":"", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -632,7 +676,7 @@ describe('file Nodes', function() { it('should read in a file and output a utf8 string', function(done) { var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", "filename":fileToTest, "format":"utf8", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -653,7 +697,7 @@ describe('file Nodes', function() { it('should read in a file and output split lines with parts', function(done) { var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -691,7 +735,7 @@ describe('file Nodes', function() { var line = data.join("\n"); fs.writeFileSync(fileToTest, line); var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"lines", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); @@ -724,7 +768,7 @@ describe('file Nodes', function() { it('should read in a file and output a buffer with parts', function(done) { var flow = [{id:"fileInNode1", type:"file in", name: "fileInNode", filename:fileToTest, format:"stream", wires:[["n2"]]}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(fileNode, flow, function() { var n1 = helper.getNode("fileInNode1"); var n2 = helper.getNode("n2"); From 7cec7ae60805aeb453da2259e76904e980260907 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Sun, 30 Sep 2018 22:30:19 +0900 Subject: [PATCH 4/7] invoke callbacks if async handler is specified --- nodes/core/storage/50-file.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 853f93186..51ffb3e58 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -31,6 +31,7 @@ module.exports = function(RED) { node.data = []; node.msgQueue = []; node.closing = false; + node.closeCallbacks = []; function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; @@ -180,10 +181,19 @@ module.exports = function(RED) { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } node.status({}); + var callbacks = node.closeCallbacks; + node.closeCallbacks = []; node.closing = false; + for (cb in callbacks) { + cb(); + } } - this.on('close', function() { + this.on('close', function(arg1, arg2) { + var cb = arg2 ? arg2 : arg1; + if (cb) { + node.closeCallbacks.push(done); + } if (node.closing) { // already closing return; From 58c8311d56142ee4faff84f74953349399204777 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Tue, 2 Oct 2018 20:37:30 +0900 Subject: [PATCH 5/7] make close handler argument only one --- nodes/core/storage/50-file.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 51ffb3e58..43195901f 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -189,8 +189,7 @@ module.exports = function(RED) { } } - this.on('close', function(arg1, arg2) { - var cb = arg2 ? arg2 : arg1; + this.on('close', function(cb) { if (cb) { node.closeCallbacks.push(done); } From 144104245818679711f0a1509cc4dccd450e39a7 Mon Sep 17 00:00:00 2001 From: Hiroyasu Nishiyama Date: Wed, 3 Oct 2018 21:29:28 +0900 Subject: [PATCH 6/7] update close & input handling of File node --- nodes/core/storage/50-file.js | 166 ++++++++++++++++++---------------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/nodes/core/storage/50-file.js b/nodes/core/storage/50-file.js index 43195901f..936e2f84f 100644 --- a/nodes/core/storage/50-file.js +++ b/nodes/core/storage/50-file.js @@ -28,10 +28,9 @@ module.exports = function(RED) { this.createDir = n.createDir || false; var node = this; node.wstream = null; - node.data = []; node.msgQueue = []; node.closing = false; - node.closeCallbacks = []; + node.closeCallback = null; function processMsg(msg, done) { var filename = node.filename || msg.filename || ""; @@ -76,83 +75,81 @@ 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({msg:msg,data:Buffer.from(data)}); - - while (node.data.length > 0) { - if (node.overwriteFile === "true") { - (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); - done(); - }); - }) - })(node.data.shift()); - } - else { - // 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 + if (node.overwriteFile === "true") { + var wstream = fs.createWriteStream(filename, { encoding:'binary', flags:'w', autoClose:true }); + node.wstream = wstream; + wstream.on("error", function(err) { + node.error(RED._("file.errors.writefail",{error:err.toString()}),msg); + done(); + }); + wstream.on("open", function() { + wstream.end(data, function() { + node.send(msg); + done(); + }); + }) + return; + } + else { + // 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; } - } - 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) { - // Static filename - write and reuse the stream next time - var packet = node.data.shift() - node.wstream.write(packet.data, function() { - node.send(packet.msg); - done(); - }); - - } else { - // Dynamic filename - write and close the stream - var packet = node.data.shift() - node.wstream.end(packet.data, function() { - node.send(packet.msg); - delete node.wstream; - delete node.wstreamIno; - done(); - }); + } 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); + done(); + }); + } + if (node.filename) { + // Static filename - write and reuse the stream next time + node.wstream.write(data, function() { + node.send(msg); + done(); + }); + } else { + // Dynamic filename - write and close the stream + node.wstream.end(data, function() { + node.send(msg); + delete node.wstream; + delete node.wstreamIno; + done(); + }); + } } } + else { + done(); + } } function processQ(queue) { @@ -162,6 +159,9 @@ module.exports = function(RED) { if (queue.length > 0) { processQ(queue); } + else if (node.closing) { + closeNode(); + } }); } @@ -171,9 +171,15 @@ module.exports = function(RED) { // pending write exists return; } - processQ(msgQueue); - if (node.closing) { - closeNode(); + try { + processQ(msgQueue); + } + catch (e) { + node.msgQueue = []; + if (node.closing) { + closeNode(); + } + throw e; } }); @@ -181,23 +187,23 @@ module.exports = function(RED) { if (node.wstream) { node.wstream.end(); } if (node.tout) { clearTimeout(node.tout); } node.status({}); - var callbacks = node.closeCallbacks; - node.closeCallbacks = []; + var cb = node.closeCallback; + node.closeCallback = null; node.closing = false; - for (cb in callbacks) { + if (cb) { cb(); } } - this.on('close', function(cb) { - if (cb) { - node.closeCallbacks.push(done); - } + this.on('close', function(done) { if (node.closing) { // already closing return; } node.closing = true; + if (done) { + node.closeCallback = done; + } if (node.msgQueue.length > 0) { // close after queue processed return; From 925ebcc06e365288493f5946a0a02a94c53af6db Mon Sep 17 00:00:00 2001 From: YuLun Shih Date: Fri, 12 Oct 2018 14:50:45 -0700 Subject: [PATCH 7/7] Add missing comma --- settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.js b/settings.js index 6d6c6948b..641c56498 100644 --- a/settings.js +++ b/settings.js @@ -148,7 +148,7 @@ module.exports = { // The following property can be used to cause insecure HTTP connections to // be redirected to HTTPS. - //requireHttps: true + //requireHttps: true, // The following property can be used to disable the editor. The admin API // is not affected by this option. To disable both the editor and the admin