summaryrefslogtreecommitdiffstats
path: root/bin/zmodemjs-sz.js
blob: d2331ba33ffea099659234c854f4398f4a4a6d74 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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 …");
        }
    }
});