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