diff options
Diffstat (limited to 'bin')
-rwxr-xr-x | bin/zmodemjs-sz.js | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/bin/zmodemjs-sz.js b/bin/zmodemjs-sz.js new file mode 100755 index 0000000..d2331ba --- /dev/null +++ b/bin/zmodemjs-sz.js @@ -0,0 +1,140 @@ +"use strict"; + +// A proof-of-concept CLI implementation of “sz” using zmodem.js. +// This is not tested extensively and isn’t really meant for production use. + +const process = require('process'); +const fs = require('fs'); +const Zmodem = require('../src/zmodem'); + +var paths = process.argv.slice(1); + +// Accommodate “node $script …” +if (paths[0] === __filename) { + paths = paths.slice(1); +} + +if (!paths.length) { + console.error("Need at least one path!"); + process.exit(1); +} + +// Can’t be to the same terminal as STDOUT. +// npm’s “ttyname” can tell us, but it’s annoying to require +// a module for this. +const DEBUG = false; + +if (DEBUG) { + var outtype = fs.fstatSync(1).mode & fs.constants.S_IFMT; + var errtype = fs.fstatSync(1).mode & fs.constants.S_IFMT; + + if (outtype === errtype && outtype === fs.constants.S_IFCHR) { + console.error("STDOUT and STDERR can’t both be to a terminal when debugging is on."); + process.exit(1); + } +} + +function _debug() { + DEBUG && console.warn.apply( console, arguments ); +} + +_debug("PID:", process.pid); +_debug("Paths to send:", paths); + +//---------------------------------------------------------------------- + +var path_fd = {}; +paths.forEach( (path) => path_fd[path] = fs.openSync(path, 'r') ); + +// TODO: This should maybe be in its own module? +// The notion of starting a session in JS wasn’t envisioned when +// this module was written. +const initial_bytes = Zmodem.Header.build("ZRQINIT").to_hex(); + +process.stdout.write(Buffer.from(initial_bytes)); +_debug('Sent ZRQINIT'); + +// We need a binary stdin. +var stdin = fs.createReadStream( "", { fd: 0 } ); + +function send_files(zsession, paths) { + function send_next() { + var path = paths.shift(); + + if (path) { + _debug("Sending offer: ", path); + + var fd = path_fd[path]; + var fstat = fs.fstatSync(fd); + + var filename = path.match(/.+\/(.+)/); + filename = filename ? filename[0] : path; + + return zsession.send_offer( { + name: filename, + size: fstat.size, + mtime: Math.round( fstat.mtimeMs / 1000 ), + } ).then( (xfer) => { + if (!xfer) { + _debug("Offer was rejected."); + return send_next(); + } + + _debug("Offer was accepted."); + + var stream = fs.createReadStream( "", { + fd: fd, + } ); + + stream.on('data', (chunk) => { + _debug("Sending chunk."); + xfer.send(chunk); + } ); + + return new Promise( (res, rej) => { + stream.on('end', () => { + _debug("Reached EOF; sending end."); + xfer.end().then( () => {; + res( send_next() ); + } ); + } ); + } ); + } ); + } + else { + _debug("Reached end of files batch."); + } + } + + return send_next(); +} + +var zsession; + +stdin.on('data', (chunk) => { + var octets = Array.from(chunk) + + if (zsession) { + zsession.consume(octets); + } + else { + _debug("Received on STDIN; checking for session.", octets); + + zsession = Zmodem.Session.parse(octets); + + if (zsession) { + _debug("Got session."); + + // It seems like .parse() should strip out the header bytes, + // but that’s not how it works. + // zsession.consume(octets); + + zsession.set_sender( (octets) => process.stdout.write( Buffer.from(octets) ) ); + + send_files(zsession, paths).then( () => zsession.close() ); + } + else { + _debug("No session yet …"); + } + } +}); |