1
0
Fork 0
zmodemjs/bin/zmodemjs-sz.js

141 lines
3.8 KiB
JavaScript
Raw Permalink Normal View History

"use strict";
// A proof-of-concept CLI implementation of “sz” using zmodem.js.
// This is not tested extensively and isnt 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);
}
// Cant be to the same terminal as STDOUT.
// npms “ttyname” can tell us, but its 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 cant 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 wasnt 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 thats 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 …");
}
}
});