summaryrefslogtreecommitdiffstats
path: root/bin/zmodemjs-sz.js
diff options
context:
space:
mode:
Diffstat (limited to 'bin/zmodemjs-sz.js')
-rwxr-xr-xbin/zmodemjs-sz.js140
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 …");
+ }
+ }
+});