comparison rlgwebd.js @ 160:ed837da65e5f

RLGWebD: Clean up code related to session timestamps. TermSessions now store the timestamp of the latest data. This removes the need to use fstat() to calculate idle times. The reaper() function is removed. It may be useful to find another way to remove old login keys.
author John "Elwin" Edwards
date Sat, 03 Jan 2015 17:39:15 -0500
parents a613380ffdc2
children 5a7e7ec136c8
comparison
equal deleted inserted replaced
159:a613380ffdc2 160:ed837da65e5f
17 var ctlsocket = "/var/local/rlgwebd/ctl"; 17 var ctlsocket = "/var/local/rlgwebd/ctl";
18 var httpPort = 8080; 18 var httpPort = 8080;
19 var chrootDir = "/var/dgl/"; 19 var chrootDir = "/var/dgl/";
20 var dropToUser = "rodney"; 20 var dropToUser = "rodney";
21 var serveStaticRoot = "/var/www/"; // inside the chroot 21 var serveStaticRoot = "/var/www/"; // inside the chroot
22 var playtimeout = 3600000; // Idle time before games are autosaved, in ms
23 22
24 /* Data on the games available. */ 23 /* Data on the games available. */
25 var games = { 24 var games = {
26 "rogue3": { 25 "rogue3": {
27 "name": "Rogue V3", 26 "name": "Rogue V3",
120 this.term = pty.spawn(this.game.path, args, spawnopts); 119 this.term = pty.spawn(this.game.path, args, spawnopts);
121 tslog("%s playing %s (pid %d)", this.pname, this.game.uname, this.term.pid); 120 tslog("%s playing %s (pid %d)", this.pname, this.game.uname, this.term.pid);
122 this.emit('open', true, this.game.uname, this.pname); 121 this.emit('open', true, this.game.uname, this.pname);
123 gamemux.emit('begin', this.game.uname, this.pname); 122 gamemux.emit('begin', this.game.uname, this.pname);
124 /* Set up the lockfile and ttyrec */ 123 /* Set up the lockfile and ttyrec */
125 var ts = timestamp(); 124 this.lasttime = new Date();
125 var ts = timestamp(this.lasttime);
126 var progressdir = path.join("/dgldir/inprogress", this.game.uname); 126 var progressdir = path.join("/dgldir/inprogress", this.game.uname);
127 this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec"); 127 this.lock = path.join(progressdir, this.pname + ":node:" + ts + ".ttyrec");
128 var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n'; 128 var lmsg = this.term.pid.toString() + '\n' + this.h + '\n' + this.w + '\n';
129 fs.writeFile(this.lock, lmsg, "utf8"); 129 fs.writeFile(this.lock, lmsg, "utf8");
130 var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname, 130 var ttyrec = path.join("/dgldir/ttyrec", this.pname, this.game.uname,
132 this.record = fs.createWriteStream(ttyrec, { mode: 0664 }); 132 this.record = fs.createWriteStream(ttyrec, { mode: 0664 });
133 /* Holds the output since the last screen clear, so watchers can begin 133 /* Holds the output since the last screen clear, so watchers can begin
134 * with a complete screen. */ 134 * with a complete screen. */
135 this.framebuf = new Buffer(1024); 135 this.framebuf = new Buffer(1024);
136 this.frameoff = 0; 136 this.frameoff = 0;
137 logins[lkey].sessions.push(this.game.uname + "/" + this.pname);
138 /* END setup */ 137 /* END setup */
139 function ttyrec_chunk(datastr) { 138 function ttyrec_chunk(datastr) {
140 var ts = new Date(); 139 ss.lasttime = new Date();
141 var buf = new Buffer(datastr); 140 var buf = new Buffer(datastr);
142 var chunk = new Buffer(buf.length + 12); 141 var chunk = new Buffer(buf.length + 12);
143 /* TTYREC headers */ 142 /* TTYREC headers */
144 chunk.writeUInt32LE(Math.floor(ts.getTime() / 1000), 0); 143 chunk.writeUInt32LE(Math.floor(ss.lasttime.getTime() / 1000), 0);
145 chunk.writeUInt32LE(1000 * (ts.getTime() % 1000), 4); 144 chunk.writeUInt32LE(1000 * (ss.lasttime.getTime() % 1000), 4);
146 chunk.writeUInt32LE(buf.length, 8); 145 chunk.writeUInt32LE(buf.length, 8);
147 buf.copy(chunk, 12); 146 buf.copy(chunk, 12);
148 ss.record.write(chunk); 147 ss.record.write(chunk);
149 ss.framepush(buf); 148 ss.framepush(buf);
150 ss.emit('data', buf); 149 ss.emit('data', buf);
371 checkprogress(user, games[gname], regactive, [games[gname]]); 370 checkprogress(user, games[gname], regactive, [games[gname]]);
372 } 371 }
373 } 372 }
374 373
375 /* A few utility functions */ 374 /* A few utility functions */
376 function timestamp() { 375 function timestamp(dd) {
377 dd = new Date(); 376 if (!(dd instanceof Date)) {
377 dd = new Date();
378 }
378 sd = dd.toISOString(); 379 sd = dd.toISOString();
379 sd = sd.slice(0, sd.indexOf(".")); 380 sd = sd.slice(0, sd.indexOf("."));
380 return sd.replace("T", "."); 381 return sd.replace("T", ".");
381 } 382 }
382 383
453 454
454 function getMsgWS(msgObj) { 455 function getMsgWS(msgObj) {
455 if (msgObj.type != "utf8") 456 if (msgObj.type != "utf8")
456 return {}; 457 return {};
457 return getMsg(msgObj.utf8Data); 458 return getMsg(msgObj.utf8Data);
458 }
459
460 /* FIXME sessid removal */
461 function reaper() {
462 return; // TODO figure out if this function is useful
463 var now = new Date();
464 function reapcheck(session) {
465 fs.fstat(session.record.fd, function (err, stats) {
466 if (!err && now - stats.mtime > playtimeout) {
467 tslog("Reaping session %s", session.sessid);
468 /* Dissociate it with its login name. */
469 var sn = logins[session.key].sessions.indexOf(session.sessid);
470 if (sn >= 0) {
471 logins[session.key].sessions.splice(sn, 1);
472 }
473 /* Shut it down. */
474 session.close();
475 }
476 });
477 }
478 for (var sessid in sessions) {
479 reapcheck(sessions[sessid]);
480 }
481 for (var lkey in logins) {
482 if (logins[lkey].sessions.length > 0) {
483 /* Check for games that have terminated normally, and remove them. */
484 var expired = [];
485 var targarray = logins[lkey].sessions;
486 /* Let's not find out what happens if you modify an array
487 * you're iterating through. */
488 for (var i = 0; i < targarray.length; i++) {
489 if (!(targarray[i] in sessions))
490 expired.push(targarray[i]);
491 }
492 if (expired.length > 0) {
493 for (var j = 0; j < expired.length; j++) {
494 targarray.splice(targarray.indexOf(expired[j]), 1);
495 }
496 }
497 }
498 }
499 } 459 }
500 460
501 function login(req, res, formdata) { 461 function login(req, res, formdata) {
502 if (!allowlogin) { 462 if (!allowlogin) {
503 sendError(res, 6, null, false); 463 sendError(res, 6, null, false);
526 return; 486 return;
527 } 487 }
528 var lkey = randkey(2); 488 var lkey = randkey(2);
529 while (lkey in logins) 489 while (lkey in logins)
530 lkey = randkey(2); 490 lkey = randkey(2);
531 logins[lkey] = {"name": username, "ts": new Date(), "sessions": []}; 491 logins[lkey] = {"name": username, "ts": new Date()};
532 res.writeHead(200, {'Content-Type': 'application/json'}); 492 res.writeHead(200, {'Content-Type': 'application/json'});
533 var reply = {"t": "l", "k": lkey, "u": username}; 493 var reply = {"t": "l", "k": lkey, "u": username};
534 res.write(JSON.stringify(reply)); 494 res.write(JSON.stringify(reply));
535 res.end(); 495 res.end();
536 tslog("%s has logged in (key %s)", username, lkey); 496 tslog("%s has logged in (key %s)", username, lkey);
577 function checkreg(code, signal) { 537 function checkreg(code, signal) {
578 if (code === 0) { 538 if (code === 0) {
579 var lkey = randkey(2); 539 var lkey = randkey(2);
580 while (lkey in logins) 540 while (lkey in logins)
581 lkey = randkey(2); 541 lkey = randkey(2);
582 logins[lkey] = {"name": uname, "ts": new Date(), "sessions": []}; 542 logins[lkey] = {"name": uname, "ts": new Date()};
583 var reply = {"t": "r", "k": lkey, "u": uname}; 543 var reply = {"t": "r", "k": lkey, "u": uname};
584 res.writeHead(200, {'Content-Type': 'application/json'}); 544 res.writeHead(200, {'Content-Type': 'application/json'});
585 res.write(JSON.stringify(reply)); 545 res.write(JSON.stringify(reply));
586 res.end(); 546 res.end();
587 tslog("Added new user: %s", uname); 547 tslog("Added new user: %s", uname);
730 } 690 }
731 }); 691 });
732 return; 692 return;
733 } 693 }
734 694
735 /* TODO simplify by storing timestamps instead of callin stat() */ 695 /* Currently, this doesn't do anything blocking, but keep the callback */
736 function getStatus(callback) { 696 function getStatus(callback) {
737 var now = new Date(); 697 var now = new Date();
738 var statusinfo = {"s": allowlogin, "g": []}; 698 var statusinfo = {"s": allowlogin, "g": []};
739 function idleset(n, idletime) {
740 if (n >= 0 && n < statusinfo.g.length) {
741 statusinfo.g[n].i = idletime;
742 }
743 for (var j = 0; j < statusinfo.g.length; j++) {
744 if (!("i" in statusinfo.g[j]))
745 return;
746 }
747 callback(statusinfo);
748 }
749 for (var tag in sessions) { 699 for (var tag in sessions) {
750 var gamedesc = {}; 700 var gamedesc = {};
751 gamedesc["tag"] = tag; 701 gamedesc["tag"] = tag;
752 gamedesc["p"] = sessions[tag].pname; 702 gamedesc["p"] = sessions[tag].pname;
753 gamedesc["g"] = sessions[tag].game.uname; 703 gamedesc["g"] = sessions[tag].game.uname;
704 gamedesc["i"] = now - sessions[tag].lasttime;
754 statusinfo["g"].push(gamedesc); 705 statusinfo["g"].push(gamedesc);
755 } 706 }
756 statusinfo["dgl"] = []; 707 statusinfo["dgl"] = [];
757 for (var tag in dglgames) { 708 for (var tag in dglgames) {
758 statusinfo["dgl"].push(tag); 709 statusinfo["dgl"].push(tag);
759 } 710 }
760 if (statusinfo.g.length == 0) { 711 callback(statusinfo);
761 callback(statusinfo);
762 return;
763 }
764 function makecallback(i) {
765 return function (err, stats) {
766 if (err)
767 idleset(i, null);
768 else
769 idleset(i, now - stats.mtime);
770 }
771 }
772 for (var i = 0; i < statusinfo.g.length; i++) {
773 /* fd sometimes isn't a number, presumably when the file isn't open yet. */
774 /* FIXME sessid -> tag */
775 var tag = statusinfo.g[i].tag;
776 if (tag in sessions && typeof(sessions[tag].record.fd) == 'number') {
777 fs.fstat(sessions[tag].record.fd, makecallback(i));
778 }
779 else {
780 idleset(i, null);
781 }
782 }
783 } 712 }
784 713
785 function statusmsg(req, res) { 714 function statusmsg(req, res) {
786 function respond(info) { 715 function respond(info) {
787 res.writeHead(200, { "Content-Type": "application/json" }); 716 res.writeHead(200, { "Content-Type": "application/json" });
1132 process.exit(1); 1061 process.exit(1);
1133 } 1062 }
1134 httpServer = http.createServer(webHandler); 1063 httpServer = http.createServer(webHandler);
1135 httpServer.listen(httpPort); 1064 httpServer.listen(httpPort);
1136 tslog('rlgwebd running on port %d', httpPort); 1065 tslog('rlgwebd running on port %d', httpPort);
1137 setInterval(reaper, playtimeout / 4);
1138 wsServer = new WebSocketServer({"httpServer": httpServer}); 1066 wsServer = new WebSocketServer({"httpServer": httpServer});
1139 wsServer.on("request", wsHandler); 1067 wsServer.on("request", wsHandler);
1140 tslog('WebSockets are online'); 1068 tslog('WebSockets are online');
1141 progressWatcher = startProgressWatcher(); 1069 progressWatcher = startProgressWatcher();
1142 setInterval(pushStatus, 4000); 1070 setInterval(pushStatus, 4000);