# HG changeset patch # User John "Elwin" Edwards # Date 1421674349 18000 # Node ID db2f5ab112e95efde83ebdbd71e7b7c173532f6d # Parent 3c0e7697bb308642d9763e66169a6b9a5c625006 Move all TermSession methods into the prototype. diff -r 3c0e7697bb30 -r db2f5ab112e9 rlgwebd.js --- a/rlgwebd.js Sat Jan 17 19:57:40 2015 -0500 +++ b/rlgwebd.js Mon Jan 19 08:32:29 2015 -0500 @@ -78,12 +78,12 @@ * "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 @@ 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 @@ 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 @@ 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);