diff --git a/core/art.js b/core/art.js index 97c56e4f..22909125 100644 --- a/core/art.js +++ b/core/art.js @@ -7,6 +7,7 @@ var miscUtil = require('./misc_util.js'); var ansi = require('./ansi_term.js'); var aep = require('./ansi_escape_parser.js'); var sauce = require('./sauce.js'); +const farmhash = require('farmhash'); // deps var fs = require('fs'); @@ -225,10 +226,157 @@ function defaultEofFromExtension(ext) { return SUPPORTED_ART_TYPES[ext.toLowerCase()].eof; } -// :TODO: change to display(art, options, cb) -// cb(err, mci) +// :TODO: Implement the following +// * Pause (disabled | termHeight | keyPress ) +// * Cancel (disabled | ) +// * Resume from pause -> continous (disabled | ) +function display(client, art, options, cb) { + if(_.isFunction(options) && !cb) { + cb = options; + options = {}; + } -function display(options, cb) { + if(!art || !art.length) { + return cb(new Error('Empty art')); + } + + options.mciReplaceChar = options.mciReplaceChar || ' '; + options.disableMciCache = options.disableMciCache || false; + + if(!_.isBoolean(options.iceColors)) { + // try to detect from SAUCE + if(_.has(options, 'sauce.ansiFlags') && (options.sauce.ansiFlags & (1 << 0))) { + options.iceColors = true; + } + } + + const ansiParser = new aep.ANSIEscapeParser({ + mciReplaceChar : options.mciReplaceChar, + termHeight : client.term.termHeight, + termWidth : client.term.termWidth, + trailingLF : options.trailingLF, + }); + + let parseComplete = false; + let cprListener; + let mciMap; + const mciCprQueue = []; + let artHash; + + function completed() { + if(cprListener) { + client.removeListener('cursor position report', cprListener); + } + + if(!options.disableMciCache) { + // cache our MCI findings... + client.mciCache[artHash] = mciMap; + } + + ansiParser.removeAllListeners(); // :TODO: Necessary??? + + const extraInfo = { + height : ansiParser.row - 1, + }; + + return cb(null, mciMap, extraInfo); + } + + + if(!options.disableMciCache) { + artHash = farmhash.hash32(art); + + // see if we have a mciMap cached for this art + if(client.mciCache) { + mciMap = client.mciCache[artHash]; + } + } + + if(!mciMap) { + // no cached MCI info + mciMap = {}; + + cprListener = function(pos) { + if(mciCprQueue.length > 0) { + mciMap[mciCprQueue.shift()].position = pos; + + if(parseComplete && 0 === mciCprQueue.length) { + return completed(); + } + } + }; + + client.on('cursor position report', cprListener); + + let generatedId = 100; + + ansiParser.on('mci', mciInfo => { + // :TODO: ensure generatedId's do not conflict with any existing |id| + const id = _.isNumber(mciInfo.id) ? mciInfo.id : generatedId; + const mapKey = `${mciInfo.mci}${id}`; + const mapEntry = mciMap[mapKey]; + + if(mapEntry) { + mapEntry.focusSGR = mciInfo.SGR; + mapEntry.focusArgs = mciInfo.args; + } else { + mciMap[mapKey] = { + args : mciInfo.args, + SGR : mciInfo.SGR, + code : mciInfo.mci, + id : id, + }; + + if(!mciInfo.id) { + ++generatedId; + } + + mciCprQueue.push(mapKey); + client.term.write(ansi.queryPos(), false); + } + + }); + } + + ansiParser.on('literal', literal => client.term.write(literal, false) ); + ansiParser.on('control', control => client.term.write(control, false) ); + + ansiParser.on('complete', () => { + parseComplete = true; + + if(0 === mciCprQueue.length) { + return completed(); + } + }); + + let ansiFontSeq; + if(options.font) { + ansiFontSeq = ansi.setSyncTermFontWithAlias(options.font); + } else if(options.sauce) { + let fontName = getFontNameFromSAUCE(options.sauce); + if(fontName) { + fontName = ansi.getSyncTERMFontFromAlias(fontName); + } + + // don't set default (CP437) from SAUCE + if(fontName && 'cp437' !== fontName) { + ansiFontSeq = ansi.setSyncTERMFont(fontName); + } + } + + if(ansiFontSeq) { + client.term.write(ansiFontSeq, false); + } + + if(options.iceColors) { + client.term.write(ansi.blinkToBrightIntensity(), false); + } + + ansiParser.reset(art); + ansiParser.parse(); +} + +function displayBACKUP(options, cb) { assert(_.isObject(options)); assert(_.isObject(options.client)); assert(!_.isUndefined(options.art)); diff --git a/core/client.js b/core/client.js index bc766a9f..544e66c4 100644 --- a/core/client.js +++ b/core/client.js @@ -82,6 +82,11 @@ function Client(input, output) { this.lastKeyPressMs = Date.now(); this.menuStack = new MenuStack(this); this.acs = new ACS(this); + this.mciCache = {}; + + this.clearMciCache = function() { + this.mciCache = {}; + }; Object.defineProperty(this, 'node', { get : function() { diff --git a/core/client_term.js b/core/client_term.js index 42d71a77..8ef4753d 100644 --- a/core/client_term.js +++ b/core/client_term.js @@ -150,11 +150,13 @@ ClientTerminal.prototype.write = function(s, convertLineFeeds, cb) { ClientTerminal.prototype.rawWrite = function(s, cb) { if(this.output) { - this.output.write(s, function written(err) { - if(_.isFunction(cb)) { - cb(err); - } else if(err) { - Log.warn('Failed writing to socket: ' + err.toString()); + this.output.write(s, err => { + if(cb) { + return cb(err); + } + + if(err) { + Log.warn( { error : err.message }, 'Failed writing to socket'); } }); } diff --git a/core/servers/ssh.js b/core/servers/ssh.js index 974d4bcc..9f47da80 100644 --- a/core/servers/ssh.js +++ b/core/servers/ssh.js @@ -149,6 +149,8 @@ function SSHClient(clientConn) { if(termHeight > 0 && termWidth > 0) { self.term.termHeight = termHeight; self.term.termWidth = termWidth; + + self.clearMciCache(); // term size changes = invalidate cache } if(_.isString(info.term) && info.term.length > 0 && 'unknown' === self.term.termType) { diff --git a/core/servers/telnet.js b/core/servers/telnet.js index c50aa00c..0e174a57 100644 --- a/core/servers/telnet.js +++ b/core/servers/telnet.js @@ -599,9 +599,11 @@ TelnetClient.prototype.handleSbCommand = function(evt) { self.setTermType(evt.envVars[name]); } else if('COLUMNS' === name && 0 === self.term.termWidth) { self.term.termWidth = parseInt(evt.envVars[name]); + self.clearMciCache(); // term size changes = invalidate cache self.log.debug({ termWidth : self.term.termWidth, source : 'NEW-ENVIRON'}, 'Window width updated'); } else if('ROWS' === name && 0 === self.term.termHeight) { self.term.termHeight = parseInt(evt.envVars[name]); + self.clearMciCache(); // term size changes = invalidate cache self.log.debug({ termHeight : self.term.termHeight, source : 'NEW-ENVIRON'}, 'Window height updated'); } else { if(name in self.term.env) { @@ -636,6 +638,8 @@ TelnetClient.prototype.handleSbCommand = function(evt) { self.term.env.ROWS = evt.height; } + self.clearMciCache(); // term size changes = invalidate cache + self.log.debug({ termWidth : evt.width , termHeight : evt.height, source : 'NAWS' }, 'Window size updated'); } else { self.log(evt, 'SB'); diff --git a/core/theme.js b/core/theme.js index 5e65a453..90ff0cd4 100644 --- a/core/theme.js +++ b/core/theme.js @@ -455,6 +455,7 @@ function displayThemeArt(options, cb) { cb(err); } else { // :TODO: just use simple merge of options -> displayOptions + /* var dispOptions = { art : artInfo.data, sauce : artInfo.sauce, @@ -466,6 +467,16 @@ function displayThemeArt(options, cb) { art.display(dispOptions, function displayed(err, mciMap, extraInfo) { cb(err, { mciMap : mciMap, artInfo : artInfo, extraInfo : extraInfo } ); }); + */ + const displayOpts = { + sauce : artInfo.sauce, + font : options.font, + trailingLF : options.trailingLF, + }; + + art.display(options.client, artInfo.data, displayOpts, (err, mciMap, extraInfo) => { + return cb(err, { mciMap : mciMap, artInfo : artInfo, extraInfo : extraInfo } ); + }); } }); } diff --git a/core/user.js b/core/user.js index 7446c872..0288d8dd 100644 --- a/core/user.js +++ b/core/user.js @@ -60,28 +60,20 @@ function User() { groupNames = [ groupNames ]; } - // :TODO: _.some() - - var isMember = false; - - _.forEach(groupNames, groupName => { - if(-1 !== self.groups.indexOf(groupName)) { - isMember = true; - return false; // stop iteration - } - }); - + const isMember = groupNames.some(gn => (-1 !== self.groups.indexOf(gn))); return isMember; }; this.getLegacySecurityLevel = function() { if(self.isRoot() || self.isGroupMember('sysops')) { return 100; - } else if(self.isGroupMember('users')) { - return 30; - } else { - return 10; // :TODO: Is this what we want? } + + if(self.isGroupMember('users')) { + return 30; + } + + return 10; // :TODO: Is this what we want? }; } diff --git a/package.json b/package.json index 0dba8a29..ccd25479 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,10 @@ "string-format": "davidchambers/string-format#mini-language", "temp": "^0.8.3", "inquirer" : "^1.1.0", - "fs-extra" : "0.26.x" + "fs-extra" : "0.26.x", + "farmhash" : "^1.2.0" }, "engines": { - "node": ">=0.12.2" + "node": ">=4.2.0" } }