@flowfuse/nr-file-nodes 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/50-file-test-file.txt +0 -0
- package/CHANGELOG.md +5 -0
- package/LICENSE +178 -0
- package/README.md +21 -0
- package/file.html +412 -0
- package/file.js +402 -0
- package/locales/en-US/file.html +69 -0
- package/locales/en-US/file.json +65 -0
- package/package.json +54 -0
- package/vfs.js +186 -0
package/file.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright JS Foundation and other contributors, http://js.foundation
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
**/
|
|
16
|
+
|
|
17
|
+
module.exports = function (RED) {
|
|
18
|
+
'use strict'
|
|
19
|
+
|
|
20
|
+
// Do not register nodes in runtime if settings are not provided
|
|
21
|
+
if (
|
|
22
|
+
!RED.settings.flowforge ||
|
|
23
|
+
!RED.settings.flowforge.projectID ||
|
|
24
|
+
!RED.settings.flowforge.teamID ||
|
|
25
|
+
!RED.settings.flowforge.fileStore ||
|
|
26
|
+
!RED.settings.flowforge.fileStore.url
|
|
27
|
+
) {
|
|
28
|
+
throw new Error('FlowFuse file nodes cannot be loaded without required settings')
|
|
29
|
+
}
|
|
30
|
+
const VFS = require('./vfs')
|
|
31
|
+
const os = require('os')
|
|
32
|
+
const path = require('path')
|
|
33
|
+
const iconv = require('iconv-lite')
|
|
34
|
+
|
|
35
|
+
function encode (data, enc) {
|
|
36
|
+
if (enc !== 'none') {
|
|
37
|
+
return iconv.encode(data, enc)
|
|
38
|
+
}
|
|
39
|
+
return Buffer.from(data)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function decode (data, enc) {
|
|
43
|
+
if (enc !== 'none') {
|
|
44
|
+
return iconv.decode(data, enc)
|
|
45
|
+
}
|
|
46
|
+
return data.toString()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function FileNode (n) {
|
|
50
|
+
// Write/delete a file
|
|
51
|
+
RED.nodes.createNode(this, n)
|
|
52
|
+
this.filename = n.filename
|
|
53
|
+
this.filenameType = n.filenameType
|
|
54
|
+
this.appendNewline = n.appendNewline
|
|
55
|
+
this.overwriteFile = n.overwriteFile.toString()
|
|
56
|
+
this.createDir = n.createDir || false
|
|
57
|
+
this.encoding = n.encoding || 'none'
|
|
58
|
+
const node = this
|
|
59
|
+
const fs = VFS(RED)
|
|
60
|
+
node.wstream = null
|
|
61
|
+
node.msgQueue = []
|
|
62
|
+
node.closing = false
|
|
63
|
+
node.closeCallback = null
|
|
64
|
+
|
|
65
|
+
function processMsg (msg, nodeSend, done) {
|
|
66
|
+
let filename = node.filename || ''
|
|
67
|
+
// Pre V3 compatibility - if filenameType is empty, do in place upgrade
|
|
68
|
+
if (typeof node.filenameType === 'undefined' || node.filenameType === '') {
|
|
69
|
+
// existing node AND filenameType is not set - inplace (compatible) upgrade
|
|
70
|
+
if (filename === '') { // was using empty value to denote msg.filename
|
|
71
|
+
node.filename = 'filename'
|
|
72
|
+
node.filenameType = 'msg'
|
|
73
|
+
} else { // was using a static filename - set typedInput type to str
|
|
74
|
+
node.filenameType = 'str'
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
RED.util.evaluateNodeProperty(node.filename, node.filenameType, node, msg, (err, value) => {
|
|
79
|
+
if (err) {
|
|
80
|
+
node.error(err, msg)
|
|
81
|
+
return done()
|
|
82
|
+
} else {
|
|
83
|
+
filename = value
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
filename = filename || ''
|
|
87
|
+
msg.filename = filename
|
|
88
|
+
let fullFilename = filename
|
|
89
|
+
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
|
90
|
+
// fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory, filename))
|
|
91
|
+
fullFilename = path.join(RED.settings.fileWorkingDirectory, filename)
|
|
92
|
+
}
|
|
93
|
+
if ((!node.filename) && (!node.tout)) {
|
|
94
|
+
node.tout = setTimeout(function () {
|
|
95
|
+
node.status({ fill: 'grey', shape: 'dot', text: filename })
|
|
96
|
+
clearTimeout(node.tout)
|
|
97
|
+
node.tout = null
|
|
98
|
+
}, 333)
|
|
99
|
+
}
|
|
100
|
+
if (path.isAbsolute(fullFilename)) {
|
|
101
|
+
fullFilename = fullFilename.slice(1)
|
|
102
|
+
}
|
|
103
|
+
if (filename === '') {
|
|
104
|
+
node.warn(RED._('file.errors.nofilename'))
|
|
105
|
+
done()
|
|
106
|
+
} else if (node.overwriteFile === 'delete') {
|
|
107
|
+
fs.unlink(fullFilename, function (err) {
|
|
108
|
+
if (err) {
|
|
109
|
+
node.error(RED._('file.errors.deletefail', { error: err.toString() }), msg)
|
|
110
|
+
} else {
|
|
111
|
+
node.debug(RED._('file.status.deletedfile', { file: filename }))
|
|
112
|
+
nodeSend(msg)
|
|
113
|
+
}
|
|
114
|
+
done()
|
|
115
|
+
})
|
|
116
|
+
// eslint-disable-next-line no-prototype-builtins
|
|
117
|
+
} else if (msg.hasOwnProperty('payload') && (typeof msg.payload !== 'undefined')) {
|
|
118
|
+
async function ensureDir (name, successCallback) {
|
|
119
|
+
const dir = path.dirname(name)
|
|
120
|
+
if (node.createDir) {
|
|
121
|
+
fs.ensureDir(dir, function (err) {
|
|
122
|
+
if (err) {
|
|
123
|
+
node.error(RED._('file.errors.createfail', { error: err.toString() }), msg)
|
|
124
|
+
}
|
|
125
|
+
successCallback()
|
|
126
|
+
})
|
|
127
|
+
} else {
|
|
128
|
+
successCallback()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
ensureDir(fullFilename, function success () {
|
|
133
|
+
let data = msg.payload
|
|
134
|
+
if ((typeof data === 'object') && (!Buffer.isBuffer(data))) {
|
|
135
|
+
data = JSON.stringify(data)
|
|
136
|
+
}
|
|
137
|
+
if (typeof data === 'boolean') { data = data.toString() }
|
|
138
|
+
if (typeof data === 'number') { data = data.toString() }
|
|
139
|
+
if ((node.appendNewline) && (!Buffer.isBuffer(data))) { data += os.EOL }
|
|
140
|
+
let buf
|
|
141
|
+
if (node.encoding === 'setbymsg') {
|
|
142
|
+
buf = encode(data, msg.encoding || 'none')
|
|
143
|
+
} else { buf = encode(data, node.encoding) }
|
|
144
|
+
if (node.overwriteFile === 'true') {
|
|
145
|
+
fs.writeFile(fullFilename, buf, function (err) {
|
|
146
|
+
if (err) {
|
|
147
|
+
node.error(RED._('file.errors.writefail', { error: err.toString() }), msg)
|
|
148
|
+
} else {
|
|
149
|
+
nodeSend(msg)
|
|
150
|
+
}
|
|
151
|
+
done()
|
|
152
|
+
})
|
|
153
|
+
} else {
|
|
154
|
+
fs.appendFile(fullFilename, buf, function (err) {
|
|
155
|
+
if (err) {
|
|
156
|
+
node.error(RED._('file.errors.appendfail', { error: err.toString() }), msg)
|
|
157
|
+
} else {
|
|
158
|
+
nodeSend(msg)
|
|
159
|
+
}
|
|
160
|
+
done()
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
} else {
|
|
165
|
+
done()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function processQueue (queue) {
|
|
170
|
+
const event = queue[0]
|
|
171
|
+
processMsg(event.msg, event.send, function () {
|
|
172
|
+
event.done()
|
|
173
|
+
queue.shift()
|
|
174
|
+
if (queue.length > 0) {
|
|
175
|
+
processQueue(queue)
|
|
176
|
+
} else if (node.closing) {
|
|
177
|
+
closeNode()
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
this.on('input', function (msg, nodeSend, nodeDone) {
|
|
183
|
+
const msgQueue = node.msgQueue
|
|
184
|
+
msgQueue.push({
|
|
185
|
+
msg,
|
|
186
|
+
send: nodeSend,
|
|
187
|
+
done: nodeDone
|
|
188
|
+
})
|
|
189
|
+
if (msgQueue.length > 1) {
|
|
190
|
+
// pending write exists
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
processQueue(msgQueue)
|
|
195
|
+
} catch (e) {
|
|
196
|
+
node.msgQueue = []
|
|
197
|
+
if (node.closing) {
|
|
198
|
+
closeNode()
|
|
199
|
+
}
|
|
200
|
+
throw e
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
function closeNode () {
|
|
205
|
+
if (node.wstream) { node.wstream.end() }
|
|
206
|
+
if (node.tout) { clearTimeout(node.tout) }
|
|
207
|
+
node.status({})
|
|
208
|
+
const cb = node.closeCallback
|
|
209
|
+
node.closeCallback = null
|
|
210
|
+
node.closing = false
|
|
211
|
+
if (cb) {
|
|
212
|
+
cb()
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.on('close', function (done) {
|
|
217
|
+
if (node.closing) {
|
|
218
|
+
// already closing
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
node.closing = true
|
|
222
|
+
if (done) {
|
|
223
|
+
node.closeCallback = done
|
|
224
|
+
}
|
|
225
|
+
if (node.msgQueue.length > 0) {
|
|
226
|
+
// close after queue processed
|
|
227
|
+
|
|
228
|
+
} else {
|
|
229
|
+
closeNode()
|
|
230
|
+
}
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
RED.nodes.registerType('file', FileNode)
|
|
234
|
+
|
|
235
|
+
function FileInNode (n) {
|
|
236
|
+
// Read a file
|
|
237
|
+
RED.nodes.createNode(this, n)
|
|
238
|
+
this.filename = n.filename
|
|
239
|
+
this.filenameType = n.filenameType
|
|
240
|
+
this.format = n.format
|
|
241
|
+
this.chunk = false
|
|
242
|
+
this.encoding = n.encoding || 'none'
|
|
243
|
+
this.allProps = n.allProps || false
|
|
244
|
+
if (n.sendError === undefined) {
|
|
245
|
+
this.sendError = true
|
|
246
|
+
} else {
|
|
247
|
+
this.sendError = n.sendError
|
|
248
|
+
}
|
|
249
|
+
if (this.format === 'lines') { this.chunk = true }
|
|
250
|
+
if (this.format === 'stream') { this.chunk = true }
|
|
251
|
+
const node = this
|
|
252
|
+
const fs = VFS(RED)
|
|
253
|
+
this.on('input', function (msg, nodeSend, nodeDone) {
|
|
254
|
+
let filename = node.filename || ''
|
|
255
|
+
// Pre V3 compatibility - if filenameType is empty, do in place upgrade
|
|
256
|
+
if (typeof node.filenameType === 'undefined' || node.filenameType === '') {
|
|
257
|
+
// existing node AND filenameType is not set - inplace (compatible) upgrade
|
|
258
|
+
if (filename === '') { // was using empty value to denote msg.filename
|
|
259
|
+
node.filename = 'filename'
|
|
260
|
+
node.filenameType = 'msg'
|
|
261
|
+
} else { // was using a static filename - set typedInput type to str
|
|
262
|
+
node.filenameType = 'str'
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
let propertyError = false
|
|
266
|
+
RED.util.evaluateNodeProperty(node.filename, node.filenameType, node, msg, (err, value) => {
|
|
267
|
+
if (err) {
|
|
268
|
+
node.error(err, msg)
|
|
269
|
+
propertyError = true
|
|
270
|
+
// return done()
|
|
271
|
+
} else {
|
|
272
|
+
filename = (value || '').replace(/\t|\r|\n/g, '')
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
if (propertyError) {
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
filename = filename || ''
|
|
279
|
+
let fullFilename = filename
|
|
280
|
+
|
|
281
|
+
if (filename && RED.settings.fileWorkingDirectory && !path.isAbsolute(filename)) {
|
|
282
|
+
// fullFilename = path.resolve(path.join(RED.settings.fileWorkingDirectory, filename))
|
|
283
|
+
fullFilename = path.join(RED.settings.fileWorkingDirectory, filename)
|
|
284
|
+
}
|
|
285
|
+
if (!node.filename) {
|
|
286
|
+
node.status({ fill: 'grey', shape: 'dot', text: filename })
|
|
287
|
+
}
|
|
288
|
+
if (path.isAbsolute(fullFilename)) {
|
|
289
|
+
fullFilename = fullFilename.slice(1)
|
|
290
|
+
}
|
|
291
|
+
if (filename === '') {
|
|
292
|
+
node.warn(RED._('file.errors.nofilename'))
|
|
293
|
+
nodeDone()
|
|
294
|
+
} else {
|
|
295
|
+
msg.filename = filename
|
|
296
|
+
let lines = Buffer.from([])
|
|
297
|
+
let spare = ''
|
|
298
|
+
let count = 0
|
|
299
|
+
let type = 'buffer'
|
|
300
|
+
let ch = ''
|
|
301
|
+
if (node.format === 'lines') {
|
|
302
|
+
ch = '\n'
|
|
303
|
+
type = 'string'
|
|
304
|
+
}
|
|
305
|
+
let getout = false
|
|
306
|
+
|
|
307
|
+
const rs = fs.createReadStream(fullFilename)
|
|
308
|
+
.on('readable', function () {
|
|
309
|
+
let chunk
|
|
310
|
+
let m
|
|
311
|
+
const hwm = rs._readableState.highWaterMark
|
|
312
|
+
while ((chunk = rs.read()) !== null) {
|
|
313
|
+
if (node.chunk === true) {
|
|
314
|
+
getout = true
|
|
315
|
+
if (node.format === 'lines') {
|
|
316
|
+
spare += decode(chunk, node.encoding)
|
|
317
|
+
const bits = spare.split('\n')
|
|
318
|
+
let i = 0
|
|
319
|
+
for (i = 0; i < bits.length - 1; i++) {
|
|
320
|
+
m = {}
|
|
321
|
+
if (node.allProps === true) {
|
|
322
|
+
m = RED.util.cloneMessage(msg)
|
|
323
|
+
} else {
|
|
324
|
+
m.topic = msg.topic
|
|
325
|
+
m.filename = msg.filename
|
|
326
|
+
}
|
|
327
|
+
m.payload = bits[i]
|
|
328
|
+
m.parts = { index: count, ch, type, id: msg._msgid }
|
|
329
|
+
count += 1
|
|
330
|
+
nodeSend(m)
|
|
331
|
+
}
|
|
332
|
+
spare = bits[i]
|
|
333
|
+
}
|
|
334
|
+
if (node.format === 'stream') {
|
|
335
|
+
m = {}
|
|
336
|
+
if (node.allProps === true) {
|
|
337
|
+
m = RED.util.cloneMessage(msg)
|
|
338
|
+
} else {
|
|
339
|
+
m.topic = msg.topic
|
|
340
|
+
m.filename = msg.filename
|
|
341
|
+
}
|
|
342
|
+
m.payload = chunk
|
|
343
|
+
m.parts = { index: count, ch, type, id: msg._msgid }
|
|
344
|
+
count += 1
|
|
345
|
+
if (chunk.length < hwm) { // last chunk is smaller that high water mark = eof
|
|
346
|
+
getout = false
|
|
347
|
+
m.parts.count = count
|
|
348
|
+
}
|
|
349
|
+
nodeSend(m)
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
lines = Buffer.concat([lines, chunk])
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
})
|
|
356
|
+
.on('error', function (err) {
|
|
357
|
+
node.error(err, msg)
|
|
358
|
+
if (node.sendError) {
|
|
359
|
+
const sendMessage = RED.util.cloneMessage(msg)
|
|
360
|
+
delete sendMessage.payload
|
|
361
|
+
sendMessage.error = err
|
|
362
|
+
nodeSend(sendMessage)
|
|
363
|
+
}
|
|
364
|
+
nodeDone()
|
|
365
|
+
})
|
|
366
|
+
.on('end', function () {
|
|
367
|
+
if (node.chunk === false) {
|
|
368
|
+
if (node.format === 'utf8') {
|
|
369
|
+
msg.payload = decode(lines, node.encoding)
|
|
370
|
+
} else { msg.payload = lines }
|
|
371
|
+
nodeSend(msg)
|
|
372
|
+
} else if (node.format === 'lines') {
|
|
373
|
+
let m = {}
|
|
374
|
+
if (node.allProps) {
|
|
375
|
+
m = RED.util.cloneMessage(msg)
|
|
376
|
+
} else {
|
|
377
|
+
m.topic = msg.topic
|
|
378
|
+
m.filename = msg.filename
|
|
379
|
+
}
|
|
380
|
+
m.payload = spare
|
|
381
|
+
m.parts = {
|
|
382
|
+
index: count,
|
|
383
|
+
count: count + 1,
|
|
384
|
+
ch,
|
|
385
|
+
type,
|
|
386
|
+
id: msg._msgid
|
|
387
|
+
}
|
|
388
|
+
nodeSend(m)
|
|
389
|
+
} else if (getout) { // last chunk same size as high water mark - have to send empty extra packet.
|
|
390
|
+
const m = { parts: { index: count, count, ch, type, id: msg._msgid } }
|
|
391
|
+
nodeSend(m)
|
|
392
|
+
}
|
|
393
|
+
nodeDone()
|
|
394
|
+
})
|
|
395
|
+
}
|
|
396
|
+
})
|
|
397
|
+
this.on('close', function () {
|
|
398
|
+
node.status({})
|
|
399
|
+
})
|
|
400
|
+
}
|
|
401
|
+
RED.nodes.registerType('file in', FileInNode)
|
|
402
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Copyright JS Foundation and other contributors, http://js.foundation
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
-->
|
|
16
|
+
|
|
17
|
+
<script type="text/html" data-help-name="file">
|
|
18
|
+
<p>Writes <code>msg.payload</code> to a file, either adding to the end or replacing the existing content.
|
|
19
|
+
Alternatively, it can delete the file.</p>
|
|
20
|
+
<h3>Inputs</h3>
|
|
21
|
+
<dl class="message-properties">
|
|
22
|
+
<dt class="optional">filename <span class="property-type">string</span></dt>
|
|
23
|
+
<dd>The name of the file to be updated can be provided in the node configuration, or as a message property.
|
|
24
|
+
By default it will use <code>msg.filename</code> but this can be customised in the node.
|
|
25
|
+
</dd>
|
|
26
|
+
<dt class="optional">encoding <span class="property-type">string</span></dt>
|
|
27
|
+
<dd>If encoding is configured to be set by msg, then this optional property can set the encoding.</dt>
|
|
28
|
+
</dl>
|
|
29
|
+
<h3>Output</h3>
|
|
30
|
+
<p>On completion of write, input message is sent to output port.</p>
|
|
31
|
+
<h3>Details</h3>
|
|
32
|
+
<p>Each message payload will be added to the end of the file, optionally appending
|
|
33
|
+
a newline (\n) character between each one.</p>
|
|
34
|
+
<p>If <code>msg.filename</code> is used the file will be closed after every write.
|
|
35
|
+
For best performance use a fixed filename.</p>
|
|
36
|
+
<p>It can be configured to overwrite the entire file rather than append. For example,
|
|
37
|
+
when writing binary data to a file, such as an image, this option should be used
|
|
38
|
+
and the option to append a newline should be disabled.</p>
|
|
39
|
+
<p>Encoding of data written to a file can be specified from list of encodings.</p>
|
|
40
|
+
<p>Alternatively, this node can be configured to delete the file.</p>
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<script type="text/html" data-help-name="file in">
|
|
44
|
+
<p>Reads the contents of a file as either a string or binary buffer.</p>
|
|
45
|
+
<h3>Inputs</h3>
|
|
46
|
+
<dl class="message-properties">
|
|
47
|
+
<dt class="optional">filename <span class="property-type">string</span></dt>
|
|
48
|
+
<dd>The name of the file to be read can be provided in the node configuration, or as a message property.
|
|
49
|
+
By default it will use <code>msg.filename</code> but this can be customised in the node.
|
|
50
|
+
</dd>
|
|
51
|
+
</dl>
|
|
52
|
+
<h3>Outputs</h3>
|
|
53
|
+
<dl class="message-properties">
|
|
54
|
+
<dt>payload <span class="property-type">string | buffer</span></dt>
|
|
55
|
+
<dd>The contents of the file as either a string or binary buffer.</dd>
|
|
56
|
+
<dt class="optional">filename <span class="property-type">string</span></dt>
|
|
57
|
+
<dd>If not configured in the node, this optional property sets the name of the file to be read.</dd>
|
|
58
|
+
</dl>
|
|
59
|
+
<h3>Details</h3>
|
|
60
|
+
<p>The filename should be an absolute path, otherwise it will be relative to
|
|
61
|
+
the working directory of the Node-RED process.</p>
|
|
62
|
+
<p>On Windows, path separators may need to be escaped, for example: <code>\\Users\\myUser</code>.</p>
|
|
63
|
+
<p>Optionally, a text file can be split into lines, outputting one message per line, or a binary file
|
|
64
|
+
split into smaller buffer chunks - the chunk size being operating system dependant, but typically 64k (Linux/Mac) or 41k (Windows).</p>
|
|
65
|
+
<p>When split into multiple messages, each message will have a <code>parts</code>
|
|
66
|
+
property set, forming a complete message sequence.</p>
|
|
67
|
+
<p>Encoding of input data can be specified from list of encodings if output format is string.</p>
|
|
68
|
+
<p>Errors should be caught and handled using a Catch node.</p>
|
|
69
|
+
</script>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"file": {
|
|
3
|
+
"label": {
|
|
4
|
+
"write": "write file",
|
|
5
|
+
"read": "read file",
|
|
6
|
+
"filename": "Filename",
|
|
7
|
+
"path": "path",
|
|
8
|
+
"action": "Action",
|
|
9
|
+
"addnewline": "Add newline (\\n) to each payload?",
|
|
10
|
+
"createdir": "Create directory if it doesn't exist?",
|
|
11
|
+
"outputas": "Output",
|
|
12
|
+
"breakchunks": "Break into chunks",
|
|
13
|
+
"breaklines": "Break into lines",
|
|
14
|
+
"sendError": "Send message on error (legacy mode)",
|
|
15
|
+
"encoding": "Encoding",
|
|
16
|
+
"deletelabel": "delete __file__",
|
|
17
|
+
"utf8String": "UTF8 string",
|
|
18
|
+
"utf8String_plural": "UTF8 strings",
|
|
19
|
+
"binaryBuffer": "binary buffer",
|
|
20
|
+
"binaryBuffer_plural": "binary buffers",
|
|
21
|
+
"allProps": "include all existing properties in each msg"
|
|
22
|
+
},
|
|
23
|
+
"action": {
|
|
24
|
+
"append": "append to file",
|
|
25
|
+
"overwrite": "overwrite file",
|
|
26
|
+
"delete": "delete file"
|
|
27
|
+
},
|
|
28
|
+
"output": {
|
|
29
|
+
"utf8": "a single utf8 string",
|
|
30
|
+
"buffer": "a single Buffer object",
|
|
31
|
+
"lines": "a msg per line",
|
|
32
|
+
"stream": "a stream of Buffers"
|
|
33
|
+
},
|
|
34
|
+
"status": {
|
|
35
|
+
"wrotefile": "wrote to file: __file__",
|
|
36
|
+
"deletedfile": "deleted file: __file__",
|
|
37
|
+
"appendedfile": "appended to file: __file__"
|
|
38
|
+
},
|
|
39
|
+
"encoding": {
|
|
40
|
+
"none": "default",
|
|
41
|
+
"setbymsg": "set by msg.encoding",
|
|
42
|
+
"native": "Native",
|
|
43
|
+
"unicode": "Unicode",
|
|
44
|
+
"japanese": "Japanese",
|
|
45
|
+
"chinese": "Chinese",
|
|
46
|
+
"korean": "Korean",
|
|
47
|
+
"taiwan": "Taiwan/Hong Kong",
|
|
48
|
+
"windows": "Windows codepages",
|
|
49
|
+
"iso": "ISO codepages",
|
|
50
|
+
"ibm": "IBM codepages",
|
|
51
|
+
"mac": "Mac codepages",
|
|
52
|
+
"koi8": "KOI8 codepages",
|
|
53
|
+
"misc": "Miscellaneous"
|
|
54
|
+
},
|
|
55
|
+
"errors": {
|
|
56
|
+
"nofilename": "No filename specified",
|
|
57
|
+
"invaliddelete": "Warning: Invalid delete. Please use specific delete option in config dialog.",
|
|
58
|
+
"deletefail": "failed to delete file: __error__",
|
|
59
|
+
"writefail": "failed to write to file: __error__",
|
|
60
|
+
"appendfail": "failed to append to file: __error__",
|
|
61
|
+
"createfail": "failed to create file: __error__"
|
|
62
|
+
},
|
|
63
|
+
"tip": "Tip: The filename should be an absolute path, otherwise it will be relative to the working directory of the Node-RED process."
|
|
64
|
+
}
|
|
65
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flowfuse/nr-file-nodes",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"description": "Node-RED file nodes packaged for FlowFuse",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npm run test:files && npm run test:memory",
|
|
8
|
+
"test:memory": "mocha 'test/memory_spec.js' --timeout 5000",
|
|
9
|
+
"test:files": "mocha 'test/file_spec.js' --timeout 5000",
|
|
10
|
+
"lint": "eslint -c .eslintrc *.js",
|
|
11
|
+
"lint:fix": "eslint -c .eslintrc *.js --fix"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"FlowFuse",
|
|
15
|
+
"node-red",
|
|
16
|
+
"filesystem"
|
|
17
|
+
],
|
|
18
|
+
"node-red": {
|
|
19
|
+
"version": ">=3.0.0",
|
|
20
|
+
"nodes": {
|
|
21
|
+
"file": "file.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/FlowFuse/nr-file-nodes.git"
|
|
27
|
+
},
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "FlowFuse Inc."
|
|
30
|
+
},
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/FlowFuse/nr-file-nodes/issues"
|
|
33
|
+
},
|
|
34
|
+
"homepage": "https://github.com/FlowFuse/nr-file-nodes#readme",
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"got": "11.8.5",
|
|
37
|
+
"iconv-lite": "0.6.3"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=16.x"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@flowforge/file-server": "^0.0.5",
|
|
44
|
+
"eslint": "^8.25.0",
|
|
45
|
+
"eslint-config-standard": "^17.0.0",
|
|
46
|
+
"eslint-plugin-no-only-tests": "^3.1.0",
|
|
47
|
+
"fs-extra": "^10.1.0",
|
|
48
|
+
"mocha": "^10.1.0",
|
|
49
|
+
"mocha-cli": "^1.0.1",
|
|
50
|
+
"node-red": "^3.1.0",
|
|
51
|
+
"node-red-node-test-helper": "^0.3.0",
|
|
52
|
+
"sinon": "^14.0.2"
|
|
53
|
+
}
|
|
54
|
+
}
|