Move all TermSession methods into the prototype.

This commit is contained in:
John "Elwin" Edwards 2015-01-19 08:32:29 -05:00
parent 4034ab16ef
commit a2bb3fe97d

View file

@ -78,12 +78,12 @@ var gamemux = new events.EventEmitter();
* "exit": Child terminated. Parameters: none
*/
function TermSession(gname, pname, wsReq) {
var ss = this;
/* Subclass EventEmitter to do the hard work. */
events.EventEmitter.call(this);
/* Don't launch anything that's not a real game. */
if (gname in games) {
this.game = games[gname];
this.gname = gname;
}
else {
this.failed = true;
@ -108,49 +108,33 @@ function TermSession(gname, pname, wsReq) {
var spawnopts = {"env": childenv, "cwd": "/", "rows": this.h, "cols": this.w,
"name": "xterm-256color"};
this.term = pty.spawn(this.game.path, args, spawnopts);
tslog("%s playing %s (pid %d)", this.pname, this.game.uname, this.term.pid);
tslog("%s playing %s (pid %d)", this.pname, this.gname, this.term.pid);
this.failed = false;
sessions[this.game.uname + "/" + this.pname] = this;
gamemux.emit('begin', this.game.uname, this.pname, 'rlg');
sessions[this.gname + "/" + this.pname] = this;
gamemux.emit('begin', this.gname, this.pname, 'rlg');
/* Set up the lockfile and ttyrec */
this.lasttime = new Date();
var ts = timestamp(this.lasttime);
var progressdir = path.join("/dgldir/inprogress", this.game.uname);
var progressdir = path.join("/dgldir/inprogress", this.gname);
this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec");
var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n';
fs.writeFile(this.lock, lmsg, "utf8");
var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname,
var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.gname,
ts + ".ttyrec");
this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
/* Holds the output since the last screen clear, so watchers can begin
* with a complete screen. */
this.framebuf = new Buffer(1024);
this.frameoff = 0;
/* The player's WebSocket and its handlers. */
this.playerconn = wsReq.accept(null, wsReq.origin);
this.playerconn.on('message', this.input_msg.bind(this));
this.playerconn.on('close', this.close.bind(this));
/* Array for watcher connections. */
this.watchers = [];
/* END setup */
function ttyrec_chunk(datastr) {
ss.lasttime = new Date();
var buf = new Buffer(datastr);
var chunk = new Buffer(buf.length + 12);
/* TTYREC headers */
chunk.writeUInt32LE(Math.floor(ss.lasttime.getTime() / 1000), 0);
chunk.writeUInt32LE(1000 * (ss.lasttime.getTime() % 1000), 4);
chunk.writeUInt32LE(buf.length, 8);
buf.copy(chunk, 12);
ss.record.write(chunk);
ss.framepush(buf);
/* Send to the player. */
var msg = JSON.stringify({"t": "d", "d": buf.toString("hex")});
ss.playerconn.sendUTF(msg);
/* Send to any watchers. */
for (var i = 0; i < ss.watchers.length; i++) {
if (ss.watchers[i].connected)
ss.watchers[i].sendUTF(msg);
}
ss.emit('data', buf);
}
/* Send initial data. */
this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h,
"p": this.pname, "g": this.gname}));
/* Begin the ttyrec with some metadata, like dgamelaunch does. */
var descstr = "\x1b[2J\x1b[1;1H\r\n";
descstr += "Player: " + this.pname + "\r\nGame: " + this.game.name + "\r\n";
@ -159,81 +143,18 @@ function TermSession(gname, pname, wsReq) {
descstr += "Time: (" + Math.floor(this.lasttime.getTime() / 1000) + ") ";
descstr += this.lasttime.toUTCString().slice(0, -4) + "\r\n";
descstr += "Size: " + this.w + "x" + this.h + "\r\n\x1b[2J";
ttyrec_chunk(descstr);
this.term.on("data", ttyrec_chunk);
this.write = function(data) {
this.term.write(data);
};
// Teardown.
this.term.on("exit", function () {
var tag = ss.tag();
fs.unlink(ss.lock);
ss.record.end();
var watchsocks = ss.watchers;
ss.watchers = [];
for (var i = 0; i < watchsocks.length; i++) {
if (watchsocks[i].connected)
watchsocks[i].close();
}
if (ss.playerconn.connected) {
ss.playerconn.sendUTF(JSON.stringify({"t": "q"}));
ss.playerconn.close();
}
ss.emit('exit');
gamemux.emit('end', ss.game.uname, ss.pname);
delete sessions[tag];
tslog("Game %s ended.", tag);
});
this.close = function () {
if (ss.tag() in sessions)
ss.term.kill('SIGHUP');
};
/* Send initial data. */
this.playerconn.sendUTF(JSON.stringify({"t": "s", "w": this.w, "h": this.h,
"p": this.pname, "g": this.game.uname}));
/* Attach handlers. */
function messageH(message) {
var parsedMsg = getMsgWS(message);
if (parsedMsg.t == 'q') {
ss.close();
}
else if (parsedMsg.t == 'd') {
var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, "");
if (hexstr.length % 2 != 0) {
hexstr = hexstr.slice(0, -1);
}
var keybuf = new Buffer(hexstr, "hex");
ss.write(keybuf);
}
}
this.playerconn.on('message', messageH);
this.playerconn.on('close', this.close);
/* To attach a watcher. */
this.attach = function (wsReq) {
var conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify({
"t": "w", "w": this.w, "h": this.h, "p": this.pname,
"g": this.game.uname
}));
conn.sendUTF(JSON.stringify({"t": "d",
"d": this.framebuf.toString("hex", 0, this.frameoff)}));
conn.on('close', function () {
/* 'this' is the connection when triggered */
var n = ss.watchers.indexOf(this);
if (n >= 0) {
ss.watchers.splice(n, 1);
tslog("A WebSocket watcher has left game %s", ss.tag());
}
});
this.watchers.push(conn);
};
this.write_ttyrec(descstr);
this.term.on("data", this.write_ttyrec.bind(this));
this.term.on("exit", this.destroy.bind(this));
}
TermSession.prototype = new events.EventEmitter();
TermSession.prototype.tag = function () {
if (this.pname === undefined || this.game === undefined)
if (this.pname === undefined || this.gname === undefined)
return "";
return this.game.uname + "/" + this.pname;
return this.gname + "/" + this.pname;
};
TermSession.prototype.framepush = function(chunk) {
/* If this chunk resets the screen, discard what preceded it. */
if (isclear(chunk)) {
@ -254,6 +175,95 @@ TermSession.prototype.framepush = function(chunk) {
this.frameoff += chunk.length;
};
/* Currently this also sends to the player and any watchers. */
TermSession.prototype.write_ttyrec = function (datastr) {
this.lasttime = new Date();
var buf = new Buffer(datastr);
var chunk = new Buffer(buf.length + 12);
/* TTYREC headers */
chunk.writeUInt32LE(Math.floor(this.lasttime.getTime() / 1000), 0);
chunk.writeUInt32LE(1000 * (this.lasttime.getTime() % 1000), 4);
chunk.writeUInt32LE(buf.length, 8);
buf.copy(chunk, 12);
this.record.write(chunk);
this.framepush(buf);
/* Send to the player. */
var msg = JSON.stringify({"t": "d", "d": buf.toString("hex")});
this.playerconn.sendUTF(msg);
/* Send to any watchers. */
for (var i = 0; i < this.watchers.length; i++) {
if (this.watchers[i].connected)
this.watchers[i].sendUTF(msg);
}
this.emit('data', buf);
};
/* For writing to the subprocess's stdin. */
TermSession.prototype.write = function (data) {
this.term.write(data);
};
TermSession.prototype.input_msg = function (message) {
var parsedMsg = getMsgWS(message);
if (parsedMsg.t == 'q') {
this.close();
}
else if (parsedMsg.t == 'd') {
var hexstr = parsedMsg.d.replace(/[^0-9a-f]/gi, "");
if (hexstr.length % 2 != 0) {
hexstr = hexstr.slice(0, -1);
}
var keybuf = new Buffer(hexstr, "hex");
this.write(keybuf);
}
};
/* Teardown. */
TermSession.prototype.close = function () {
if (this.tag() in sessions)
this.term.kill('SIGHUP');
};
TermSession.prototype.destroy = function () {
var tag = this.tag();
fs.unlink(this.lock);
this.record.end();
var watchsocks = this.watchers;
this.watchers = [];
for (var i = 0; i < watchsocks.length; i++) {
if (watchsocks[i].connected)
watchsocks[i].close();
}
if (this.playerconn.connected) {
this.playerconn.sendUTF(JSON.stringify({"t": "q"}));
this.playerconn.close();
}
this.emit('exit');
gamemux.emit('end', this.gname, this.pname);
delete sessions[tag];
tslog("Game %s ended.", tag);
};
/* Adds a watcher. */
TermSession.prototype.attach = function (wsReq) {
var conn = wsReq.accept(null, wsReq.origin);
conn.sendUTF(JSON.stringify({
"t": "w", "w": this.w, "h": this.h, "p": this.pname, "g": this.gname
}));
conn.sendUTF(JSON.stringify({"t": "d",
"d": this.framebuf.toString("hex", 0, this.frameoff)}));
conn.on('close', this.detach.bind(this, conn));
this.watchers.push(conn);
};
TermSession.prototype.detach = function (socket) {
var n = this.watchers.indexOf(socket);
if (n >= 0) {
this.watchers.splice(n, 1);
tslog("A WebSocket watcher has left game %s", this.tag());
}
};
function DglSession(filename) {
var ss = this;
events.EventEmitter.call(this);