diff --git a/core/bbs.js b/core/bbs.js index 0d6453d1..58186af5 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -5,22 +5,22 @@ //SegfaultHandler.registerHandler('enigma-bbs-segfault.log'); // ENiGMA½ -let conf = require('./config.js'); -let logger = require('./logger.js'); -let miscUtil = require('./misc_util.js'); -let database = require('./database.js'); -let clientConns = require('./client_connections.js'); +const conf = require('./config.js'); +const logger = require('./logger.js'); +const database = require('./database.js'); +const clientConns = require('./client_connections.js'); -let paths = require('path'); -let async = require('async'); -let util = require('util'); -let _ = require('lodash'); -let assert = require('assert'); -let mkdirs = require('fs-extra').mkdirs; +const async = require('async'); +const util = require('util'); +const _ = require('lodash'); +const mkdirs = require('fs-extra').mkdirs; // our main entry point exports.bbsMain = bbsMain; +// object with various services we want to de-init/shutdown cleanly if possible +const initServices = {}; + function bbsMain() { async.waterfall( [ @@ -84,6 +84,39 @@ function bbsMain() { ); } +function shutdownSystem() { + logger.log.info('Process interrupted, shutting down...'); + + async.series( + [ + function closeConnections(callback) { + const activeConnections = clientConns.getActiveConnections(); + let i = activeConnections.length; + while(i--) { + activeConnections[i].term.write('\n\nServer is shutting down NOW! Disconnecting...\n\n'); + clientConns.removeClient(activeConnections[i]); + } + callback(null); + }, + function stopEventScheduler(callback) { + if(initServices.eventScheduler) { + return initServices.eventScheduler.shutdown( () => { + callback(null); // ignore err + }); + } else { + return callback(null); + } + }, + function stopMsgNetwork(callback) { + require('./msg_network.js').shutdown(callback); + } + ], + () => { + process.exit(); + } + ); +} + function initialize(cb) { async.series( [ @@ -102,18 +135,7 @@ function initialize(cb) { function basicInit(callback) { logger.init(); - process.on('SIGINT', function onSigInt() { - logger.log.info('Process interrupted, shutting down...'); - - var activeConnections = clientConns.getActiveConnections(); - var i = activeConnections.length; - while(i--) { - activeConnections[i].term.write('\n\nServer is shutting down NOW! Disconnecting...\n\n'); - clientConns.removeClient(activeConnections[i]); - } - - process.exit(); - }); + process.on('SIGINT', shutdownSystem); // Init some extensions require('string-format').extend(String.prototype, require('./string_util.js').stringFormatExtensions); @@ -172,7 +194,10 @@ function initialize(cb) { }, function readyEventScheduler(callback) { const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule; - EventSchedulerModule.loadAndStart(callback); + EventSchedulerModule.loadAndStart( (err, modInst) => { + initServices.eventScheduler = modInst; + return callback(err); + }); } ], function onComplete(err) { diff --git a/core/door.js b/core/door.js index 2862be4b..da106ce6 100644 --- a/core/door.js +++ b/core/door.js @@ -2,7 +2,6 @@ 'use strict'; const events = require('events'); - const _ = require('lodash'); const pty = require('ptyw.js'); const decode = require('iconv-lite').decode; @@ -80,6 +79,10 @@ function Door(client, exeInfo) { return cb(null); } }; + + this.doorExited = function() { + self.emit('finished'); + }; } require('util').inherits(Door, events.EventEmitter); @@ -90,7 +93,7 @@ Door.prototype.run = function() { this.prepareSocketIoServer( (err, sockServer) => { if(err) { this.client.log.warn( { error : err.toString() }, 'Failed executing door'); - return self.emit('finished'); + return self.doorExited(); } // Expand arg strings, e.g. {dropFile} -> DOOR32.SYS @@ -140,7 +143,7 @@ Door.prototype.run = function() { door.removeAllListeners(); - self.emit('finished'); + return self.doorExited(); }); }); }; diff --git a/core/event_scheduler.js b/core/event_scheduler.js index 9b1b3bdf..b4243a7a 100644 --- a/core/event_scheduler.js +++ b/core/event_scheduler.js @@ -9,6 +9,8 @@ const Log = require('./logger.js').log; const _ = require('lodash'); const later = require('later'); const path = require('path'); +const pty = require('ptyw.js'); +const gaze = require('gaze'); exports.getModule = EventSchedulerModule; exports.EventSchedulerModule = EventSchedulerModule; // allow for loadAndStart @@ -101,8 +103,8 @@ class ScheduledEvent { } } - executeAction(cb) { - Log.info( { eventName : this.name, action : this.action }, 'Executing scheduled action...'); + executeAction(reason, cb) { + Log.info( { eventName : this.name, action : this.action, reason : reason }, 'Executing scheduled event action...'); if('method' === this.action.type) { const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js') @@ -112,7 +114,7 @@ class ScheduledEvent { if(err) { Log.debug( { error : err.toString(), eventName : this.name, action : this.action }, - 'Error while performing scheduled event action'); + 'Error performing scheduled event action'); } return cb(err); @@ -125,7 +127,24 @@ class ScheduledEvent { return cb(e); } } else if('execute' === this.action.type) { - // :TODO: implement execute! + const opts = { + // :TODO: cwd + name : this.name, + cols : 80, + rows : 24, + env : process.env, + }; + + const proc = pty.spawn(this.action.what, this.action.args, opts); + + proc.once('exit', exitCode => { + if(exitCode) { + Log.warn( + { eventName : this.name, action : this.action, exitCode : exitCode }, + 'Bad exit code while performing scheduled event action'); + } + return cb(exitCode ? new Error(`Bad exit code while performing scheduled event action: ${exitCode}`) : null); + }); } } } @@ -140,14 +159,14 @@ function EventSchedulerModule(options) { const self = this; this.runningActions = new Set(); - this.performAction = function(schedEvent) { + this.performAction = function(schedEvent, reason) { if(self.runningActions.has(schedEvent.name)) { return; // already running } self.runningActions.add(schedEvent.name); - schedEvent.executeAction( () => { + schedEvent.executeAction(reason, () => { self.runningActions.delete(schedEvent.name); }); }; @@ -169,14 +188,14 @@ EventSchedulerModule.loadAndStart = function(cb) { const modInst = new mod.getModule(); modInst.startup( err => { - return cb(err); + return cb(err, modInst); }); }); }; EventSchedulerModule.prototype.startup = function(cb) { - this.eventTimers = []; + this.eventTimers = []; const self = this; if(this.moduleConfig && _.has(this.moduleConfig, 'events')) { @@ -192,7 +211,7 @@ EventSchedulerModule.prototype.startup = function(cb) { Log.debug( { - evetnName : schedEvent.name, + eventName : schedEvent.name, schedule : this.moduleConfig.events[schedEvent.name].schedule, action : schedEvent.action, }, @@ -201,11 +220,20 @@ EventSchedulerModule.prototype.startup = function(cb) { if(schedEvent.schedule.sched) { this.eventTimers.push(later.setInterval( () => { - self.performAction(schedEvent); + self.performAction(schedEvent, 'Schedule'); }, schedEvent.schedule.sched)); } - - // :TODO: handle watchfile -> performAction + + if(schedEvent.schedule.watchFile) { + gaze(schedEvent.schedule.watchFile, (err, watcher) => { + // :TODO: should track watched files & stop watching @ shutdown + watcher.on('all', (watchEvent, watchedPath) => { + if(schedEvent.schedule.watchFile === watchedPath) { + self.performAction(schedEvent, `Watch file: ${watchedPath}`); + } + }); + }); + } }); } @@ -216,6 +244,6 @@ EventSchedulerModule.prototype.shutdown = function(cb) { if(this.eventTimers) { this.eventTimers.forEach( et => et.clear() ); } - + cb(null); }; diff --git a/core/msg_network.js b/core/msg_network.js index 030c544e..9e0813f4 100644 --- a/core/msg_network.js +++ b/core/msg_network.js @@ -5,9 +5,9 @@ let loadModulesForCategory = require('./module_util.js').loadModulesForCategory; // standard/deps -let async = require('async'); +let async = require('async'); -exports.startup = startup +exports.startup = startup; exports.shutdown = shutdown; exports.recordMessage = recordMessage; @@ -36,12 +36,19 @@ function startup(cb) { ); } -function shutdown() { - msgNetworkModules.forEach(mod => { - mod.shutdown(); - }); - - msgNetworkModules = []; +function shutdown(cb) { + async.each( + msgNetworkModules, + (msgNetModule, next) => { + msgNetModule.shutdown( () => { + return next(); + }); + }, + () => { + msgNetworkModules = []; + return cb(null); + } + ); } function recordMessage(message, cb) { diff --git a/mods/abracadabra.js b/mods/abracadabra.js index 665a5ffe..4596fb4a 100644 --- a/mods/abracadabra.js +++ b/mods/abracadabra.js @@ -11,7 +11,6 @@ let async = require('async'); let assert = require('assert'); let paths = require('path'); let _ = require('lodash'); -let net = require('net'); let mkdirs = require('fs-extra').mkdirs; // :TODO: This should really be a system module... needs a little work to allow for such @@ -20,8 +19,6 @@ exports.getModule = AbracadabraModule; let activeDoorNodeInstances = {}; -let doorInstances = {}; // name -> { count : , { : } } - exports.moduleInfo = { name : 'Abracadabra', desc : 'External BBS Door Module', @@ -166,6 +163,18 @@ function AbracadabraModule(options) { const doorInstance = new door.Door(self.client, exeInfo); doorInstance.once('finished', () => { + // + // Try to clean up various settings such as scroll regions that may + // have been set within the door + // + self.client.term.rawWrite( + ansi.normal() + + ansi.goto(self.client.term.termHeight, self.client.term.termWidth) + + ansi.setScrollRegion() + + ansi.goto(self.client.term.termHeight, 0) + + '\r\n\r\n' + ); + self.prevMenu(); });