Skip to content

Instantly share code, notes, and snippets.

@TotallyInformation
Created June 12, 2016 20:05
Show Gist options
  • Save TotallyInformation/cdf160c36f082dc2a20cd1183ac6cd7d to your computer and use it in GitHub Desktop.
Save TotallyInformation/cdf160c36f082dc2a20cd1183ac6cd7d to your computer and use it in GitHub Desktop.
Example reading file paths using native node.js modules

This example flow uses the native Node.JS fs and path modules to read a list of file names having a specified file extension from a given folder.

You can pass the folder and extension strings in via the msg.payload. Output is a single message for each file found. The payload will be the full file path. Everything other than the payload will be passed through to each output msg.

You would actually probably be better off using a 3rd party module as indicated in the main code but this may be a useful example of using modules in a Node-Red function node.

Note that function nodes run in a "sandbox" and have restricted syntax. As such, you cannot do:

var fs = require('fs');

So you have to require the modules in your settings.js file instead:

...
    functionGlobalContext: { // enables and pre-populates the context.global variable
        // -- Pass in Libraries for convenience in function nodes -- //
        'path' : require('path'),
        'fs'   : require('fs')
    },
...

WARNING: Errors in the module callback functions WILL CRASH Node-Red - that is why the files.filter().foreach() code is wrapped in a try/catch

Here is the JavaScript in the function node complete with error checking:

// Sends a msg with full file path for each file in a given folder ending with given extension
// PARAMETERS:
//   msg.payload.folder {string} Folder to look in for files
//   msg.payload.ext    {string} The file extension to look for
// ASSUMPTIONS:
//   Node.JS libraries fs and path have been required in the functionGlobal section of settings.js
//   Tested using Node.JS v4.4.5 LTS and Node-Red 0.13.4
// NOTES:
//   You would almost certainly be better off using one of the "glob" modules such as glob-fs, node-glob or glob-all

var fs = global.get('fs1')
var path = global.get('path')

// Give up if fs or path have not been assigned to global context (see assumption 1)
if ( (typeof fs !== 'undefined') || (typeof path !== 'undefined') ) {
    node.error('Either fs or path modules could not be found in global context')
    return    
}
// And make sure they are functions
if ( (typeof fs !== 'object') || (typeof path !== 'object') ) {
    node.error('Either fs or path from global context is not an object & it must be')
    return    
}

var folder = msg.payload.folder || '/home/pi'
var extension = msg.payload.ext || '.gpx'

// Make sure the parameters are strings
if ( (typeof folder !== 'string') || (typeof extension !== 'string') ) {
    node.error('Either Folder or Ext is not a string, cannot process. Folder: ' + folder + ', Ext: ' + extension, err)
    return    
}

fs.readdir(folder, callback)

// readir is ASYNC so don't return msg here as
// it would be pointless

function callback(err, files) {
    if (err) {
        node.error('Oops! Folder does not exist: ' + folder, err)
        return
    }
    
    // Any error in here will crash Node-Red!! So catch them instead
    try {
        files
            .filter( filterOnExtension ) // calls the given callback func for each file, will only pass names where fn returns true
            .forEach( sendName )
    } catch (err) {
        node.error('Ouch! Something went badly wrong processing the files list', err)
        return
    }
}

function filterOnExtension(file) {
    var len = extension.length

    // Returns TRUE only if the file name ends in the given extension
    return file.substr(0-len) === extension
}

function sendName(file) {
        msg.payload = path.join(folder, file)
        node.send( msg )
}
[{"id":"b1bc15bb.e99be8","type":"inject","z":"106a5b82.ef95a4","name":"Inject Payload JSON","topic":"","payload":"{\"folder\":\"/home/pi\", \"ext\":\".sh\"}","payloadType":"json","repeat":"","crontab":"","once":false,"x":330,"y":4380,"wires":[["41f356dc.7b3c18"]]},{"id":"41f356dc.7b3c18","type":"function","z":"106a5b82.ef95a4","name":"Read folder, output file paths","func":"// Sends a msg with full file path for each file in a given folder ending with given extension\n// PARAMETERS:\n// msg.payload.folder {string} Folder to look in for files\n// msg.payload.ext {string} The file extension to look for\n// ASSUMPTIONS:\n// Node.JS libraries fs and path have been required in the functionGlobal section of settings.js\n// Tested using Node.JS v4.4.5 LTS and Node-Red 0.13.4\n// NOTES:\n// You would almost certainly be better off using one of the \"glob\" modules such as glob-fs, node-glob or glob-all\n\nvar fs = global.get('fs1')\nvar path = global.get('path')\n\n// Give up if fs or path have not been assigned to global context (see assumption 1)\nif ( (typeof fs !== 'undefined') || (typeof path !== 'undefined') ) {\n node.error('Either fs or path modules could not be found in global context')\n return \n}\n// And make sure they are functions\nif ( (typeof fs !== 'object') || (typeof path !== 'object') ) {\n node.error('Either fs or path from global context is not an object & it must be')\n return \n}\n\nvar folder = msg.payload.folder || '/home/pi'\nvar extension = msg.payload.ext || '.gpx'\n\n// Make sure the parameters are strings\nif ( (typeof folder !== 'string') || (typeof extension !== 'string') ) {\n node.error('Either Folder or Ext is not a string, cannot process. Folder: ' + folder + ', Ext: ' + extension, err)\n return \n}\n\nfs.readdir(folder, callback)\n\n// readir is ASYNC so don't return msg here as\n// it would be pointless\n\nfunction callback(err, files) {\n if (err) {\n node.error('Oops! Folder does not exist: ' + folder, err)\n return\n }\n \n // Any error in here will crash Node-Red!! So catch them instead\n try {\n files\n .filter( filterOnExtension ) // calls the given callback func for each file, will only pass names where fn returns true\n .forEach( sendName )\n } catch (err) {\n node.error('Ouch! Something went badly wrong processing the files list', err)\n return\n }\n}\n\nfunction filterOnExtension(file) {\n var len = extension.length\n\n // Returns TRUE only if the file name ends in the given extension\n return file.substr(0-len) === extension\n}\n\nfunction sendName(file) {\n msg.payload = path.join(folder, file)\n node.send( msg )\n}","outputs":1,"noerr":0,"x":569,"y":4380,"wires":[["60da0b63.227c54"]]},{"id":"60da0b63.227c54","type":"debug","z":"106a5b82.ef95a4","name":"","active":true,"console":"false","complete":"true","x":770,"y":4380,"wires":[]}]
@hathemi
Copy link

hathemi commented Dec 3, 2018

i test this. i change settings.js like you mentioned and i put {"folder":"C:\Users\Public\img\tmp\", "ext":".pdf"} in inject node. when i deploy and start injecting i get this error
"Either fs or path modules could not be found in global context".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment