141 lines
3.8 KiB
JavaScript
141 lines
3.8 KiB
JavaScript
|
"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 …");
|
|||
|
}
|
|||
|
}
|
|||
|
});
|