ENiGMA 1/2 WILL USE SPACES FROM THIS POINT ON VS TABS
* Really just to make GitHub formatting happy. Arg.
This commit is contained in:
parent
5ddf04c882
commit
e9787cee3e
|
@ -18,9 +18,9 @@ const mkdirs = require('fs-extra').mkdirs;
|
|||
const activeDoorNodeInstances = {};
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Abracadabra',
|
||||
desc : 'External BBS Door Module',
|
||||
author : 'NuSkooler',
|
||||
name : 'Abracadabra',
|
||||
desc : 'External BBS Door Module',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -60,138 +60,138 @@ exports.moduleInfo = {
|
|||
*/
|
||||
|
||||
exports.getModule = class AbracadabraModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.config = options.menuConfig.config;
|
||||
// :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... }
|
||||
assert(_.isString(this.config.name, 'Config \'name\' is required'));
|
||||
assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required'));
|
||||
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required'));
|
||||
this.config = options.menuConfig.config;
|
||||
// :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... }
|
||||
assert(_.isString(this.config.name, 'Config \'name\' is required'));
|
||||
assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required'));
|
||||
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required'));
|
||||
|
||||
this.config.nodeMax = this.config.nodeMax || 0;
|
||||
this.config.args = this.config.args || [];
|
||||
}
|
||||
this.config.nodeMax = this.config.nodeMax || 0;
|
||||
this.config.args = this.config.args || [];
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
:TODO:
|
||||
* disconnecting wile door is open leaves dosemu
|
||||
* http://bbslink.net/sysop.php support
|
||||
* Font support ala all other menus... or does this just work?
|
||||
*/
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateNodeCount(callback) {
|
||||
if(self.config.nodeMax > 0 &&
|
||||
async.series(
|
||||
[
|
||||
function validateNodeCount(callback) {
|
||||
if(self.config.nodeMax > 0 &&
|
||||
_.isNumber(activeDoorNodeInstances[self.config.name]) &&
|
||||
activeDoorNodeInstances[self.config.name] + 1 > self.config.nodeMax)
|
||||
{
|
||||
self.client.log.info(
|
||||
{
|
||||
name : self.config.name,
|
||||
activeCount : activeDoorNodeInstances[self.config.name]
|
||||
},
|
||||
'Too many active instances');
|
||||
{
|
||||
self.client.log.info(
|
||||
{
|
||||
name : self.config.name,
|
||||
activeCount : activeDoorNodeInstances[self.config.name]
|
||||
},
|
||||
'Too many active instances');
|
||||
|
||||
if(_.isString(self.config.tooManyArt)) {
|
||||
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
||||
self.pausePrompt( () => {
|
||||
callback(new Error('Too many active instances'));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.client.term.write('\nToo many active instances. Try again later.\n');
|
||||
if(_.isString(self.config.tooManyArt)) {
|
||||
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
||||
self.pausePrompt( () => {
|
||||
callback(new Error('Too many active instances'));
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.client.term.write('\nToo many active instances. Try again later.\n');
|
||||
|
||||
// :TODO: Use MenuModule.pausePrompt()
|
||||
self.pausePrompt( () => {
|
||||
callback(new Error('Too many active instances'));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// :TODO: JS elegant way to do this?
|
||||
if(activeDoorNodeInstances[self.config.name]) {
|
||||
activeDoorNodeInstances[self.config.name] += 1;
|
||||
} else {
|
||||
activeDoorNodeInstances[self.config.name] = 1;
|
||||
}
|
||||
// :TODO: Use MenuModule.pausePrompt()
|
||||
self.pausePrompt( () => {
|
||||
callback(new Error('Too many active instances'));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// :TODO: JS elegant way to do this?
|
||||
if(activeDoorNodeInstances[self.config.name]) {
|
||||
activeDoorNodeInstances[self.config.name] += 1;
|
||||
} else {
|
||||
activeDoorNodeInstances[self.config.name] = 1;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
function generateDropfile(callback) {
|
||||
self.dropFile = new DropFile(self.client, self.config.dropFileType);
|
||||
var fullPath = self.dropFile.fullPath;
|
||||
callback(null);
|
||||
}
|
||||
},
|
||||
function generateDropfile(callback) {
|
||||
self.dropFile = new DropFile(self.client, self.config.dropFileType);
|
||||
var fullPath = self.dropFile.fullPath;
|
||||
|
||||
mkdirs(paths.dirname(fullPath), function dirCreated(err) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self.dropFile.createFile(function created(err) {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.toString() }, 'Could not start door');
|
||||
self.lastError = err;
|
||||
self.prevMenu();
|
||||
} else {
|
||||
self.finishedLoading();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
mkdirs(paths.dirname(fullPath), function dirCreated(err) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
self.dropFile.createFile(function created(err) {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.toString() }, 'Could not start door');
|
||||
self.lastError = err;
|
||||
self.prevMenu();
|
||||
} else {
|
||||
self.finishedLoading();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
runDoor() {
|
||||
runDoor() {
|
||||
|
||||
const exeInfo = {
|
||||
cmd : this.config.cmd,
|
||||
args : this.config.args,
|
||||
io : this.config.io || 'stdio',
|
||||
encoding : this.config.encoding || this.client.term.outputEncoding,
|
||||
dropFile : this.dropFile.fileName,
|
||||
node : this.client.node,
|
||||
//inhSocket : this.client.output._handle.fd,
|
||||
};
|
||||
const exeInfo = {
|
||||
cmd : this.config.cmd,
|
||||
args : this.config.args,
|
||||
io : this.config.io || 'stdio',
|
||||
encoding : this.config.encoding || this.client.term.outputEncoding,
|
||||
dropFile : this.dropFile.fileName,
|
||||
node : this.client.node,
|
||||
//inhSocket : this.client.output._handle.fd,
|
||||
};
|
||||
|
||||
const doorInstance = new door.Door(this.client, exeInfo);
|
||||
const doorInstance = new door.Door(this.client, exeInfo);
|
||||
|
||||
doorInstance.once('finished', () => {
|
||||
//
|
||||
// Try to clean up various settings such as scroll regions that may
|
||||
// have been set within the door
|
||||
//
|
||||
this.client.term.rawWrite(
|
||||
ansi.normal() +
|
||||
doorInstance.once('finished', () => {
|
||||
//
|
||||
// Try to clean up various settings such as scroll regions that may
|
||||
// have been set within the door
|
||||
//
|
||||
this.client.term.rawWrite(
|
||||
ansi.normal() +
|
||||
ansi.goto(this.client.term.termHeight, this.client.term.termWidth) +
|
||||
ansi.setScrollRegion() +
|
||||
ansi.goto(this.client.term.termHeight, 0) +
|
||||
'\r\n\r\n'
|
||||
);
|
||||
);
|
||||
|
||||
this.prevMenu();
|
||||
});
|
||||
this.prevMenu();
|
||||
});
|
||||
|
||||
this.client.term.write(ansi.resetScreen());
|
||||
this.client.term.write(ansi.resetScreen());
|
||||
|
||||
doorInstance.run();
|
||||
}
|
||||
doorInstance.run();
|
||||
}
|
||||
|
||||
leave() {
|
||||
super.leave();
|
||||
if(!this.lastError) {
|
||||
activeDoorNodeInstances[this.config.name] -= 1;
|
||||
}
|
||||
}
|
||||
leave() {
|
||||
super.leave();
|
||||
if(!this.lastError) {
|
||||
activeDoorNodeInstances[this.config.name] -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
finishedLoading() {
|
||||
this.runDoor();
|
||||
}
|
||||
finishedLoading() {
|
||||
this.runDoor();
|
||||
}
|
||||
};
|
||||
|
|
124
core/acs.js
124
core/acs.js
|
@ -10,81 +10,81 @@ const assert = require('assert');
|
|||
const _ = require('lodash');
|
||||
|
||||
class ACS {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
check(acs, scope, defaultAcs) {
|
||||
acs = acs ? acs[scope] : defaultAcs;
|
||||
acs = acs || defaultAcs;
|
||||
try {
|
||||
return checkAcs(acs, { client : this.client } );
|
||||
} catch(e) {
|
||||
Log.warn( { exception : e, acs : acs }, 'Exception caught checking ACS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
check(acs, scope, defaultAcs) {
|
||||
acs = acs ? acs[scope] : defaultAcs;
|
||||
acs = acs || defaultAcs;
|
||||
try {
|
||||
return checkAcs(acs, { client : this.client } );
|
||||
} catch(e) {
|
||||
Log.warn( { exception : e, acs : acs }, 'Exception caught checking ACS');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Message Conferences & Areas
|
||||
//
|
||||
hasMessageConfRead(conf) {
|
||||
return this.check(conf.acs, 'read', ACS.Defaults.MessageConfRead);
|
||||
}
|
||||
//
|
||||
// Message Conferences & Areas
|
||||
//
|
||||
hasMessageConfRead(conf) {
|
||||
return this.check(conf.acs, 'read', ACS.Defaults.MessageConfRead);
|
||||
}
|
||||
|
||||
hasMessageAreaRead(area) {
|
||||
return this.check(area.acs, 'read', ACS.Defaults.MessageAreaRead);
|
||||
}
|
||||
hasMessageAreaRead(area) {
|
||||
return this.check(area.acs, 'read', ACS.Defaults.MessageAreaRead);
|
||||
}
|
||||
|
||||
//
|
||||
// File Base / Areas
|
||||
//
|
||||
hasFileAreaRead(area) {
|
||||
return this.check(area.acs, 'read', ACS.Defaults.FileAreaRead);
|
||||
}
|
||||
//
|
||||
// File Base / Areas
|
||||
//
|
||||
hasFileAreaRead(area) {
|
||||
return this.check(area.acs, 'read', ACS.Defaults.FileAreaRead);
|
||||
}
|
||||
|
||||
hasFileAreaWrite(area) {
|
||||
return this.check(area.acs, 'write', ACS.Defaults.FileAreaWrite);
|
||||
}
|
||||
hasFileAreaWrite(area) {
|
||||
return this.check(area.acs, 'write', ACS.Defaults.FileAreaWrite);
|
||||
}
|
||||
|
||||
hasFileAreaDownload(area) {
|
||||
return this.check(area.acs, 'download', ACS.Defaults.FileAreaDownload);
|
||||
}
|
||||
hasFileAreaDownload(area) {
|
||||
return this.check(area.acs, 'download', ACS.Defaults.FileAreaDownload);
|
||||
}
|
||||
|
||||
getConditionalValue(condArray, memberName) {
|
||||
if(!Array.isArray(condArray)) {
|
||||
// no cond array, just use the value
|
||||
return condArray;
|
||||
}
|
||||
getConditionalValue(condArray, memberName) {
|
||||
if(!Array.isArray(condArray)) {
|
||||
// no cond array, just use the value
|
||||
return condArray;
|
||||
}
|
||||
|
||||
assert(_.isString(memberName));
|
||||
assert(_.isString(memberName));
|
||||
|
||||
const matchCond = condArray.find( cond => {
|
||||
if(_.has(cond, 'acs')) {
|
||||
try {
|
||||
return checkAcs(cond.acs, { client : this.client } );
|
||||
} catch(e) {
|
||||
Log.warn( { exception : e, acs : cond }, 'Exception caught checking ACS');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true; // no acs check req.
|
||||
}
|
||||
});
|
||||
const matchCond = condArray.find( cond => {
|
||||
if(_.has(cond, 'acs')) {
|
||||
try {
|
||||
return checkAcs(cond.acs, { client : this.client } );
|
||||
} catch(e) {
|
||||
Log.warn( { exception : e, acs : cond }, 'Exception caught checking ACS');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return true; // no acs check req.
|
||||
}
|
||||
});
|
||||
|
||||
if(matchCond) {
|
||||
return matchCond[memberName];
|
||||
}
|
||||
}
|
||||
if(matchCond) {
|
||||
return matchCond[memberName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ACS.Defaults = {
|
||||
MessageAreaRead : 'GM[users]',
|
||||
MessageConfRead : 'GM[users]',
|
||||
MessageAreaRead : 'GM[users]',
|
||||
MessageConfRead : 'GM[users]',
|
||||
|
||||
FileAreaRead : 'GM[users]',
|
||||
FileAreaWrite : 'GM[sysops]',
|
||||
FileAreaDownload : 'GM[users]',
|
||||
FileAreaRead : 'GM[users]',
|
||||
FileAreaWrite : 'GM[sysops]',
|
||||
FileAreaDownload : 'GM[users]',
|
||||
};
|
||||
|
||||
module.exports = ACS;
|
||||
module.exports = ACS;
|
||||
|
|
|
@ -16,278 +16,278 @@ const CR = 0x0d;
|
|||
const LF = 0x0a;
|
||||
|
||||
function ANSIEscapeParser(options) {
|
||||
var self = this;
|
||||
var self = this;
|
||||
|
||||
events.EventEmitter.call(this);
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
this.column = 1;
|
||||
this.row = 1;
|
||||
this.scrollBack = 0;
|
||||
this.graphicRendition = {};
|
||||
this.column = 1;
|
||||
this.row = 1;
|
||||
this.scrollBack = 0;
|
||||
this.graphicRendition = {};
|
||||
|
||||
this.parseState = {
|
||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||
};
|
||||
this.parseState = {
|
||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||
};
|
||||
|
||||
options = miscUtil.valueWithDefault(options, {
|
||||
mciReplaceChar : '',
|
||||
termHeight : 25,
|
||||
termWidth : 80,
|
||||
trailingLF : 'default', // default|omit|no|yes, ...
|
||||
});
|
||||
options = miscUtil.valueWithDefault(options, {
|
||||
mciReplaceChar : '',
|
||||
termHeight : 25,
|
||||
termWidth : 80,
|
||||
trailingLF : 'default', // default|omit|no|yes, ...
|
||||
});
|
||||
|
||||
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
|
||||
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
|
||||
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
|
||||
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
|
||||
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
|
||||
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
|
||||
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
|
||||
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
|
||||
|
||||
self.moveCursor = function(cols, rows) {
|
||||
self.column += cols;
|
||||
self.row += rows;
|
||||
self.moveCursor = function(cols, rows) {
|
||||
self.column += cols;
|
||||
self.row += rows;
|
||||
|
||||
self.column = Math.max(self.column, 1);
|
||||
self.column = Math.min(self.column, self.termWidth); // can't move past term width
|
||||
self.row = Math.max(self.row, 1);
|
||||
self.column = Math.max(self.column, 1);
|
||||
self.column = Math.min(self.column, self.termWidth); // can't move past term width
|
||||
self.row = Math.max(self.row, 1);
|
||||
|
||||
self.positionUpdated();
|
||||
};
|
||||
self.positionUpdated();
|
||||
};
|
||||
|
||||
self.saveCursorPosition = function() {
|
||||
self.savedPosition = {
|
||||
row : self.row,
|
||||
column : self.column
|
||||
};
|
||||
};
|
||||
self.saveCursorPosition = function() {
|
||||
self.savedPosition = {
|
||||
row : self.row,
|
||||
column : self.column
|
||||
};
|
||||
};
|
||||
|
||||
self.restoreCursorPosition = function() {
|
||||
self.row = self.savedPosition.row;
|
||||
self.column = self.savedPosition.column;
|
||||
delete self.savedPosition;
|
||||
self.restoreCursorPosition = function() {
|
||||
self.row = self.savedPosition.row;
|
||||
self.column = self.savedPosition.column;
|
||||
delete self.savedPosition;
|
||||
|
||||
self.positionUpdated();
|
||||
// self.rowUpdated();
|
||||
};
|
||||
self.positionUpdated();
|
||||
// self.rowUpdated();
|
||||
};
|
||||
|
||||
self.clearScreen = function() {
|
||||
// :TODO: should be doing something with row/column?
|
||||
self.emit('clear screen');
|
||||
};
|
||||
self.clearScreen = function() {
|
||||
// :TODO: should be doing something with row/column?
|
||||
self.emit('clear screen');
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
self.rowUpdated = function() {
|
||||
self.emit('row update', self.row + self.scrollBack);
|
||||
};*/
|
||||
|
||||
self.positionUpdated = function() {
|
||||
self.emit('position update', self.row, self.column);
|
||||
};
|
||||
self.positionUpdated = function() {
|
||||
self.emit('position update', self.row, self.column);
|
||||
};
|
||||
|
||||
function literal(text) {
|
||||
const len = text.length;
|
||||
let pos = 0;
|
||||
let start = 0;
|
||||
let charCode;
|
||||
function literal(text) {
|
||||
const len = text.length;
|
||||
let pos = 0;
|
||||
let start = 0;
|
||||
let charCode;
|
||||
|
||||
while(pos < len) {
|
||||
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
|
||||
while(pos < len) {
|
||||
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
|
||||
|
||||
switch(charCode) {
|
||||
case CR :
|
||||
self.emit('literal', text.slice(start, pos));
|
||||
start = pos;
|
||||
switch(charCode) {
|
||||
case CR :
|
||||
self.emit('literal', text.slice(start, pos));
|
||||
start = pos;
|
||||
|
||||
self.column = 1;
|
||||
self.column = 1;
|
||||
|
||||
self.positionUpdated();
|
||||
break;
|
||||
self.positionUpdated();
|
||||
break;
|
||||
|
||||
case LF :
|
||||
self.emit('literal', text.slice(start, pos));
|
||||
start = pos;
|
||||
case LF :
|
||||
self.emit('literal', text.slice(start, pos));
|
||||
start = pos;
|
||||
|
||||
self.row += 1;
|
||||
self.row += 1;
|
||||
|
||||
self.positionUpdated();
|
||||
break;
|
||||
self.positionUpdated();
|
||||
break;
|
||||
|
||||
default :
|
||||
if(self.column === self.termWidth) {
|
||||
self.emit('literal', text.slice(start, pos + 1));
|
||||
start = pos + 1;
|
||||
default :
|
||||
if(self.column === self.termWidth) {
|
||||
self.emit('literal', text.slice(start, pos + 1));
|
||||
start = pos + 1;
|
||||
|
||||
self.column = 1;
|
||||
self.row += 1;
|
||||
self.column = 1;
|
||||
self.row += 1;
|
||||
|
||||
self.positionUpdated();
|
||||
} else {
|
||||
self.column += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
self.positionUpdated();
|
||||
} else {
|
||||
self.column += 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
++pos;
|
||||
}
|
||||
++pos;
|
||||
}
|
||||
|
||||
//
|
||||
// Finalize this chunk
|
||||
//
|
||||
if(self.column > self.termWidth) {
|
||||
self.column = 1;
|
||||
self.row += 1;
|
||||
//
|
||||
// Finalize this chunk
|
||||
//
|
||||
if(self.column > self.termWidth) {
|
||||
self.column = 1;
|
||||
self.row += 1;
|
||||
|
||||
self.positionUpdated();
|
||||
}
|
||||
self.positionUpdated();
|
||||
}
|
||||
|
||||
const rem = text.slice(start);
|
||||
if(rem) {
|
||||
self.emit('literal', rem);
|
||||
}
|
||||
}
|
||||
const rem = text.slice(start);
|
||||
if(rem) {
|
||||
self.emit('literal', rem);
|
||||
}
|
||||
}
|
||||
|
||||
function parseMCI(buffer) {
|
||||
// :TODO: move this to "constants" seciton @ top
|
||||
var mciRe = /%([A-Z]{2})([0-9]{1,2})?(?:\(([0-9A-Za-z,]+)\))*/g;
|
||||
var pos = 0;
|
||||
var match;
|
||||
var mciCode;
|
||||
var args;
|
||||
var id;
|
||||
function parseMCI(buffer) {
|
||||
// :TODO: move this to "constants" seciton @ top
|
||||
var mciRe = /%([A-Z]{2})([0-9]{1,2})?(?:\(([0-9A-Za-z,]+)\))*/g;
|
||||
var pos = 0;
|
||||
var match;
|
||||
var mciCode;
|
||||
var args;
|
||||
var id;
|
||||
|
||||
do {
|
||||
pos = mciRe.lastIndex;
|
||||
match = mciRe.exec(buffer);
|
||||
do {
|
||||
pos = mciRe.lastIndex;
|
||||
match = mciRe.exec(buffer);
|
||||
|
||||
if(null !== match) {
|
||||
if(match.index > pos) {
|
||||
literal(buffer.slice(pos, match.index));
|
||||
}
|
||||
if(null !== match) {
|
||||
if(match.index > pos) {
|
||||
literal(buffer.slice(pos, match.index));
|
||||
}
|
||||
|
||||
mciCode = match[1];
|
||||
id = match[2] || null;
|
||||
mciCode = match[1];
|
||||
id = match[2] || null;
|
||||
|
||||
if(match[3]) {
|
||||
args = match[3].split(',');
|
||||
} else {
|
||||
args = [];
|
||||
}
|
||||
if(match[3]) {
|
||||
args = match[3].split(',');
|
||||
} else {
|
||||
args = [];
|
||||
}
|
||||
|
||||
// if MCI codes are changing, save off the current color
|
||||
var fullMciCode = mciCode + (id || '');
|
||||
if(self.lastMciCode !== fullMciCode) {
|
||||
// if MCI codes are changing, save off the current color
|
||||
var fullMciCode = mciCode + (id || '');
|
||||
if(self.lastMciCode !== fullMciCode) {
|
||||
|
||||
self.lastMciCode = fullMciCode;
|
||||
self.lastMciCode = fullMciCode;
|
||||
|
||||
self.graphicRenditionForErase = _.clone(self.graphicRendition);
|
||||
}
|
||||
self.graphicRenditionForErase = _.clone(self.graphicRendition);
|
||||
}
|
||||
|
||||
|
||||
self.emit('mci', {
|
||||
mci : mciCode,
|
||||
id : id ? parseInt(id, 10) : null,
|
||||
args : args,
|
||||
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
|
||||
});
|
||||
self.emit('mci', {
|
||||
mci : mciCode,
|
||||
id : id ? parseInt(id, 10) : null,
|
||||
args : args,
|
||||
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
|
||||
});
|
||||
|
||||
if(self.mciReplaceChar.length > 0) {
|
||||
const sgrCtrl = ansi.getSGRFromGraphicRendition(self.graphicRenditionForErase);
|
||||
if(self.mciReplaceChar.length > 0) {
|
||||
const sgrCtrl = ansi.getSGRFromGraphicRendition(self.graphicRenditionForErase);
|
||||
|
||||
self.emit('control', sgrCtrl, 'm', sgrCtrl.slice(2).split(/[;m]/).slice(0, 3));
|
||||
self.emit('control', sgrCtrl, 'm', sgrCtrl.slice(2).split(/[;m]/).slice(0, 3));
|
||||
|
||||
literal(new Array(match[0].length + 1).join(self.mciReplaceChar));
|
||||
} else {
|
||||
literal(match[0]);
|
||||
}
|
||||
}
|
||||
literal(new Array(match[0].length + 1).join(self.mciReplaceChar));
|
||||
} else {
|
||||
literal(match[0]);
|
||||
}
|
||||
}
|
||||
|
||||
} while(0 !== mciRe.lastIndex);
|
||||
} while(0 !== mciRe.lastIndex);
|
||||
|
||||
if(pos < buffer.length) {
|
||||
literal(buffer.slice(pos));
|
||||
}
|
||||
}
|
||||
if(pos < buffer.length) {
|
||||
literal(buffer.slice(pos));
|
||||
}
|
||||
}
|
||||
|
||||
self.reset = function(input) {
|
||||
self.parseState = {
|
||||
// ignore anything past EOF marker, if any
|
||||
buffer : input.split(String.fromCharCode(0x1a), 1)[0],
|
||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||
stop : false,
|
||||
};
|
||||
};
|
||||
self.reset = function(input) {
|
||||
self.parseState = {
|
||||
// ignore anything past EOF marker, if any
|
||||
buffer : input.split(String.fromCharCode(0x1a), 1)[0],
|
||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||
stop : false,
|
||||
};
|
||||
};
|
||||
|
||||
self.stop = function() {
|
||||
self.parseState.stop = true;
|
||||
};
|
||||
self.stop = function() {
|
||||
self.parseState.stop = true;
|
||||
};
|
||||
|
||||
self.parse = function(input) {
|
||||
if(input) {
|
||||
self.reset(input);
|
||||
}
|
||||
self.parse = function(input) {
|
||||
if(input) {
|
||||
self.reset(input);
|
||||
}
|
||||
|
||||
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||
var pos;
|
||||
var match;
|
||||
var opCode;
|
||||
var args;
|
||||
var re = self.parseState.re;
|
||||
var buffer = self.parseState.buffer;
|
||||
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||
var pos;
|
||||
var match;
|
||||
var opCode;
|
||||
var args;
|
||||
var re = self.parseState.re;
|
||||
var buffer = self.parseState.buffer;
|
||||
|
||||
self.parseState.stop = false;
|
||||
self.parseState.stop = false;
|
||||
|
||||
do {
|
||||
if(self.parseState.stop) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
if(self.parseState.stop) {
|
||||
return;
|
||||
}
|
||||
|
||||
pos = re.lastIndex;
|
||||
match = re.exec(buffer);
|
||||
pos = re.lastIndex;
|
||||
match = re.exec(buffer);
|
||||
|
||||
if(null !== match) {
|
||||
if(match.index > pos) {
|
||||
parseMCI(buffer.slice(pos, match.index));
|
||||
}
|
||||
if(null !== match) {
|
||||
if(match.index > pos) {
|
||||
parseMCI(buffer.slice(pos, match.index));
|
||||
}
|
||||
|
||||
opCode = match[2];
|
||||
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
|
||||
opCode = match[2];
|
||||
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
|
||||
|
||||
escape(opCode, args);
|
||||
escape(opCode, args);
|
||||
|
||||
//self.emit('chunk', match[0]);
|
||||
self.emit('control', match[0], opCode, args);
|
||||
}
|
||||
} while(0 !== re.lastIndex);
|
||||
//self.emit('chunk', match[0]);
|
||||
self.emit('control', match[0], opCode, args);
|
||||
}
|
||||
} while(0 !== re.lastIndex);
|
||||
|
||||
if(pos < buffer.length) {
|
||||
var lastBit = buffer.slice(pos);
|
||||
if(pos < buffer.length) {
|
||||
var lastBit = buffer.slice(pos);
|
||||
|
||||
// :TODO: check for various ending LF's, not just DOS \r\n
|
||||
if('\r\n' === lastBit.slice(-2).toString()) {
|
||||
switch(self.trailingLF) {
|
||||
case 'default' :
|
||||
//
|
||||
// Default is to *not* omit the trailing LF
|
||||
// if we're going to end on termHeight
|
||||
//
|
||||
if(this.termHeight === self.row) {
|
||||
lastBit = lastBit.slice(0, -2);
|
||||
}
|
||||
break;
|
||||
// :TODO: check for various ending LF's, not just DOS \r\n
|
||||
if('\r\n' === lastBit.slice(-2).toString()) {
|
||||
switch(self.trailingLF) {
|
||||
case 'default' :
|
||||
//
|
||||
// Default is to *not* omit the trailing LF
|
||||
// if we're going to end on termHeight
|
||||
//
|
||||
if(this.termHeight === self.row) {
|
||||
lastBit = lastBit.slice(0, -2);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'omit' :
|
||||
case 'no' :
|
||||
case false :
|
||||
lastBit = lastBit.slice(0, -2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
case 'omit' :
|
||||
case 'no' :
|
||||
case false :
|
||||
lastBit = lastBit.slice(0, -2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parseMCI(lastBit);
|
||||
}
|
||||
parseMCI(lastBit);
|
||||
}
|
||||
|
||||
self.emit('complete');
|
||||
};
|
||||
self.emit('complete');
|
||||
};
|
||||
|
||||
/*
|
||||
/*
|
||||
self.parse = function(buffer, savedRe) {
|
||||
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||
// :TODO: move this to "constants" section @ top
|
||||
|
@ -329,164 +329,164 @@ function ANSIEscapeParser(options) {
|
|||
};
|
||||
*/
|
||||
|
||||
function escape(opCode, args) {
|
||||
let arg;
|
||||
function escape(opCode, args) {
|
||||
let arg;
|
||||
|
||||
switch(opCode) {
|
||||
// cursor up
|
||||
case 'A' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(0, -arg);
|
||||
break;
|
||||
switch(opCode) {
|
||||
// cursor up
|
||||
case 'A' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(0, -arg);
|
||||
break;
|
||||
|
||||
// cursor down
|
||||
case 'B' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(0, arg);
|
||||
break;
|
||||
// cursor down
|
||||
case 'B' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(0, arg);
|
||||
break;
|
||||
|
||||
// cursor forward/right
|
||||
case 'C' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(arg, 0);
|
||||
break;
|
||||
// cursor forward/right
|
||||
case 'C' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(arg, 0);
|
||||
break;
|
||||
|
||||
// cursor back/left
|
||||
case 'D' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(-arg, 0);
|
||||
break;
|
||||
// cursor back/left
|
||||
case 'D' :
|
||||
//arg = args[0] || 1;
|
||||
arg = isNaN(args[0]) ? 1 : args[0];
|
||||
self.moveCursor(-arg, 0);
|
||||
break;
|
||||
|
||||
case 'f' : // horiz & vertical
|
||||
case 'H' : // cursor position
|
||||
//self.row = args[0] || 1;
|
||||
//self.column = args[1] || 1;
|
||||
self.row = isNaN(args[0]) ? 1 : args[0];
|
||||
self.column = isNaN(args[1]) ? 1 : args[1];
|
||||
//self.rowUpdated();
|
||||
self.positionUpdated();
|
||||
break;
|
||||
case 'f' : // horiz & vertical
|
||||
case 'H' : // cursor position
|
||||
//self.row = args[0] || 1;
|
||||
//self.column = args[1] || 1;
|
||||
self.row = isNaN(args[0]) ? 1 : args[0];
|
||||
self.column = isNaN(args[1]) ? 1 : args[1];
|
||||
//self.rowUpdated();
|
||||
self.positionUpdated();
|
||||
break;
|
||||
|
||||
// save position
|
||||
case 's' :
|
||||
self.saveCursorPosition();
|
||||
break;
|
||||
// save position
|
||||
case 's' :
|
||||
self.saveCursorPosition();
|
||||
break;
|
||||
|
||||
// restore position
|
||||
case 'u' :
|
||||
self.restoreCursorPosition();
|
||||
break;
|
||||
// restore position
|
||||
case 'u' :
|
||||
self.restoreCursorPosition();
|
||||
break;
|
||||
|
||||
// set graphic rendition
|
||||
case 'm' :
|
||||
self.graphicRendition.reset = false;
|
||||
// set graphic rendition
|
||||
case 'm' :
|
||||
self.graphicRendition.reset = false;
|
||||
|
||||
for(let i = 0, len = args.length; i < len; ++i) {
|
||||
arg = args[i];
|
||||
for(let i = 0, len = args.length; i < len; ++i) {
|
||||
arg = args[i];
|
||||
|
||||
if(ANSIEscapeParser.foregroundColors[arg]) {
|
||||
self.graphicRendition.fg = arg;
|
||||
} else if(ANSIEscapeParser.backgroundColors[arg]) {
|
||||
self.graphicRendition.bg = arg;
|
||||
} else if(ANSIEscapeParser.styles[arg]) {
|
||||
switch(arg) {
|
||||
case 0 :
|
||||
// clear out everything
|
||||
delete self.graphicRendition.intensity;
|
||||
delete self.graphicRendition.underline;
|
||||
delete self.graphicRendition.blink;
|
||||
delete self.graphicRendition.negative;
|
||||
delete self.graphicRendition.invisible;
|
||||
if(ANSIEscapeParser.foregroundColors[arg]) {
|
||||
self.graphicRendition.fg = arg;
|
||||
} else if(ANSIEscapeParser.backgroundColors[arg]) {
|
||||
self.graphicRendition.bg = arg;
|
||||
} else if(ANSIEscapeParser.styles[arg]) {
|
||||
switch(arg) {
|
||||
case 0 :
|
||||
// clear out everything
|
||||
delete self.graphicRendition.intensity;
|
||||
delete self.graphicRendition.underline;
|
||||
delete self.graphicRendition.blink;
|
||||
delete self.graphicRendition.negative;
|
||||
delete self.graphicRendition.invisible;
|
||||
|
||||
delete self.graphicRendition.fg;
|
||||
delete self.graphicRendition.bg;
|
||||
delete self.graphicRendition.fg;
|
||||
delete self.graphicRendition.bg;
|
||||
|
||||
self.graphicRendition.reset = true;
|
||||
//self.graphicRendition.fg = 39;
|
||||
//self.graphicRendition.bg = 49;
|
||||
break;
|
||||
self.graphicRendition.reset = true;
|
||||
//self.graphicRendition.fg = 39;
|
||||
//self.graphicRendition.bg = 49;
|
||||
break;
|
||||
|
||||
case 1 :
|
||||
case 2 :
|
||||
case 22 :
|
||||
self.graphicRendition.intensity = arg;
|
||||
break;
|
||||
case 1 :
|
||||
case 2 :
|
||||
case 22 :
|
||||
self.graphicRendition.intensity = arg;
|
||||
break;
|
||||
|
||||
case 4 :
|
||||
case 24 :
|
||||
self.graphicRendition.underline = arg;
|
||||
break;
|
||||
case 4 :
|
||||
case 24 :
|
||||
self.graphicRendition.underline = arg;
|
||||
break;
|
||||
|
||||
case 5 :
|
||||
case 6 :
|
||||
case 25 :
|
||||
self.graphicRendition.blink = arg;
|
||||
break;
|
||||
case 5 :
|
||||
case 6 :
|
||||
case 25 :
|
||||
self.graphicRendition.blink = arg;
|
||||
break;
|
||||
|
||||
case 7 :
|
||||
case 27 :
|
||||
self.graphicRendition.negative = arg;
|
||||
break;
|
||||
case 7 :
|
||||
case 27 :
|
||||
self.graphicRendition.negative = arg;
|
||||
break;
|
||||
|
||||
case 8 :
|
||||
case 28 :
|
||||
self.graphicRendition.invisible = arg;
|
||||
break;
|
||||
case 8 :
|
||||
case 28 :
|
||||
self.graphicRendition.invisible = arg;
|
||||
break;
|
||||
|
||||
default :
|
||||
Log.trace( { attribute : arg }, 'Unknown attribute while parsing ANSI');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
default :
|
||||
Log.trace( { attribute : arg }, 'Unknown attribute while parsing ANSI');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.emit('sgr update', self.graphicRendition);
|
||||
break; // m
|
||||
self.emit('sgr update', self.graphicRendition);
|
||||
break; // m
|
||||
|
||||
// :TODO: s, u, K
|
||||
// :TODO: s, u, K
|
||||
|
||||
// erase display/screen
|
||||
case 'J' :
|
||||
// :TODO: Handle other 'J' types!
|
||||
if(2 === args[0]) {
|
||||
self.clearScreen();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// erase display/screen
|
||||
case 'J' :
|
||||
// :TODO: Handle other 'J' types!
|
||||
if(2 === args[0]) {
|
||||
self.clearScreen();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
util.inherits(ANSIEscapeParser, events.EventEmitter);
|
||||
|
||||
ANSIEscapeParser.foregroundColors = {
|
||||
30 : 'black',
|
||||
31 : 'red',
|
||||
32 : 'green',
|
||||
33 : 'yellow',
|
||||
34 : 'blue',
|
||||
35 : 'magenta',
|
||||
36 : 'cyan',
|
||||
37 : 'white',
|
||||
39 : 'default', // same as white for most implementations
|
||||
30 : 'black',
|
||||
31 : 'red',
|
||||
32 : 'green',
|
||||
33 : 'yellow',
|
||||
34 : 'blue',
|
||||
35 : 'magenta',
|
||||
36 : 'cyan',
|
||||
37 : 'white',
|
||||
39 : 'default', // same as white for most implementations
|
||||
|
||||
90 : 'grey'
|
||||
90 : 'grey'
|
||||
};
|
||||
Object.freeze(ANSIEscapeParser.foregroundColors);
|
||||
|
||||
ANSIEscapeParser.backgroundColors = {
|
||||
40 : 'black',
|
||||
41 : 'red',
|
||||
42 : 'green',
|
||||
43 : 'yellow',
|
||||
44 : 'blue',
|
||||
45 : 'magenta',
|
||||
46 : 'cyan',
|
||||
47 : 'white',
|
||||
49 : 'default', // same as black for most implementations
|
||||
40 : 'black',
|
||||
41 : 'red',
|
||||
42 : 'green',
|
||||
43 : 'yellow',
|
||||
44 : 'blue',
|
||||
45 : 'magenta',
|
||||
46 : 'cyan',
|
||||
47 : 'white',
|
||||
49 : 'default', // same as black for most implementations
|
||||
};
|
||||
Object.freeze(ANSIEscapeParser.backgroundColors);
|
||||
|
||||
|
@ -501,24 +501,24 @@ Object.freeze(ANSIEscapeParser.backgroundColors);
|
|||
// can be grouped by concept here in code.
|
||||
//
|
||||
ANSIEscapeParser.styles = {
|
||||
0 : 'default', // Everything disabled
|
||||
0 : 'default', // Everything disabled
|
||||
|
||||
1 : 'intensityBright', // aka bold
|
||||
2 : 'intensityDim',
|
||||
22 : 'intensityNormal',
|
||||
1 : 'intensityBright', // aka bold
|
||||
2 : 'intensityDim',
|
||||
22 : 'intensityNormal',
|
||||
|
||||
4 : 'underlineOn', // Not supported by most BBS-like terminals
|
||||
24 : 'underlineOff', // Not supported by most BBS-like terminals
|
||||
4 : 'underlineOn', // Not supported by most BBS-like terminals
|
||||
24 : 'underlineOff', // Not supported by most BBS-like terminals
|
||||
|
||||
5 : 'blinkSlow', // blinkSlow & blinkFast are generally treated the same
|
||||
6 : 'blinkFast', // blinkSlow & blinkFast are generally treated the same
|
||||
25 : 'blinkOff',
|
||||
5 : 'blinkSlow', // blinkSlow & blinkFast are generally treated the same
|
||||
6 : 'blinkFast', // blinkSlow & blinkFast are generally treated the same
|
||||
25 : 'blinkOff',
|
||||
|
||||
7 : 'negativeImageOn', // Generally not supported or treated as "reverse FG & BG"
|
||||
27 : 'negativeImageOff', // Generally not supported or treated as "reverse FG & BG"
|
||||
7 : 'negativeImageOn', // Generally not supported or treated as "reverse FG & BG"
|
||||
27 : 'negativeImageOff', // Generally not supported or treated as "reverse FG & BG"
|
||||
|
||||
8 : 'invisibleOn', // FG set to BG
|
||||
28 : 'invisibleOff', // Not supported by most BBS-like terminals
|
||||
8 : 'invisibleOn', // FG set to BG
|
||||
28 : 'invisibleOff', // Not supported by most BBS-like terminals
|
||||
};
|
||||
Object.freeze(ANSIEscapeParser.styles);
|
||||
|
||||
|
|
|
@ -5,216 +5,216 @@
|
|||
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
|
||||
const ANSI = require('./ansi_term.js');
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
renderStringLength
|
||||
splitTextAtTerms,
|
||||
renderStringLength
|
||||
} = require('./string_util.js');
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
||||
module.exports = function ansiPrep(input, options, cb) {
|
||||
if(!input) {
|
||||
return cb(null, '');
|
||||
}
|
||||
if(!input) {
|
||||
return cb(null, '');
|
||||
}
|
||||
|
||||
options.termWidth = options.termWidth || 80;
|
||||
options.termHeight = options.termHeight || 25;
|
||||
options.cols = options.cols || options.termWidth || 80;
|
||||
options.rows = options.rows || options.termHeight || 'auto';
|
||||
options.startCol = options.startCol || 1;
|
||||
options.exportMode = options.exportMode || false;
|
||||
options.fillLines = _.get(options, 'fillLines', true);
|
||||
options.indent = options.indent || 0;
|
||||
options.termWidth = options.termWidth || 80;
|
||||
options.termHeight = options.termHeight || 25;
|
||||
options.cols = options.cols || options.termWidth || 80;
|
||||
options.rows = options.rows || options.termHeight || 'auto';
|
||||
options.startCol = options.startCol || 1;
|
||||
options.exportMode = options.exportMode || false;
|
||||
options.fillLines = _.get(options, 'fillLines', true);
|
||||
options.indent = options.indent || 0;
|
||||
|
||||
// in auto we start out at 25 rows, but can always expand for more
|
||||
const canvas = Array.from( { length : 'auto' === options.rows ? 25 : options.rows }, () => Array.from( { length : options.cols}, () => new Object() ) );
|
||||
const parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } );
|
||||
// in auto we start out at 25 rows, but can always expand for more
|
||||
const canvas = Array.from( { length : 'auto' === options.rows ? 25 : options.rows }, () => Array.from( { length : options.cols}, () => new Object() ) );
|
||||
const parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } );
|
||||
|
||||
const state = {
|
||||
row : 0,
|
||||
col : 0,
|
||||
};
|
||||
const state = {
|
||||
row : 0,
|
||||
col : 0,
|
||||
};
|
||||
|
||||
let lastRow = 0;
|
||||
let lastRow = 0;
|
||||
|
||||
function ensureRow(row) {
|
||||
if(canvas[row]) {
|
||||
return;
|
||||
}
|
||||
function ensureRow(row) {
|
||||
if(canvas[row]) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvas[row] = Array.from( { length : options.cols}, () => new Object() );
|
||||
}
|
||||
canvas[row] = Array.from( { length : options.cols}, () => new Object() );
|
||||
}
|
||||
|
||||
parser.on('position update', (row, col) => {
|
||||
state.row = row - 1;
|
||||
state.col = col - 1;
|
||||
parser.on('position update', (row, col) => {
|
||||
state.row = row - 1;
|
||||
state.col = col - 1;
|
||||
|
||||
if(0 === state.col) {
|
||||
state.initialSgr = state.lastSgr;
|
||||
}
|
||||
if(0 === state.col) {
|
||||
state.initialSgr = state.lastSgr;
|
||||
}
|
||||
|
||||
lastRow = Math.max(state.row, lastRow);
|
||||
});
|
||||
lastRow = Math.max(state.row, lastRow);
|
||||
});
|
||||
|
||||
parser.on('literal', literal => {
|
||||
//
|
||||
// CR/LF are handled for 'position update'; we don't need the chars themselves
|
||||
//
|
||||
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||
parser.on('literal', literal => {
|
||||
//
|
||||
// CR/LF are handled for 'position update'; we don't need the chars themselves
|
||||
//
|
||||
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||
|
||||
for(let c of literal) {
|
||||
if(state.col < options.cols && ('auto' === options.rows || state.row < options.rows)) {
|
||||
ensureRow(state.row);
|
||||
for(let c of literal) {
|
||||
if(state.col < options.cols && ('auto' === options.rows || state.row < options.rows)) {
|
||||
ensureRow(state.row);
|
||||
|
||||
if(0 === state.col) {
|
||||
canvas[state.row][state.col].initialSgr = state.initialSgr;
|
||||
}
|
||||
if(0 === state.col) {
|
||||
canvas[state.row][state.col].initialSgr = state.initialSgr;
|
||||
}
|
||||
|
||||
canvas[state.row][state.col].char = c;
|
||||
canvas[state.row][state.col].char = c;
|
||||
|
||||
if(state.sgr) {
|
||||
canvas[state.row][state.col].sgr = _.clone(state.sgr);
|
||||
state.lastSgr = canvas[state.row][state.col].sgr;
|
||||
state.sgr = null;
|
||||
}
|
||||
}
|
||||
if(state.sgr) {
|
||||
canvas[state.row][state.col].sgr = _.clone(state.sgr);
|
||||
state.lastSgr = canvas[state.row][state.col].sgr;
|
||||
state.sgr = null;
|
||||
}
|
||||
}
|
||||
|
||||
state.col += 1;
|
||||
}
|
||||
});
|
||||
state.col += 1;
|
||||
}
|
||||
});
|
||||
|
||||
parser.on('sgr update', sgr => {
|
||||
ensureRow(state.row);
|
||||
parser.on('sgr update', sgr => {
|
||||
ensureRow(state.row);
|
||||
|
||||
if(state.col < options.cols) {
|
||||
canvas[state.row][state.col].sgr = _.clone(sgr);
|
||||
state.lastSgr = canvas[state.row][state.col].sgr;
|
||||
} else {
|
||||
state.sgr = sgr;
|
||||
}
|
||||
});
|
||||
if(state.col < options.cols) {
|
||||
canvas[state.row][state.col].sgr = _.clone(sgr);
|
||||
state.lastSgr = canvas[state.row][state.col].sgr;
|
||||
} else {
|
||||
state.sgr = sgr;
|
||||
}
|
||||
});
|
||||
|
||||
function getLastPopulatedColumn(row) {
|
||||
let col = row.length;
|
||||
while(--col > 0) {
|
||||
if(row[col].char || row[col].sgr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return col;
|
||||
}
|
||||
function getLastPopulatedColumn(row) {
|
||||
let col = row.length;
|
||||
while(--col > 0) {
|
||||
if(row[col].char || row[col].sgr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
parser.on('complete', () => {
|
||||
let output = '';
|
||||
let line;
|
||||
let sgr;
|
||||
parser.on('complete', () => {
|
||||
let output = '';
|
||||
let line;
|
||||
let sgr;
|
||||
|
||||
canvas.slice(0, lastRow + 1).forEach(row => {
|
||||
const lastCol = getLastPopulatedColumn(row) + 1;
|
||||
canvas.slice(0, lastRow + 1).forEach(row => {
|
||||
const lastCol = getLastPopulatedColumn(row) + 1;
|
||||
|
||||
let i;
|
||||
line = options.indent ?
|
||||
output.length > 0 ? ' '.repeat(options.indent) : '' :
|
||||
'';
|
||||
let i;
|
||||
line = options.indent ?
|
||||
output.length > 0 ? ' '.repeat(options.indent) : '' :
|
||||
'';
|
||||
|
||||
for(i = 0; i < lastCol; ++i) {
|
||||
const col = row[i];
|
||||
for(i = 0; i < lastCol; ++i) {
|
||||
const col = row[i];
|
||||
|
||||
sgr = !options.asciiMode && 0 === i ?
|
||||
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' :
|
||||
'';
|
||||
sgr = !options.asciiMode && 0 === i ?
|
||||
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' :
|
||||
'';
|
||||
|
||||
if(!options.asciiMode && col.sgr) {
|
||||
sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
|
||||
}
|
||||
if(!options.asciiMode && col.sgr) {
|
||||
sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
|
||||
}
|
||||
|
||||
line += `${sgr}${col.char || ' '}`;
|
||||
}
|
||||
line += `${sgr}${col.char || ' '}`;
|
||||
}
|
||||
|
||||
output += line;
|
||||
output += line;
|
||||
|
||||
if(i < row.length) {
|
||||
output += `${options.asciiMode ? '' : ANSI.blackBG()}`;
|
||||
if(options.fillLines) {
|
||||
output += `${row.slice(i).map( () => ' ').join('')}`;//${lastSgr}`;
|
||||
}
|
||||
}
|
||||
if(i < row.length) {
|
||||
output += `${options.asciiMode ? '' : ANSI.blackBG()}`;
|
||||
if(options.fillLines) {
|
||||
output += `${row.slice(i).map( () => ' ').join('')}`;//${lastSgr}`;
|
||||
}
|
||||
}
|
||||
|
||||
if(options.startCol + i < options.termWidth || options.forceLineTerm) {
|
||||
output += '\r\n';
|
||||
}
|
||||
});
|
||||
if(options.startCol + i < options.termWidth || options.forceLineTerm) {
|
||||
output += '\r\n';
|
||||
}
|
||||
});
|
||||
|
||||
if(options.exportMode) {
|
||||
//
|
||||
// If we're in export mode, we do some additional hackery:
|
||||
//
|
||||
// * Hard wrap ALL lines at <= 79 *characters* (not visible columns)
|
||||
// if a line must wrap early, we'll place a ESC[A ESC[<N>C where <N>
|
||||
// represents chars to get back to the position we were previously at
|
||||
//
|
||||
// * Replace contig spaces with ESC[<N>C as well to save... space.
|
||||
//
|
||||
// :TODO: this would be better to do as part of the processing above, but this will do for now
|
||||
const MAX_CHARS = 79 - 8; // 79 max, - 8 for max ESC seq's we may prefix a line with
|
||||
let exportOutput = '';
|
||||
if(options.exportMode) {
|
||||
//
|
||||
// If we're in export mode, we do some additional hackery:
|
||||
//
|
||||
// * Hard wrap ALL lines at <= 79 *characters* (not visible columns)
|
||||
// if a line must wrap early, we'll place a ESC[A ESC[<N>C where <N>
|
||||
// represents chars to get back to the position we were previously at
|
||||
//
|
||||
// * Replace contig spaces with ESC[<N>C as well to save... space.
|
||||
//
|
||||
// :TODO: this would be better to do as part of the processing above, but this will do for now
|
||||
const MAX_CHARS = 79 - 8; // 79 max, - 8 for max ESC seq's we may prefix a line with
|
||||
let exportOutput = '';
|
||||
|
||||
let m;
|
||||
let afterSeq;
|
||||
let wantMore;
|
||||
let renderStart;
|
||||
let m;
|
||||
let afterSeq;
|
||||
let wantMore;
|
||||
let renderStart;
|
||||
|
||||
splitTextAtTerms(output).forEach(fullLine => {
|
||||
renderStart = 0;
|
||||
splitTextAtTerms(output).forEach(fullLine => {
|
||||
renderStart = 0;
|
||||
|
||||
while(fullLine.length > 0) {
|
||||
let splitAt;
|
||||
const ANSI_REGEXP = ANSI.getFullMatchRegExp();
|
||||
wantMore = true;
|
||||
while(fullLine.length > 0) {
|
||||
let splitAt;
|
||||
const ANSI_REGEXP = ANSI.getFullMatchRegExp();
|
||||
wantMore = true;
|
||||
|
||||
while((m = ANSI_REGEXP.exec(fullLine))) {
|
||||
afterSeq = m.index + m[0].length;
|
||||
while((m = ANSI_REGEXP.exec(fullLine))) {
|
||||
afterSeq = m.index + m[0].length;
|
||||
|
||||
if(afterSeq < MAX_CHARS) {
|
||||
// after current seq
|
||||
splitAt = afterSeq;
|
||||
} else {
|
||||
if(m.index < MAX_CHARS) {
|
||||
// before last found seq
|
||||
splitAt = m.index;
|
||||
wantMore = false; // can't eat up any more
|
||||
}
|
||||
if(afterSeq < MAX_CHARS) {
|
||||
// after current seq
|
||||
splitAt = afterSeq;
|
||||
} else {
|
||||
if(m.index < MAX_CHARS) {
|
||||
// before last found seq
|
||||
splitAt = m.index;
|
||||
wantMore = false; // can't eat up any more
|
||||
}
|
||||
|
||||
break; // seq's beyond this point are >= MAX_CHARS
|
||||
}
|
||||
}
|
||||
break; // seq's beyond this point are >= MAX_CHARS
|
||||
}
|
||||
}
|
||||
|
||||
if(splitAt) {
|
||||
if(wantMore) {
|
||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||
}
|
||||
} else {
|
||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||
}
|
||||
if(splitAt) {
|
||||
if(wantMore) {
|
||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||
}
|
||||
} else {
|
||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||
}
|
||||
|
||||
const part = fullLine.slice(0, splitAt);
|
||||
fullLine = fullLine.slice(splitAt);
|
||||
renderStart += renderStringLength(part);
|
||||
exportOutput += `${part}\r\n`;
|
||||
const part = fullLine.slice(0, splitAt);
|
||||
fullLine = fullLine.slice(splitAt);
|
||||
renderStart += renderStringLength(part);
|
||||
exportOutput += `${part}\r\n`;
|
||||
|
||||
if(fullLine.length > 0) { // more to go for this line?
|
||||
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
||||
} else {
|
||||
exportOutput += ANSI.up();
|
||||
}
|
||||
}
|
||||
});
|
||||
if(fullLine.length > 0) { // more to go for this line?
|
||||
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
||||
} else {
|
||||
exportOutput += ANSI.up();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return cb(null, exportOutput);
|
||||
}
|
||||
return cb(null, exportOutput);
|
||||
}
|
||||
|
||||
return cb(null, output);
|
||||
});
|
||||
return cb(null, output);
|
||||
});
|
||||
|
||||
parser.parse(input);
|
||||
parser.parse(input);
|
||||
};
|
||||
|
|
|
@ -65,86 +65,86 @@ exports.vtxHyperlink = vtxHyperlink;
|
|||
const ESC_CSI = '\u001b[';
|
||||
|
||||
const CONTROL = {
|
||||
up : 'A',
|
||||
down : 'B',
|
||||
up : 'A',
|
||||
down : 'B',
|
||||
|
||||
forward : 'C',
|
||||
right : 'C',
|
||||
forward : 'C',
|
||||
right : 'C',
|
||||
|
||||
back : 'D',
|
||||
left : 'D',
|
||||
back : 'D',
|
||||
left : 'D',
|
||||
|
||||
nextLine : 'E',
|
||||
prevLine : 'F',
|
||||
horizAbsolute : 'G',
|
||||
nextLine : 'E',
|
||||
prevLine : 'F',
|
||||
horizAbsolute : 'G',
|
||||
|
||||
//
|
||||
// CSI [ p1 ] J
|
||||
// Erase in Page / Erase Data
|
||||
// Defaults: p1 = 0
|
||||
// Erases from the current screen according to the value of p1
|
||||
// 0 - Erase from the current position to the end of the screen.
|
||||
// 1 - Erase from the current position to the start of the screen.
|
||||
// 2 - Erase entire screen. As a violation of ECMA-048, also moves
|
||||
// the cursor to position 1/1 as a number of BBS programs assume
|
||||
// this behaviour.
|
||||
// Erased characters are set to the current attribute.
|
||||
//
|
||||
// Support:
|
||||
// * SyncTERM: Works as expected
|
||||
// * NetRunner: Always clears a screen *height* (e.g. 25) regardless of p1
|
||||
// and screen remainder
|
||||
//
|
||||
eraseData : 'J',
|
||||
//
|
||||
// CSI [ p1 ] J
|
||||
// Erase in Page / Erase Data
|
||||
// Defaults: p1 = 0
|
||||
// Erases from the current screen according to the value of p1
|
||||
// 0 - Erase from the current position to the end of the screen.
|
||||
// 1 - Erase from the current position to the start of the screen.
|
||||
// 2 - Erase entire screen. As a violation of ECMA-048, also moves
|
||||
// the cursor to position 1/1 as a number of BBS programs assume
|
||||
// this behaviour.
|
||||
// Erased characters are set to the current attribute.
|
||||
//
|
||||
// Support:
|
||||
// * SyncTERM: Works as expected
|
||||
// * NetRunner: Always clears a screen *height* (e.g. 25) regardless of p1
|
||||
// and screen remainder
|
||||
//
|
||||
eraseData : 'J',
|
||||
|
||||
eraseLine : 'K',
|
||||
insertLine : 'L',
|
||||
eraseLine : 'K',
|
||||
insertLine : 'L',
|
||||
|
||||
//
|
||||
// CSI [ p1 ] M
|
||||
// Delete Line(s) / "ANSI" Music
|
||||
// Defaults: p1 = 1
|
||||
// Deletes the current line and the p1 - 1 lines after it scrolling the
|
||||
// first non-deleted line up to the current line and filling the newly
|
||||
// empty lines at the end of the screen with the current attribute.
|
||||
// If "ANSI" Music is fully enabled (CSI = 2 M), performs "ANSI" music
|
||||
// instead.
|
||||
// See "ANSI" MUSIC section for more details.
|
||||
//
|
||||
// Support:
|
||||
// * SyncTERM: Works as expected
|
||||
// * NetRunner:
|
||||
//
|
||||
// General Notes:
|
||||
// See also notes in bansi.txt and cterm.txt about the various
|
||||
// incompatibilities & oddities around this sequence. ANSI-BBS
|
||||
// states that it *should* work with any value of p1.
|
||||
//
|
||||
deleteLine : 'M',
|
||||
ansiMusic : 'M',
|
||||
//
|
||||
// CSI [ p1 ] M
|
||||
// Delete Line(s) / "ANSI" Music
|
||||
// Defaults: p1 = 1
|
||||
// Deletes the current line and the p1 - 1 lines after it scrolling the
|
||||
// first non-deleted line up to the current line and filling the newly
|
||||
// empty lines at the end of the screen with the current attribute.
|
||||
// If "ANSI" Music is fully enabled (CSI = 2 M), performs "ANSI" music
|
||||
// instead.
|
||||
// See "ANSI" MUSIC section for more details.
|
||||
//
|
||||
// Support:
|
||||
// * SyncTERM: Works as expected
|
||||
// * NetRunner:
|
||||
//
|
||||
// General Notes:
|
||||
// See also notes in bansi.txt and cterm.txt about the various
|
||||
// incompatibilities & oddities around this sequence. ANSI-BBS
|
||||
// states that it *should* work with any value of p1.
|
||||
//
|
||||
deleteLine : 'M',
|
||||
ansiMusic : 'M',
|
||||
|
||||
scrollUp : 'S',
|
||||
scrollDown : 'T',
|
||||
setScrollRegion : 'r',
|
||||
savePos : 's',
|
||||
restorePos : 'u',
|
||||
queryPos : '6n',
|
||||
queryScreenSize : '255n', // See bansi.txt
|
||||
goto : 'H', // row Pr, column Pc -- same as f
|
||||
gotoAlt : 'f', // same as H
|
||||
scrollUp : 'S',
|
||||
scrollDown : 'T',
|
||||
setScrollRegion : 'r',
|
||||
savePos : 's',
|
||||
restorePos : 'u',
|
||||
queryPos : '6n',
|
||||
queryScreenSize : '255n', // See bansi.txt
|
||||
goto : 'H', // row Pr, column Pc -- same as f
|
||||
gotoAlt : 'f', // same as H
|
||||
|
||||
blinkToBrightIntensity : '?33h',
|
||||
blinkNormal : '?33l',
|
||||
blinkToBrightIntensity : '?33h',
|
||||
blinkNormal : '?33l',
|
||||
|
||||
emulationSpeed : '*r', // Set output emulation speed. See cterm.txt
|
||||
emulationSpeed : '*r', // Set output emulation speed. See cterm.txt
|
||||
|
||||
hideCursor : '?25l', // Nonstandard - cterm.txt
|
||||
showCursor : '?25h', // Nonstandard - cterm.txt
|
||||
hideCursor : '?25l', // Nonstandard - cterm.txt
|
||||
showCursor : '?25h', // Nonstandard - cterm.txt
|
||||
|
||||
queryDeviceAttributes : 'c', // Nonstandard - cterm.txt
|
||||
queryDeviceAttributes : 'c', // Nonstandard - cterm.txt
|
||||
|
||||
// :TODO: see https://code.google.com/p/conemu-maximus5/wiki/AnsiEscapeCodes
|
||||
// apparently some terms can report screen size and text area via 18t and 19t
|
||||
// :TODO: see https://code.google.com/p/conemu-maximus5/wiki/AnsiEscapeCodes
|
||||
// apparently some terms can report screen size and text area via 18t and 19t
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -152,49 +152,49 @@ const CONTROL = {
|
|||
// See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
|
||||
//
|
||||
const SGRValues = {
|
||||
reset : 0,
|
||||
bold : 1,
|
||||
dim : 2,
|
||||
blink : 5,
|
||||
fastBlink : 6,
|
||||
negative : 7,
|
||||
hidden : 8,
|
||||
reset : 0,
|
||||
bold : 1,
|
||||
dim : 2,
|
||||
blink : 5,
|
||||
fastBlink : 6,
|
||||
negative : 7,
|
||||
hidden : 8,
|
||||
|
||||
normal : 22, //
|
||||
steady : 25,
|
||||
positive : 27,
|
||||
normal : 22, //
|
||||
steady : 25,
|
||||
positive : 27,
|
||||
|
||||
black : 30,
|
||||
red : 31,
|
||||
green : 32,
|
||||
yellow : 33,
|
||||
blue : 34,
|
||||
magenta : 35,
|
||||
cyan : 36,
|
||||
white : 37,
|
||||
black : 30,
|
||||
red : 31,
|
||||
green : 32,
|
||||
yellow : 33,
|
||||
blue : 34,
|
||||
magenta : 35,
|
||||
cyan : 36,
|
||||
white : 37,
|
||||
|
||||
blackBG : 40,
|
||||
redBG : 41,
|
||||
greenBG : 42,
|
||||
yellowBG : 43,
|
||||
blueBG : 44,
|
||||
magentaBG : 45,
|
||||
cyanBG : 46,
|
||||
whiteBG : 47,
|
||||
blackBG : 40,
|
||||
redBG : 41,
|
||||
greenBG : 42,
|
||||
yellowBG : 43,
|
||||
blueBG : 44,
|
||||
magentaBG : 45,
|
||||
cyanBG : 46,
|
||||
whiteBG : 47,
|
||||
};
|
||||
|
||||
function getFullMatchRegExp(flags = 'g') {
|
||||
// :TODO: expand this a bit - see strip-ansi/etc.
|
||||
// :TODO: \u009b ?
|
||||
return new RegExp(/[\u001b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/, flags); // eslint-disable-line no-control-regex
|
||||
// :TODO: expand this a bit - see strip-ansi/etc.
|
||||
// :TODO: \u009b ?
|
||||
return new RegExp(/[\u001b][[()#;?]*([0-9]{1,4}(?:;[0-9]{0,4})*)?([0-9A-ORZcf-npqrsuy=><])/, flags); // eslint-disable-line no-control-regex
|
||||
}
|
||||
|
||||
function getFGColorValue(name) {
|
||||
return SGRValues[name];
|
||||
return SGRValues[name];
|
||||
}
|
||||
|
||||
function getBGColorValue(name) {
|
||||
return SGRValues[name + 'BG'];
|
||||
return SGRValues[name + 'BG'];
|
||||
}
|
||||
|
||||
|
||||
|
@ -214,49 +214,49 @@ function getBGColorValue(name) {
|
|||
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
||||
//
|
||||
const SYNCTERM_FONT_AND_ENCODING_TABLE = [
|
||||
'cp437',
|
||||
'cp1251',
|
||||
'koi8_r',
|
||||
'iso8859_2',
|
||||
'iso8859_4',
|
||||
'cp866',
|
||||
'iso8859_9',
|
||||
'haik8',
|
||||
'iso8859_8',
|
||||
'koi8_u',
|
||||
'iso8859_15',
|
||||
'iso8859_4',
|
||||
'koi8_r_b',
|
||||
'iso8859_4',
|
||||
'iso8859_5',
|
||||
'ARMSCII_8',
|
||||
'iso8859_15',
|
||||
'cp850',
|
||||
'cp850',
|
||||
'cp885',
|
||||
'cp1251',
|
||||
'iso8859_7',
|
||||
'koi8-r_c',
|
||||
'iso8859_4',
|
||||
'iso8859_1',
|
||||
'cp866',
|
||||
'cp437',
|
||||
'cp866',
|
||||
'cp885',
|
||||
'cp866_u',
|
||||
'iso8859_1',
|
||||
'cp1131',
|
||||
'c64_upper',
|
||||
'c64_lower',
|
||||
'c128_upper',
|
||||
'c128_lower',
|
||||
'atari',
|
||||
'pot_noodle',
|
||||
'mo_soul',
|
||||
'microknight_plus',
|
||||
'topaz_plus',
|
||||
'microknight',
|
||||
'topaz',
|
||||
'cp437',
|
||||
'cp1251',
|
||||
'koi8_r',
|
||||
'iso8859_2',
|
||||
'iso8859_4',
|
||||
'cp866',
|
||||
'iso8859_9',
|
||||
'haik8',
|
||||
'iso8859_8',
|
||||
'koi8_u',
|
||||
'iso8859_15',
|
||||
'iso8859_4',
|
||||
'koi8_r_b',
|
||||
'iso8859_4',
|
||||
'iso8859_5',
|
||||
'ARMSCII_8',
|
||||
'iso8859_15',
|
||||
'cp850',
|
||||
'cp850',
|
||||
'cp885',
|
||||
'cp1251',
|
||||
'iso8859_7',
|
||||
'koi8-r_c',
|
||||
'iso8859_4',
|
||||
'iso8859_1',
|
||||
'cp866',
|
||||
'cp437',
|
||||
'cp866',
|
||||
'cp885',
|
||||
'cp866_u',
|
||||
'iso8859_1',
|
||||
'cp1131',
|
||||
'c64_upper',
|
||||
'c64_lower',
|
||||
'c128_upper',
|
||||
'c128_lower',
|
||||
'atari',
|
||||
'pot_noodle',
|
||||
'mo_soul',
|
||||
'microknight_plus',
|
||||
'topaz_plus',
|
||||
'microknight',
|
||||
'topaz',
|
||||
];
|
||||
|
||||
//
|
||||
|
@ -267,137 +267,137 @@ const SYNCTERM_FONT_AND_ENCODING_TABLE = [
|
|||
// replaced with '_' for lookup purposes.
|
||||
//
|
||||
const FONT_ALIAS_TO_SYNCTERM_MAP = {
|
||||
'cp437' : 'cp437',
|
||||
'ibm_vga' : 'cp437',
|
||||
'ibmpc' : 'cp437',
|
||||
'ibm_pc' : 'cp437',
|
||||
'pc' : 'cp437',
|
||||
'cp437_art' : 'cp437',
|
||||
'ibmpcart' : 'cp437',
|
||||
'ibmpc_art' : 'cp437',
|
||||
'ibm_pc_art' : 'cp437',
|
||||
'msdos_art' : 'cp437',
|
||||
'msdosart' : 'cp437',
|
||||
'pc_art' : 'cp437',
|
||||
'pcart' : 'cp437',
|
||||
'cp437' : 'cp437',
|
||||
'ibm_vga' : 'cp437',
|
||||
'ibmpc' : 'cp437',
|
||||
'ibm_pc' : 'cp437',
|
||||
'pc' : 'cp437',
|
||||
'cp437_art' : 'cp437',
|
||||
'ibmpcart' : 'cp437',
|
||||
'ibmpc_art' : 'cp437',
|
||||
'ibm_pc_art' : 'cp437',
|
||||
'msdos_art' : 'cp437',
|
||||
'msdosart' : 'cp437',
|
||||
'pc_art' : 'cp437',
|
||||
'pcart' : 'cp437',
|
||||
|
||||
'ibm_vga50' : 'cp437',
|
||||
'ibm_vga25g' : 'cp437',
|
||||
'ibm_ega' : 'cp437',
|
||||
'ibm_ega43' : 'cp437',
|
||||
'ibm_vga50' : 'cp437',
|
||||
'ibm_vga25g' : 'cp437',
|
||||
'ibm_ega' : 'cp437',
|
||||
'ibm_ega43' : 'cp437',
|
||||
|
||||
'topaz' : 'topaz',
|
||||
'amiga_topaz_1' : 'topaz',
|
||||
'amiga_topaz_1+' : 'topaz_plus',
|
||||
'topazplus' : 'topaz_plus',
|
||||
'topaz_plus' : 'topaz_plus',
|
||||
'amiga_topaz_2' : 'topaz',
|
||||
'amiga_topaz_2+' : 'topaz_plus',
|
||||
'topaz2plus' : 'topaz_plus',
|
||||
'topaz' : 'topaz',
|
||||
'amiga_topaz_1' : 'topaz',
|
||||
'amiga_topaz_1+' : 'topaz_plus',
|
||||
'topazplus' : 'topaz_plus',
|
||||
'topaz_plus' : 'topaz_plus',
|
||||
'amiga_topaz_2' : 'topaz',
|
||||
'amiga_topaz_2+' : 'topaz_plus',
|
||||
'topaz2plus' : 'topaz_plus',
|
||||
|
||||
'pot_noodle' : 'pot_noodle',
|
||||
'p0tnoodle' : 'pot_noodle',
|
||||
'amiga_p0t-noodle' : 'pot_noodle',
|
||||
'pot_noodle' : 'pot_noodle',
|
||||
'p0tnoodle' : 'pot_noodle',
|
||||
'amiga_p0t-noodle' : 'pot_noodle',
|
||||
|
||||
'mo_soul' : 'mo_soul',
|
||||
'mosoul' : 'mo_soul',
|
||||
'mO\'sOul' : 'mo_soul',
|
||||
'mo_soul' : 'mo_soul',
|
||||
'mosoul' : 'mo_soul',
|
||||
'mO\'sOul' : 'mo_soul',
|
||||
|
||||
'amiga_microknight' : 'microknight',
|
||||
'amiga_microknight+' : 'microknight_plus',
|
||||
'amiga_microknight' : 'microknight',
|
||||
'amiga_microknight+' : 'microknight_plus',
|
||||
|
||||
'atari' : 'atari',
|
||||
'atarist' : 'atari',
|
||||
'atari' : 'atari',
|
||||
'atarist' : 'atari',
|
||||
|
||||
};
|
||||
|
||||
function setSyncTERMFont(name, fontPage) {
|
||||
const p1 = miscUtil.valueWithDefault(fontPage, 0);
|
||||
const p1 = miscUtil.valueWithDefault(fontPage, 0);
|
||||
|
||||
assert(p1 >= 0 && p1 <= 3);
|
||||
assert(p1 >= 0 && p1 <= 3);
|
||||
|
||||
const p2 = SYNCTERM_FONT_AND_ENCODING_TABLE.indexOf(name);
|
||||
if(p2 > -1) {
|
||||
return `${ESC_CSI}${p1};${p2} D`;
|
||||
}
|
||||
const p2 = SYNCTERM_FONT_AND_ENCODING_TABLE.indexOf(name);
|
||||
if(p2 > -1) {
|
||||
return `${ESC_CSI}${p1};${p2} D`;
|
||||
}
|
||||
|
||||
return '';
|
||||
return '';
|
||||
}
|
||||
|
||||
function getSyncTERMFontFromAlias(alias) {
|
||||
return FONT_ALIAS_TO_SYNCTERM_MAP[alias.toLowerCase().replace(/ /g, '_')];
|
||||
return FONT_ALIAS_TO_SYNCTERM_MAP[alias.toLowerCase().replace(/ /g, '_')];
|
||||
}
|
||||
|
||||
function setSyncTermFontWithAlias(nameOrAlias) {
|
||||
nameOrAlias = getSyncTERMFontFromAlias(nameOrAlias) || nameOrAlias;
|
||||
return setSyncTERMFont(nameOrAlias);
|
||||
nameOrAlias = getSyncTERMFontFromAlias(nameOrAlias) || nameOrAlias;
|
||||
return setSyncTERMFont(nameOrAlias);
|
||||
}
|
||||
|
||||
const DEC_CURSOR_STYLE = {
|
||||
'blinking block' : 0,
|
||||
'default' : 1,
|
||||
'steady block' : 2,
|
||||
'blinking underline' : 3,
|
||||
'steady underline' : 4,
|
||||
'blinking bar' : 5,
|
||||
'steady bar' : 6,
|
||||
'blinking block' : 0,
|
||||
'default' : 1,
|
||||
'steady block' : 2,
|
||||
'blinking underline' : 3,
|
||||
'steady underline' : 4,
|
||||
'blinking bar' : 5,
|
||||
'steady bar' : 6,
|
||||
};
|
||||
|
||||
function setCursorStyle(cursorStyle) {
|
||||
const ps = DEC_CURSOR_STYLE[cursorStyle];
|
||||
if(ps) {
|
||||
return `${ESC_CSI}${ps} q`;
|
||||
}
|
||||
return '';
|
||||
const ps = DEC_CURSOR_STYLE[cursorStyle];
|
||||
if(ps) {
|
||||
return `${ESC_CSI}${ps} q`;
|
||||
}
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
// Create methods such as up(), nextLine(),...
|
||||
Object.keys(CONTROL).forEach(function onControlName(name) {
|
||||
const code = CONTROL[name];
|
||||
const code = CONTROL[name];
|
||||
|
||||
exports[name] = function() {
|
||||
let c = code;
|
||||
if(arguments.length > 0) {
|
||||
// arguments are array like -- we want an array
|
||||
c = Array.prototype.slice.call(arguments).map(Math.round).join(';') + code;
|
||||
}
|
||||
return `${ESC_CSI}${c}`;
|
||||
};
|
||||
exports[name] = function() {
|
||||
let c = code;
|
||||
if(arguments.length > 0) {
|
||||
// arguments are array like -- we want an array
|
||||
c = Array.prototype.slice.call(arguments).map(Math.round).join(';') + code;
|
||||
}
|
||||
return `${ESC_CSI}${c}`;
|
||||
};
|
||||
});
|
||||
|
||||
// Create various color methods such as white(), yellowBG(), reset(), ...
|
||||
Object.keys(SGRValues).forEach( name => {
|
||||
const code = SGRValues[name];
|
||||
const code = SGRValues[name];
|
||||
|
||||
exports[name] = function() {
|
||||
return `${ESC_CSI}${code}m`;
|
||||
};
|
||||
exports[name] = function() {
|
||||
return `${ESC_CSI}${code}m`;
|
||||
};
|
||||
});
|
||||
|
||||
function sgr() {
|
||||
//
|
||||
// - Allow an single array or variable number of arguments
|
||||
// - Each element can be either a integer or string found in SGRValues
|
||||
// which in turn maps to a integer
|
||||
//
|
||||
if(arguments.length <= 0) {
|
||||
return '';
|
||||
}
|
||||
//
|
||||
// - Allow an single array or variable number of arguments
|
||||
// - Each element can be either a integer or string found in SGRValues
|
||||
// which in turn maps to a integer
|
||||
//
|
||||
if(arguments.length <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let result = [];
|
||||
const args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||
let result = [];
|
||||
const args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||
|
||||
for(let i = 0; i < args.length; ++i) {
|
||||
const arg = args[i];
|
||||
if(_.isString(arg) && arg in SGRValues) {
|
||||
result.push(SGRValues[arg]);
|
||||
} else if(_.isNumber(arg)) {
|
||||
result.push(arg);
|
||||
}
|
||||
}
|
||||
for(let i = 0; i < args.length; ++i) {
|
||||
const arg = args[i];
|
||||
if(_.isString(arg) && arg in SGRValues) {
|
||||
result.push(SGRValues[arg]);
|
||||
} else if(_.isNumber(arg)) {
|
||||
result.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return `${ESC_CSI}${result.join(';')}m`;
|
||||
return `${ESC_CSI}${result.join(';')}m`;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -405,29 +405,29 @@ function sgr() {
|
|||
// to a ANSI SGR sequence.
|
||||
//
|
||||
function getSGRFromGraphicRendition(graphicRendition, initialReset) {
|
||||
let sgrSeq = [];
|
||||
let styleCount = 0;
|
||||
let sgrSeq = [];
|
||||
let styleCount = 0;
|
||||
|
||||
[ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach( s => {
|
||||
if(graphicRendition[s]) {
|
||||
sgrSeq.push(graphicRendition[s]);
|
||||
++styleCount;
|
||||
}
|
||||
});
|
||||
[ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach( s => {
|
||||
if(graphicRendition[s]) {
|
||||
sgrSeq.push(graphicRendition[s]);
|
||||
++styleCount;
|
||||
}
|
||||
});
|
||||
|
||||
if(graphicRendition.fg) {
|
||||
sgrSeq.push(graphicRendition.fg);
|
||||
}
|
||||
if(graphicRendition.fg) {
|
||||
sgrSeq.push(graphicRendition.fg);
|
||||
}
|
||||
|
||||
if(graphicRendition.bg) {
|
||||
sgrSeq.push(graphicRendition.bg);
|
||||
}
|
||||
if(graphicRendition.bg) {
|
||||
sgrSeq.push(graphicRendition.bg);
|
||||
}
|
||||
|
||||
if(0 === styleCount || initialReset) {
|
||||
sgrSeq.unshift(0);
|
||||
}
|
||||
if(0 === styleCount || initialReset) {
|
||||
sgrSeq.unshift(0);
|
||||
}
|
||||
|
||||
return sgr(sgrSeq);
|
||||
return sgr(sgrSeq);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -435,19 +435,19 @@ function getSGRFromGraphicRendition(graphicRendition, initialReset) {
|
|||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function clearScreen() {
|
||||
return exports.eraseData(2);
|
||||
return exports.eraseData(2);
|
||||
}
|
||||
|
||||
function resetScreen() {
|
||||
return `${exports.reset()}${exports.eraseData(2)}${exports.goHome()}`;
|
||||
return `${exports.reset()}${exports.eraseData(2)}${exports.goHome()}`;
|
||||
}
|
||||
|
||||
function normal() {
|
||||
return sgr( [ 'normal', 'reset' ] );
|
||||
return sgr( [ 'normal', 'reset' ] );
|
||||
}
|
||||
|
||||
function goHome() {
|
||||
return exports.goto(); // no params = home = 1,1
|
||||
return exports.goto(); // no params = home = 1,1
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -463,36 +463,36 @@ function goHome() {
|
|||
// and use term width -- generally 80 columns -- will display garbled!
|
||||
//
|
||||
function disableVT100LineWrapping() {
|
||||
return `${ESC_CSI}?7l`;
|
||||
return `${ESC_CSI}?7l`;
|
||||
}
|
||||
|
||||
function setEmulatedBaudRate(rate) {
|
||||
const speed = {
|
||||
unlimited : 0,
|
||||
off : 0,
|
||||
0 : 0,
|
||||
300 : 1,
|
||||
600 : 2,
|
||||
1200 : 3,
|
||||
2400 : 4,
|
||||
4800 : 5,
|
||||
9600 : 6,
|
||||
19200 : 7,
|
||||
38400 : 8,
|
||||
57600 : 9,
|
||||
76800 : 10,
|
||||
115200 : 11,
|
||||
}[rate] || 0;
|
||||
return 0 === speed ? exports.emulationSpeed() : exports.emulationSpeed(1, speed);
|
||||
const speed = {
|
||||
unlimited : 0,
|
||||
off : 0,
|
||||
0 : 0,
|
||||
300 : 1,
|
||||
600 : 2,
|
||||
1200 : 3,
|
||||
2400 : 4,
|
||||
4800 : 5,
|
||||
9600 : 6,
|
||||
19200 : 7,
|
||||
38400 : 8,
|
||||
57600 : 9,
|
||||
76800 : 10,
|
||||
115200 : 11,
|
||||
}[rate] || 0;
|
||||
return 0 === speed ? exports.emulationSpeed() : exports.emulationSpeed(1, speed);
|
||||
}
|
||||
|
||||
function vtxHyperlink(client, url, len) {
|
||||
if(!client.terminalSupports('vtx_hyperlink')) {
|
||||
return '';
|
||||
}
|
||||
if(!client.terminalSupports('vtx_hyperlink')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
len = len || url.length;
|
||||
len = len || url.length;
|
||||
|
||||
url = url.split('').map(c => c.charCodeAt(0)).join(';');
|
||||
return `${ESC_CSI}1;${len};1;1;${url}\\`;
|
||||
url = url.split('').map(c => c.charCodeAt(0)).join(';');
|
||||
return `${ESC_CSI}1;${len};1;1;${url}\\`;
|
||||
}
|
|
@ -16,314 +16,314 @@ const paths = require('path');
|
|||
let archiveUtil;
|
||||
|
||||
class Archiver {
|
||||
constructor(config) {
|
||||
this.compress = config.compress;
|
||||
this.decompress = config.decompress;
|
||||
this.list = config.list;
|
||||
this.extract = config.extract;
|
||||
}
|
||||
constructor(config) {
|
||||
this.compress = config.compress;
|
||||
this.decompress = config.decompress;
|
||||
this.list = config.list;
|
||||
this.extract = config.extract;
|
||||
}
|
||||
|
||||
ok() {
|
||||
return this.canCompress() && this.canDecompress();
|
||||
}
|
||||
ok() {
|
||||
return this.canCompress() && this.canDecompress();
|
||||
}
|
||||
|
||||
can(what) {
|
||||
if(!_.has(this, [ what, 'cmd' ]) || !_.has(this, [ what, 'args' ])) {
|
||||
return false;
|
||||
}
|
||||
can(what) {
|
||||
if(!_.has(this, [ what, 'cmd' ]) || !_.has(this, [ what, 'args' ])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _.isString(this[what].cmd) && Array.isArray(this[what].args) && this[what].args.length > 0;
|
||||
}
|
||||
return _.isString(this[what].cmd) && Array.isArray(this[what].args) && this[what].args.length > 0;
|
||||
}
|
||||
|
||||
canCompress() { return this.can('compress'); }
|
||||
canDecompress() { return this.can('decompress'); }
|
||||
canList() { return this.can('list'); } // :TODO: validate entryMatch
|
||||
canExtract() { return this.can('extract'); }
|
||||
canCompress() { return this.can('compress'); }
|
||||
canDecompress() { return this.can('decompress'); }
|
||||
canList() { return this.can('list'); } // :TODO: validate entryMatch
|
||||
canExtract() { return this.can('extract'); }
|
||||
}
|
||||
|
||||
module.exports = class ArchiveUtil {
|
||||
|
||||
constructor() {
|
||||
this.archivers = {};
|
||||
this.longestSignature = 0;
|
||||
}
|
||||
constructor() {
|
||||
this.archivers = {};
|
||||
this.longestSignature = 0;
|
||||
}
|
||||
|
||||
// singleton access
|
||||
static getInstance() {
|
||||
if(!archiveUtil) {
|
||||
archiveUtil = new ArchiveUtil();
|
||||
archiveUtil.init();
|
||||
}
|
||||
return archiveUtil;
|
||||
}
|
||||
// singleton access
|
||||
static getInstance() {
|
||||
if(!archiveUtil) {
|
||||
archiveUtil = new ArchiveUtil();
|
||||
archiveUtil.init();
|
||||
}
|
||||
return archiveUtil;
|
||||
}
|
||||
|
||||
init() {
|
||||
//
|
||||
// Load configuration
|
||||
//
|
||||
const config = Config();
|
||||
if(_.has(config, 'archives.archivers')) {
|
||||
Object.keys(config.archives.archivers).forEach(archKey => {
|
||||
init() {
|
||||
//
|
||||
// Load configuration
|
||||
//
|
||||
const config = Config();
|
||||
if(_.has(config, 'archives.archivers')) {
|
||||
Object.keys(config.archives.archivers).forEach(archKey => {
|
||||
|
||||
const archConfig = config.archives.archivers[archKey];
|
||||
const archiver = new Archiver(archConfig);
|
||||
const archConfig = config.archives.archivers[archKey];
|
||||
const archiver = new Archiver(archConfig);
|
||||
|
||||
if(!archiver.ok()) {
|
||||
// :TODO: Log warning - bad archiver/config
|
||||
}
|
||||
if(!archiver.ok()) {
|
||||
// :TODO: Log warning - bad archiver/config
|
||||
}
|
||||
|
||||
this.archivers[archKey] = archiver;
|
||||
});
|
||||
}
|
||||
this.archivers[archKey] = archiver;
|
||||
});
|
||||
}
|
||||
|
||||
if(_.isObject(config.fileTypes)) {
|
||||
const updateSig = (ft) => {
|
||||
ft.sig = Buffer.from(ft.sig, 'hex');
|
||||
ft.offset = ft.offset || 0;
|
||||
if(_.isObject(config.fileTypes)) {
|
||||
const updateSig = (ft) => {
|
||||
ft.sig = Buffer.from(ft.sig, 'hex');
|
||||
ft.offset = ft.offset || 0;
|
||||
|
||||
// :TODO: this is broken: sig is NOT this long, it's sig.length long; offset needs to allow for -negative values as well
|
||||
const sigLen = ft.offset + ft.sig.length;
|
||||
if(sigLen > this.longestSignature) {
|
||||
this.longestSignature = sigLen;
|
||||
}
|
||||
};
|
||||
// :TODO: this is broken: sig is NOT this long, it's sig.length long; offset needs to allow for -negative values as well
|
||||
const sigLen = ft.offset + ft.sig.length;
|
||||
if(sigLen > this.longestSignature) {
|
||||
this.longestSignature = sigLen;
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(config.fileTypes).forEach(mimeType => {
|
||||
const fileType = config.fileTypes[mimeType];
|
||||
if(Array.isArray(fileType)) {
|
||||
fileType.forEach(ft => {
|
||||
if(ft.sig) {
|
||||
updateSig(ft);
|
||||
}
|
||||
});
|
||||
} else if(fileType.sig) {
|
||||
updateSig(fileType);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Object.keys(config.fileTypes).forEach(mimeType => {
|
||||
const fileType = config.fileTypes[mimeType];
|
||||
if(Array.isArray(fileType)) {
|
||||
fileType.forEach(ft => {
|
||||
if(ft.sig) {
|
||||
updateSig(ft);
|
||||
}
|
||||
});
|
||||
} else if(fileType.sig) {
|
||||
updateSig(fileType);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getArchiver(mimeTypeOrExtension, justExtention) {
|
||||
const mimeType = resolveMimeType(mimeTypeOrExtension);
|
||||
getArchiver(mimeTypeOrExtension, justExtention) {
|
||||
const mimeType = resolveMimeType(mimeTypeOrExtension);
|
||||
|
||||
if(!mimeType) { // lookup returns false on failure
|
||||
return;
|
||||
}
|
||||
if(!mimeType) { // lookup returns false on failure
|
||||
return;
|
||||
}
|
||||
|
||||
const config = Config();
|
||||
let fileType = _.get(config, [ 'fileTypes', mimeType ] );
|
||||
const config = Config();
|
||||
let fileType = _.get(config, [ 'fileTypes', mimeType ] );
|
||||
|
||||
if(Array.isArray(fileType)) {
|
||||
if(!justExtention) {
|
||||
// need extention for lookup; ambiguous as-is :(
|
||||
return;
|
||||
}
|
||||
// further refine by extention
|
||||
fileType = fileType.find(ft => justExtention === ft.ext);
|
||||
}
|
||||
if(Array.isArray(fileType)) {
|
||||
if(!justExtention) {
|
||||
// need extention for lookup; ambiguous as-is :(
|
||||
return;
|
||||
}
|
||||
// further refine by extention
|
||||
fileType = fileType.find(ft => justExtention === ft.ext);
|
||||
}
|
||||
|
||||
if(!_.isObject(fileType)) {
|
||||
return;
|
||||
}
|
||||
if(!_.isObject(fileType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(fileType.archiveHandler) {
|
||||
return _.get( config, [ 'archives', 'archivers', fileType.archiveHandler ] );
|
||||
}
|
||||
}
|
||||
if(fileType.archiveHandler) {
|
||||
return _.get( config, [ 'archives', 'archivers', fileType.archiveHandler ] );
|
||||
}
|
||||
}
|
||||
|
||||
haveArchiver(archType) {
|
||||
return this.getArchiver(archType) ? true : false;
|
||||
}
|
||||
haveArchiver(archType) {
|
||||
return this.getArchiver(archType) ? true : false;
|
||||
}
|
||||
|
||||
// :TODO: implement me:
|
||||
/*
|
||||
detectTypeWithBuf(buf, cb) {
|
||||
// :TODO: implement me:
|
||||
/*
|
||||
detectTypeWithBuf(buf, cb) {
|
||||
}
|
||||
*/
|
||||
|
||||
detectType(path, cb) {
|
||||
fs.open(path, 'r', (err, fd) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
detectType(path, cb) {
|
||||
fs.open(path, 'r', (err, fd) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const buf = Buffer.alloc(this.longestSignature);
|
||||
fs.read(fd, buf, 0, buf.length, 0, (err, bytesRead) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
const buf = Buffer.alloc(this.longestSignature);
|
||||
fs.read(fd, buf, 0, buf.length, 0, (err, bytesRead) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => {
|
||||
const fileTypeInfos = Array.isArray(fileTypeInfo) ? fileTypeInfo : [ fileTypeInfo ];
|
||||
return fileTypeInfos.find(fti => {
|
||||
if(!fti.sig || !fti.archiveHandler) {
|
||||
return false;
|
||||
}
|
||||
const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => {
|
||||
const fileTypeInfos = Array.isArray(fileTypeInfo) ? fileTypeInfo : [ fileTypeInfo ];
|
||||
return fileTypeInfos.find(fti => {
|
||||
if(!fti.sig || !fti.archiveHandler) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lenNeeded = fti.offset + fti.sig.length;
|
||||
const lenNeeded = fti.offset + fti.sig.length;
|
||||
|
||||
if(bytesRead < lenNeeded) {
|
||||
return false;
|
||||
}
|
||||
if(bytesRead < lenNeeded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const comp = buf.slice(fti.offset, fti.offset + fti.sig.length);
|
||||
return (fti.sig.equals(comp));
|
||||
});
|
||||
});
|
||||
const comp = buf.slice(fti.offset, fti.offset + fti.sig.length);
|
||||
return (fti.sig.equals(comp));
|
||||
});
|
||||
});
|
||||
|
||||
return cb(archFormat ? null : Errors.General('Unknown type'), archFormat);
|
||||
});
|
||||
});
|
||||
}
|
||||
return cb(archFormat ? null : Errors.General('Unknown type'), archFormat);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
spawnHandler(proc, action, cb) {
|
||||
// pty.js doesn't currently give us a error when things fail,
|
||||
// so we have this horrible, horrible hack:
|
||||
let err;
|
||||
proc.once('data', d => {
|
||||
if(_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
||||
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
||||
}
|
||||
});
|
||||
spawnHandler(proc, action, cb) {
|
||||
// pty.js doesn't currently give us a error when things fail,
|
||||
// so we have this horrible, horrible hack:
|
||||
let err;
|
||||
proc.once('data', d => {
|
||||
if(_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
||||
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
||||
}
|
||||
});
|
||||
|
||||
proc.once('exit', exitCode => {
|
||||
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err);
|
||||
});
|
||||
}
|
||||
proc.once('exit', exitCode => {
|
||||
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err);
|
||||
});
|
||||
}
|
||||
|
||||
compressTo(archType, archivePath, files, cb) {
|
||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||
compressTo(archType, archivePath, files, cb) {
|
||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
|
||||
const fmtObj = {
|
||||
archivePath : archivePath,
|
||||
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
||||
};
|
||||
const fmtObj = {
|
||||
archivePath : archivePath,
|
||||
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
||||
};
|
||||
|
||||
const args = archiver.compress.args.map( arg => stringFormat(arg, fmtObj) );
|
||||
const args = archiver.compress.args.map( arg => stringFormat(arg, fmtObj) );
|
||||
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
return this.spawnHandler(proc, 'Compression', cb);
|
||||
}
|
||||
return this.spawnHandler(proc, 'Compression', cb);
|
||||
}
|
||||
|
||||
extractTo(archivePath, extractPath, archType, fileList, cb) {
|
||||
let haveFileList;
|
||||
extractTo(archivePath, extractPath, archType, fileList, cb) {
|
||||
let haveFileList;
|
||||
|
||||
if(!cb && _.isFunction(fileList)) {
|
||||
cb = fileList;
|
||||
fileList = [];
|
||||
haveFileList = false;
|
||||
} else {
|
||||
haveFileList = true;
|
||||
}
|
||||
if(!cb && _.isFunction(fileList)) {
|
||||
cb = fileList;
|
||||
fileList = [];
|
||||
haveFileList = false;
|
||||
} else {
|
||||
haveFileList = true;
|
||||
}
|
||||
|
||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
|
||||
const fmtObj = {
|
||||
archivePath : archivePath,
|
||||
extractPath : extractPath,
|
||||
};
|
||||
const fmtObj = {
|
||||
archivePath : archivePath,
|
||||
extractPath : extractPath,
|
||||
};
|
||||
|
||||
let action = haveFileList ? 'extract' : 'decompress';
|
||||
if('extract' === action && !_.isObject(archiver[action])) {
|
||||
// we're forced to do a full decompress
|
||||
action = 'decompress';
|
||||
haveFileList = false;
|
||||
}
|
||||
let action = haveFileList ? 'extract' : 'decompress';
|
||||
if('extract' === action && !_.isObject(archiver[action])) {
|
||||
// we're forced to do a full decompress
|
||||
action = 'decompress';
|
||||
haveFileList = false;
|
||||
}
|
||||
|
||||
// we need to treat {fileList} special in that it should be broken up to 0:n args
|
||||
const args = archiver[action].args.map( arg => {
|
||||
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
||||
});
|
||||
// we need to treat {fileList} special in that it should be broken up to 0:n args
|
||||
const args = archiver[action].args.map( arg => {
|
||||
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
||||
});
|
||||
|
||||
const fileListPos = args.indexOf('{fileList}');
|
||||
if(fileListPos > -1) {
|
||||
// replace {fileList} with 0:n sep file list arguments
|
||||
args.splice.apply(args, [fileListPos, 1].concat(fileList));
|
||||
}
|
||||
const fileListPos = args.indexOf('{fileList}');
|
||||
if(fileListPos > -1) {
|
||||
// replace {fileList} with 0:n sep file list arguments
|
||||
args.splice.apply(args, [fileListPos, 1].concat(fileList));
|
||||
}
|
||||
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath));
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath));
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
return this.spawnHandler(proc, (haveFileList ? 'Extraction' : 'Decompression'), cb);
|
||||
}
|
||||
return this.spawnHandler(proc, (haveFileList ? 'Extraction' : 'Decompression'), cb);
|
||||
}
|
||||
|
||||
listEntries(archivePath, archType, cb) {
|
||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||
listEntries(archivePath, archType, cb) {
|
||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
if(!archiver) {
|
||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||
}
|
||||
|
||||
const fmtObj = {
|
||||
archivePath : archivePath,
|
||||
};
|
||||
const fmtObj = {
|
||||
archivePath : archivePath,
|
||||
};
|
||||
|
||||
const args = archiver.list.args.map( arg => stringFormat(arg, fmtObj) );
|
||||
const args = archiver.list.args.map( arg => stringFormat(arg, fmtObj) );
|
||||
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
let proc;
|
||||
try {
|
||||
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
let output = '';
|
||||
proc.on('data', data => {
|
||||
// :TODO: hack for: execvp(3) failed.: No such file or directory
|
||||
let output = '';
|
||||
proc.on('data', data => {
|
||||
// :TODO: hack for: execvp(3) failed.: No such file or directory
|
||||
|
||||
output += data;
|
||||
});
|
||||
output += data;
|
||||
});
|
||||
|
||||
proc.once('exit', exitCode => {
|
||||
if(exitCode) {
|
||||
return cb(Errors.ExternalProcess(`List failed with exit code: ${exitCode}`));
|
||||
}
|
||||
proc.once('exit', exitCode => {
|
||||
if(exitCode) {
|
||||
return cb(Errors.ExternalProcess(`List failed with exit code: ${exitCode}`));
|
||||
}
|
||||
|
||||
const entryGroupOrder = archiver.list.entryGroupOrder || { byteSize : 1, fileName : 2 };
|
||||
const entryGroupOrder = archiver.list.entryGroupOrder || { byteSize : 1, fileName : 2 };
|
||||
|
||||
const entries = [];
|
||||
const entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm');
|
||||
let m;
|
||||
while((m = entryMatchRe.exec(output))) {
|
||||
entries.push({
|
||||
byteSize : parseInt(m[entryGroupOrder.byteSize]),
|
||||
fileName : m[entryGroupOrder.fileName].trim(),
|
||||
});
|
||||
}
|
||||
const entries = [];
|
||||
const entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm');
|
||||
let m;
|
||||
while((m = entryMatchRe.exec(output))) {
|
||||
entries.push({
|
||||
byteSize : parseInt(m[entryGroupOrder.byteSize]),
|
||||
fileName : m[entryGroupOrder.fileName].trim(),
|
||||
});
|
||||
}
|
||||
|
||||
return cb(null, entries);
|
||||
});
|
||||
}
|
||||
return cb(null, entries);
|
||||
});
|
||||
}
|
||||
|
||||
getPtyOpts(extractPath) {
|
||||
const opts = {
|
||||
name : 'enigma-archiver',
|
||||
cols : 80,
|
||||
rows : 24,
|
||||
env : process.env,
|
||||
};
|
||||
if(extractPath) {
|
||||
opts.cwd = extractPath;
|
||||
}
|
||||
// :TODO: set cwd to supplied temp path if not sepcific extract
|
||||
return opts;
|
||||
}
|
||||
getPtyOpts(extractPath) {
|
||||
const opts = {
|
||||
name : 'enigma-archiver',
|
||||
cols : 80,
|
||||
rows : 24,
|
||||
env : process.env,
|
||||
};
|
||||
if(extractPath) {
|
||||
opts.cwd = extractPath;
|
||||
}
|
||||
// :TODO: set cwd to supplied temp path if not sepcific extract
|
||||
return opts;
|
||||
}
|
||||
};
|
||||
|
|
550
core/art.js
550
core/art.js
|
@ -26,87 +26,87 @@ exports.defaultEncodingFromExtension = defaultEncodingFromExtension;
|
|||
// :TODO: return font + font mapped information from SAUCE
|
||||
|
||||
const SUPPORTED_ART_TYPES = {
|
||||
// :TODO: the defualt encoding are really useless if they are all the same ...
|
||||
// perhaps .ansamiga and .ascamiga could be supported as well as overrides via conf
|
||||
'.ans' : { name : 'ANSI', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.asc' : { name : 'ASCII', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.pcb' : { name : 'PCBoard', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.bbs' : { name : 'Wildcat', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
// :TODO: the defualt encoding are really useless if they are all the same ...
|
||||
// perhaps .ansamiga and .ascamiga could be supported as well as overrides via conf
|
||||
'.ans' : { name : 'ANSI', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.asc' : { name : 'ASCII', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.pcb' : { name : 'PCBoard', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
'.bbs' : { name : 'Wildcat', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
|
||||
'.amiga' : { name : 'Amiga', defaultEncoding : 'amiga', eof : 0x1a },
|
||||
'.txt' : { name : 'Amiga Text', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
// :TODO: extentions for wwiv, renegade, celerity, syncronet, ...
|
||||
// :TODO: extension for atari
|
||||
// :TODO: extension for topaz ansi/ascii.
|
||||
'.amiga' : { name : 'Amiga', defaultEncoding : 'amiga', eof : 0x1a },
|
||||
'.txt' : { name : 'Amiga Text', defaultEncoding : 'cp437', eof : 0x1a },
|
||||
// :TODO: extentions for wwiv, renegade, celerity, syncronet, ...
|
||||
// :TODO: extension for atari
|
||||
// :TODO: extension for topaz ansi/ascii.
|
||||
};
|
||||
|
||||
function getFontNameFromSAUCE(sauce) {
|
||||
if(sauce.Character) {
|
||||
return sauce.Character.fontName;
|
||||
}
|
||||
if(sauce.Character) {
|
||||
return sauce.Character.fontName;
|
||||
}
|
||||
}
|
||||
|
||||
function sliceAtEOF(data, eofMarker) {
|
||||
let eof = data.length;
|
||||
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE)
|
||||
let eof = data.length;
|
||||
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE)
|
||||
|
||||
for(let i = eof - 1; i > stopPos; i--) {
|
||||
if(eofMarker === data[i]) {
|
||||
eof = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return data.slice(0, eof);
|
||||
for(let i = eof - 1; i > stopPos; i--) {
|
||||
if(eofMarker === data[i]) {
|
||||
eof = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return data.slice(0, eof);
|
||||
}
|
||||
|
||||
function getArtFromPath(path, options, cb) {
|
||||
fs.readFile(path, (err, data) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
fs.readFile(path, (err, data) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
//
|
||||
// Convert from encodedAs -> j
|
||||
//
|
||||
const ext = paths.extname(path).toLowerCase();
|
||||
const encoding = options.encodedAs || defaultEncodingFromExtension(ext);
|
||||
//
|
||||
// Convert from encodedAs -> j
|
||||
//
|
||||
const ext = paths.extname(path).toLowerCase();
|
||||
const encoding = options.encodedAs || defaultEncodingFromExtension(ext);
|
||||
|
||||
// :TODO: how are BOM's currently handled if present? Are they removed? Do we need to?
|
||||
// :TODO: how are BOM's currently handled if present? Are they removed? Do we need to?
|
||||
|
||||
function sliceOfData() {
|
||||
if(options.fullFile === true) {
|
||||
return iconv.decode(data, encoding);
|
||||
} else {
|
||||
const eofMarker = defaultEofFromExtension(ext);
|
||||
return iconv.decode(eofMarker ? sliceAtEOF(data, eofMarker) : data, encoding);
|
||||
}
|
||||
}
|
||||
function sliceOfData() {
|
||||
if(options.fullFile === true) {
|
||||
return iconv.decode(data, encoding);
|
||||
} else {
|
||||
const eofMarker = defaultEofFromExtension(ext);
|
||||
return iconv.decode(eofMarker ? sliceAtEOF(data, eofMarker) : data, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
function getResult(sauce) {
|
||||
const result = {
|
||||
data : sliceOfData(),
|
||||
fromPath : path,
|
||||
};
|
||||
function getResult(sauce) {
|
||||
const result = {
|
||||
data : sliceOfData(),
|
||||
fromPath : path,
|
||||
};
|
||||
|
||||
if(sauce) {
|
||||
result.sauce = sauce;
|
||||
}
|
||||
if(sauce) {
|
||||
result.sauce = sauce;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if(options.readSauce === true) {
|
||||
sauce.readSAUCE(data, (err, sauce) => {
|
||||
if(err) {
|
||||
return cb(null, getResult());
|
||||
}
|
||||
if(options.readSauce === true) {
|
||||
sauce.readSAUCE(data, (err, sauce) => {
|
||||
if(err) {
|
||||
return cb(null, getResult());
|
||||
}
|
||||
|
||||
//
|
||||
// If a encoding was not provided & we have a mapping from
|
||||
// the information provided by SAUCE, use that.
|
||||
//
|
||||
if(!options.encodedAs) {
|
||||
/*
|
||||
//
|
||||
// If a encoding was not provided & we have a mapping from
|
||||
// the information provided by SAUCE, use that.
|
||||
//
|
||||
if(!options.encodedAs) {
|
||||
/*
|
||||
if(sauce.Character && sauce.Character.fontName) {
|
||||
var enc = SAUCE_FONT_TO_ENCODING_HINT[sauce.Character.fontName];
|
||||
if(enc) {
|
||||
|
@ -114,115 +114,115 @@ function getArtFromPath(path, options, cb) {
|
|||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
return cb(null, getResult(sauce));
|
||||
});
|
||||
} else {
|
||||
return cb(null, getResult());
|
||||
}
|
||||
});
|
||||
}
|
||||
return cb(null, getResult(sauce));
|
||||
});
|
||||
} else {
|
||||
return cb(null, getResult());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getArt(name, options, cb) {
|
||||
const ext = paths.extname(name);
|
||||
const ext = paths.extname(name);
|
||||
|
||||
options.basePath = miscUtil.valueWithDefault(options.basePath, Config().paths.art);
|
||||
options.asAnsi = miscUtil.valueWithDefault(options.asAnsi, true);
|
||||
options.basePath = miscUtil.valueWithDefault(options.basePath, Config().paths.art);
|
||||
options.asAnsi = miscUtil.valueWithDefault(options.asAnsi, true);
|
||||
|
||||
// :TODO: make use of asAnsi option and convert from supported -> ansi
|
||||
// :TODO: make use of asAnsi option and convert from supported -> ansi
|
||||
|
||||
if('' !== ext) {
|
||||
options.types = [ ext.toLowerCase() ];
|
||||
} else {
|
||||
if(_.isUndefined(options.types)) {
|
||||
options.types = Object.keys(SUPPORTED_ART_TYPES);
|
||||
} else if(_.isString(options.types)) {
|
||||
options.types = [ options.types.toLowerCase() ];
|
||||
}
|
||||
}
|
||||
if('' !== ext) {
|
||||
options.types = [ ext.toLowerCase() ];
|
||||
} else {
|
||||
if(_.isUndefined(options.types)) {
|
||||
options.types = Object.keys(SUPPORTED_ART_TYPES);
|
||||
} else if(_.isString(options.types)) {
|
||||
options.types = [ options.types.toLowerCase() ];
|
||||
}
|
||||
}
|
||||
|
||||
// If an extension is provided, just read the file now
|
||||
if('' !== ext) {
|
||||
const directPath = paths.join(options.basePath, name);
|
||||
return getArtFromPath(directPath, options, cb);
|
||||
}
|
||||
// If an extension is provided, just read the file now
|
||||
if('' !== ext) {
|
||||
const directPath = paths.join(options.basePath, name);
|
||||
return getArtFromPath(directPath, options, cb);
|
||||
}
|
||||
|
||||
fs.readdir(options.basePath, (err, files) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
fs.readdir(options.basePath, (err, files) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const filtered = files.filter( file => {
|
||||
//
|
||||
// Ignore anything not allowed in |options.types|
|
||||
//
|
||||
const fext = paths.extname(file);
|
||||
if(!options.types.includes(fext.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
const filtered = files.filter( file => {
|
||||
//
|
||||
// Ignore anything not allowed in |options.types|
|
||||
//
|
||||
const fext = paths.extname(file);
|
||||
if(!options.types.includes(fext.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bn = paths.basename(file, fext).toLowerCase();
|
||||
if(options.random) {
|
||||
const suppliedBn = paths.basename(name, fext).toLowerCase();
|
||||
const bn = paths.basename(file, fext).toLowerCase();
|
||||
if(options.random) {
|
||||
const suppliedBn = paths.basename(name, fext).toLowerCase();
|
||||
|
||||
//
|
||||
// Random selection enabled. We'll allow for
|
||||
// basename1.ext, basename2.ext, ...
|
||||
//
|
||||
if(!bn.startsWith(suppliedBn)) {
|
||||
return false;
|
||||
}
|
||||
//
|
||||
// Random selection enabled. We'll allow for
|
||||
// basename1.ext, basename2.ext, ...
|
||||
//
|
||||
if(!bn.startsWith(suppliedBn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const num = bn.substr(suppliedBn.length);
|
||||
if(num.length > 0) {
|
||||
if(isNaN(parseInt(num, 10))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// We've already validated the extension (above). Must be an exact
|
||||
// match to basename here
|
||||
//
|
||||
if(bn != paths.basename(name, fext).toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const num = bn.substr(suppliedBn.length);
|
||||
if(num.length > 0) {
|
||||
if(isNaN(parseInt(num, 10))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//
|
||||
// We've already validated the extension (above). Must be an exact
|
||||
// match to basename here
|
||||
//
|
||||
if(bn != paths.basename(name, fext).toLowerCase()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
if(filtered.length > 0) {
|
||||
//
|
||||
// We should now have:
|
||||
// - Exactly (1) item in |filtered| if non-random
|
||||
// - 1:n items in |filtered| to choose from if random
|
||||
//
|
||||
let readPath;
|
||||
if(options.random) {
|
||||
readPath = paths.join(options.basePath, filtered[Math.floor(Math.random() * filtered.length)]);
|
||||
} else {
|
||||
assert(1 === filtered.length);
|
||||
readPath = paths.join(options.basePath, filtered[0]);
|
||||
}
|
||||
if(filtered.length > 0) {
|
||||
//
|
||||
// We should now have:
|
||||
// - Exactly (1) item in |filtered| if non-random
|
||||
// - 1:n items in |filtered| to choose from if random
|
||||
//
|
||||
let readPath;
|
||||
if(options.random) {
|
||||
readPath = paths.join(options.basePath, filtered[Math.floor(Math.random() * filtered.length)]);
|
||||
} else {
|
||||
assert(1 === filtered.length);
|
||||
readPath = paths.join(options.basePath, filtered[0]);
|
||||
}
|
||||
|
||||
return getArtFromPath(readPath, options, cb);
|
||||
}
|
||||
return getArtFromPath(readPath, options, cb);
|
||||
}
|
||||
|
||||
return cb(new Error(`No matching art for supplied criteria: ${name}`));
|
||||
});
|
||||
return cb(new Error(`No matching art for supplied criteria: ${name}`));
|
||||
});
|
||||
}
|
||||
|
||||
function defaultEncodingFromExtension(ext) {
|
||||
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
||||
return artType ? artType.defaultEncoding : 'utf8';
|
||||
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
||||
return artType ? artType.defaultEncoding : 'utf8';
|
||||
}
|
||||
|
||||
function defaultEofFromExtension(ext) {
|
||||
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
||||
if(artType) {
|
||||
return artType.eof;
|
||||
}
|
||||
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
||||
if(artType) {
|
||||
return artType.eof;
|
||||
}
|
||||
}
|
||||
|
||||
// :TODO: Implement the following
|
||||
|
@ -230,161 +230,161 @@ function defaultEofFromExtension(ext) {
|
|||
// * Cancel (disabled | <keys> )
|
||||
// * Resume from pause -> continous (disabled | <keys>)
|
||||
function display(client, art, options, cb) {
|
||||
if(_.isFunction(options) && !cb) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
if(_.isFunction(options) && !cb) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if(!art || !art.length) {
|
||||
return cb(new Error('Empty art'));
|
||||
}
|
||||
if(!art || !art.length) {
|
||||
return cb(new Error('Empty art'));
|
||||
}
|
||||
|
||||
options.mciReplaceChar = options.mciReplaceChar || ' ';
|
||||
options.disableMciCache = options.disableMciCache || false;
|
||||
options.mciReplaceChar = options.mciReplaceChar || ' ';
|
||||
options.disableMciCache = options.disableMciCache || false;
|
||||
|
||||
// :TODO: this is going to be broken into two approaches controlled via options:
|
||||
// 1) Standard - use internal tracking of locations for MCI -- no CPR's/etc.
|
||||
// 2) CPR driven
|
||||
// :TODO: this is going to be broken into two approaches controlled via options:
|
||||
// 1) Standard - use internal tracking of locations for MCI -- no CPR's/etc.
|
||||
// 2) CPR driven
|
||||
|
||||
if(!_.isBoolean(options.iceColors)) {
|
||||
// try to detect from SAUCE
|
||||
if(_.has(options, 'sauce.ansiFlags') && (options.sauce.ansiFlags & (1 << 0))) {
|
||||
options.iceColors = true;
|
||||
}
|
||||
}
|
||||
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,
|
||||
});
|
||||
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;
|
||||
let mciMapFromCache;
|
||||
let parseComplete = false;
|
||||
let cprListener;
|
||||
let mciMap;
|
||||
const mciCprQueue = [];
|
||||
let artHash;
|
||||
let mciMapFromCache;
|
||||
|
||||
function completed() {
|
||||
if(cprListener) {
|
||||
client.removeListener('cursor position report', cprListener);
|
||||
}
|
||||
function completed() {
|
||||
if(cprListener) {
|
||||
client.removeListener('cursor position report', cprListener);
|
||||
}
|
||||
|
||||
if(!options.disableMciCache && !mciMapFromCache) {
|
||||
// cache our MCI findings...
|
||||
client.mciCache[artHash] = mciMap;
|
||||
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Added MCI map to cache');
|
||||
}
|
||||
if(!options.disableMciCache && !mciMapFromCache) {
|
||||
// cache our MCI findings...
|
||||
client.mciCache[artHash] = mciMap;
|
||||
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Added MCI map to cache');
|
||||
}
|
||||
|
||||
ansiParser.removeAllListeners(); // :TODO: Necessary???
|
||||
ansiParser.removeAllListeners(); // :TODO: Necessary???
|
||||
|
||||
const extraInfo = {
|
||||
height : ansiParser.row - 1,
|
||||
};
|
||||
const extraInfo = {
|
||||
height : ansiParser.row - 1,
|
||||
};
|
||||
|
||||
return cb(null, mciMap, extraInfo);
|
||||
}
|
||||
return cb(null, mciMap, extraInfo);
|
||||
}
|
||||
|
||||
if(!options.disableMciCache) {
|
||||
artHash = xxhash.hash(Buffer.from(art), 0xCAFEBABE);
|
||||
if(!options.disableMciCache) {
|
||||
artHash = xxhash.hash(Buffer.from(art), 0xCAFEBABE);
|
||||
|
||||
// see if we have a mciMap cached for this art
|
||||
if(client.mciCache) {
|
||||
mciMap = client.mciCache[artHash];
|
||||
}
|
||||
}
|
||||
// see if we have a mciMap cached for this art
|
||||
if(client.mciCache) {
|
||||
mciMap = client.mciCache[artHash];
|
||||
}
|
||||
}
|
||||
|
||||
if(mciMap) {
|
||||
mciMapFromCache = true;
|
||||
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Loaded MCI map from cache');
|
||||
} else {
|
||||
// no cached MCI info
|
||||
mciMap = {};
|
||||
if(mciMap) {
|
||||
mciMapFromCache = true;
|
||||
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Loaded MCI map from cache');
|
||||
} else {
|
||||
// no cached MCI info
|
||||
mciMap = {};
|
||||
|
||||
cprListener = function(pos) {
|
||||
if(mciCprQueue.length > 0) {
|
||||
mciMap[mciCprQueue.shift()].position = pos;
|
||||
cprListener = function(pos) {
|
||||
if(mciCprQueue.length > 0) {
|
||||
mciMap[mciCprQueue.shift()].position = pos;
|
||||
|
||||
if(parseComplete && 0 === mciCprQueue.length) {
|
||||
return completed();
|
||||
}
|
||||
}
|
||||
};
|
||||
if(parseComplete && 0 === mciCprQueue.length) {
|
||||
return completed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
client.on('cursor position report', cprListener);
|
||||
client.on('cursor position report', cprListener);
|
||||
|
||||
let generatedId = 100;
|
||||
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];
|
||||
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(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;
|
||||
}
|
||||
if(!mciInfo.id) {
|
||||
++generatedId;
|
||||
}
|
||||
|
||||
mciCprQueue.push(mapKey);
|
||||
client.term.rawWrite(ansi.queryPos());
|
||||
}
|
||||
mciCprQueue.push(mapKey);
|
||||
client.term.rawWrite(ansi.queryPos());
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ansiParser.on('literal', literal => client.term.write(literal, false) );
|
||||
ansiParser.on('control', control => client.term.rawWrite(control) );
|
||||
ansiParser.on('literal', literal => client.term.write(literal, false) );
|
||||
ansiParser.on('control', control => client.term.rawWrite(control) );
|
||||
|
||||
ansiParser.on('complete', () => {
|
||||
parseComplete = true;
|
||||
ansiParser.on('complete', () => {
|
||||
parseComplete = true;
|
||||
|
||||
if(0 === mciCprQueue.length) {
|
||||
return completed();
|
||||
}
|
||||
});
|
||||
if(0 === mciCprQueue.length) {
|
||||
return completed();
|
||||
}
|
||||
});
|
||||
|
||||
let initSeq = '';
|
||||
if(options.font) {
|
||||
initSeq = ansi.setSyncTermFontWithAlias(options.font);
|
||||
} else if(options.sauce) {
|
||||
let fontName = getFontNameFromSAUCE(options.sauce);
|
||||
if(fontName) {
|
||||
fontName = ansi.getSyncTERMFontFromAlias(fontName);
|
||||
}
|
||||
let initSeq = '';
|
||||
if(options.font) {
|
||||
initSeq = ansi.setSyncTermFontWithAlias(options.font);
|
||||
} else if(options.sauce) {
|
||||
let fontName = getFontNameFromSAUCE(options.sauce);
|
||||
if(fontName) {
|
||||
fontName = ansi.getSyncTERMFontFromAlias(fontName);
|
||||
}
|
||||
|
||||
//
|
||||
// Set SyncTERM font if we're switching only. Most terminals
|
||||
// that support this ESC sequence can only show *one* font
|
||||
// at a time. This applies to detection only (e.g. SAUCE).
|
||||
// If explicit, we'll set it no matter what (above)
|
||||
//
|
||||
if(fontName && client.term.currentSyncFont != fontName) {
|
||||
client.term.currentSyncFont = fontName;
|
||||
initSeq = ansi.setSyncTERMFont(fontName);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Set SyncTERM font if we're switching only. Most terminals
|
||||
// that support this ESC sequence can only show *one* font
|
||||
// at a time. This applies to detection only (e.g. SAUCE).
|
||||
// If explicit, we'll set it no matter what (above)
|
||||
//
|
||||
if(fontName && client.term.currentSyncFont != fontName) {
|
||||
client.term.currentSyncFont = fontName;
|
||||
initSeq = ansi.setSyncTERMFont(fontName);
|
||||
}
|
||||
}
|
||||
|
||||
if(options.iceColors) {
|
||||
initSeq += ansi.blinkToBrightIntensity();
|
||||
}
|
||||
if(options.iceColors) {
|
||||
initSeq += ansi.blinkToBrightIntensity();
|
||||
}
|
||||
|
||||
if(initSeq) {
|
||||
client.term.rawWrite(initSeq);
|
||||
}
|
||||
if(initSeq) {
|
||||
client.term.rawWrite(initSeq);
|
||||
}
|
||||
|
||||
ansiParser.reset(art);
|
||||
return ansiParser.parse();
|
||||
ansiParser.reset(art);
|
||||
return ansiParser.parse();
|
||||
}
|
||||
|
|
138
core/asset.js
138
core/asset.js
|
@ -18,111 +18,111 @@ exports.resolveSystemStatAsset = resolveSystemStatAsset;
|
|||
exports.getViewPropertyAsset = getViewPropertyAsset;
|
||||
|
||||
const ALL_ASSETS = [
|
||||
'art',
|
||||
'menu',
|
||||
'method',
|
||||
'userModule',
|
||||
'systemMethod',
|
||||
'systemModule',
|
||||
'prompt',
|
||||
'config',
|
||||
'sysStat',
|
||||
'art',
|
||||
'menu',
|
||||
'method',
|
||||
'userModule',
|
||||
'systemMethod',
|
||||
'systemModule',
|
||||
'prompt',
|
||||
'config',
|
||||
'sysStat',
|
||||
];
|
||||
|
||||
const ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*');
|
||||
|
||||
function parseAsset(s) {
|
||||
const m = ASSET_RE.exec(s);
|
||||
const m = ASSET_RE.exec(s);
|
||||
|
||||
if(m) {
|
||||
let result = { type : m[1] };
|
||||
if(m) {
|
||||
let result = { type : m[1] };
|
||||
|
||||
if(m[3]) {
|
||||
result.location = m[2];
|
||||
result.asset = m[3];
|
||||
} else {
|
||||
result.asset = m[2];
|
||||
}
|
||||
if(m[3]) {
|
||||
result.location = m[2];
|
||||
result.asset = m[3];
|
||||
} else {
|
||||
result.asset = m[2];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
function getAssetWithShorthand(spec, defaultType) {
|
||||
if(!_.isString(spec)) {
|
||||
return null;
|
||||
}
|
||||
if(!_.isString(spec)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if('@' === spec[0]) {
|
||||
const asset = parseAsset(spec);
|
||||
assert(_.isString(asset.type));
|
||||
if('@' === spec[0]) {
|
||||
const asset = parseAsset(spec);
|
||||
assert(_.isString(asset.type));
|
||||
|
||||
return asset;
|
||||
}
|
||||
return asset;
|
||||
}
|
||||
|
||||
return {
|
||||
type : defaultType,
|
||||
asset : spec,
|
||||
};
|
||||
return {
|
||||
type : defaultType,
|
||||
asset : spec,
|
||||
};
|
||||
}
|
||||
|
||||
function getArtAsset(spec) {
|
||||
const asset = getAssetWithShorthand(spec, 'art');
|
||||
const asset = getAssetWithShorthand(spec, 'art');
|
||||
|
||||
if(!asset) {
|
||||
return null;
|
||||
}
|
||||
if(!asset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assert( ['art', 'method' ].indexOf(asset.type) > -1);
|
||||
return asset;
|
||||
assert( ['art', 'method' ].indexOf(asset.type) > -1);
|
||||
return asset;
|
||||
}
|
||||
|
||||
function getModuleAsset(spec) {
|
||||
const asset = getAssetWithShorthand(spec, 'systemModule');
|
||||
const asset = getAssetWithShorthand(spec, 'systemModule');
|
||||
|
||||
if(!asset) {
|
||||
return null;
|
||||
}
|
||||
if(!asset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assert( ['userModule', 'systemModule' ].includes(asset.type) );
|
||||
assert( ['userModule', 'systemModule' ].includes(asset.type) );
|
||||
|
||||
return asset;
|
||||
return asset;
|
||||
}
|
||||
|
||||
function resolveConfigAsset(spec) {
|
||||
const asset = parseAsset(spec);
|
||||
if(asset) {
|
||||
assert('config' === asset.type);
|
||||
const asset = parseAsset(spec);
|
||||
if(asset) {
|
||||
assert('config' === asset.type);
|
||||
|
||||
const path = asset.asset.split('.');
|
||||
let conf = Config();
|
||||
for(let i = 0; i < path.length; ++i) {
|
||||
if(_.isUndefined(conf[path[i]])) {
|
||||
return spec;
|
||||
}
|
||||
conf = conf[path[i]];
|
||||
}
|
||||
return conf;
|
||||
} else {
|
||||
return spec;
|
||||
}
|
||||
const path = asset.asset.split('.');
|
||||
let conf = Config();
|
||||
for(let i = 0; i < path.length; ++i) {
|
||||
if(_.isUndefined(conf[path[i]])) {
|
||||
return spec;
|
||||
}
|
||||
conf = conf[path[i]];
|
||||
}
|
||||
return conf;
|
||||
} else {
|
||||
return spec;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveSystemStatAsset(spec) {
|
||||
const asset = parseAsset(spec);
|
||||
if(!asset) {
|
||||
return spec;
|
||||
}
|
||||
const asset = parseAsset(spec);
|
||||
if(!asset) {
|
||||
return spec;
|
||||
}
|
||||
|
||||
assert('sysStat' === asset.type);
|
||||
assert('sysStat' === asset.type);
|
||||
|
||||
return StatLog.getSystemStat(asset.asset) || spec;
|
||||
return StatLog.getSystemStat(asset.asset) || spec;
|
||||
}
|
||||
|
||||
function getViewPropertyAsset(src) {
|
||||
if(!_.isString(src) || '@' !== src.charAt(0)) {
|
||||
return null;
|
||||
}
|
||||
if(!_.isString(src) || '@' !== src.charAt(0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return parseAsset(src);
|
||||
return parseAsset(src);
|
||||
}
|
||||
|
|
452
core/bbs.js
452
core/bbs.js
|
@ -41,253 +41,253 @@ valid args:
|
|||
`;
|
||||
|
||||
function printHelpAndExit() {
|
||||
console.info(HELP);
|
||||
process.exit();
|
||||
console.info(HELP);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
function main() {
|
||||
async.waterfall(
|
||||
[
|
||||
function processArgs(callback) {
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
async.waterfall(
|
||||
[
|
||||
function processArgs(callback) {
|
||||
const argv = require('minimist')(process.argv.slice(2));
|
||||
|
||||
if(argv.help) {
|
||||
printHelpAndExit();
|
||||
}
|
||||
if(argv.help) {
|
||||
printHelpAndExit();
|
||||
}
|
||||
|
||||
const configOverridePath = argv.config;
|
||||
const configOverridePath = argv.config;
|
||||
|
||||
return callback(null, configOverridePath || conf.getDefaultPath(), _.isString(configOverridePath));
|
||||
},
|
||||
function initConfig(configPath, configPathSupplied, callback) {
|
||||
const configFile = configPath + 'config.hjson';
|
||||
conf.init(resolvePath(configFile), function configInit(err) {
|
||||
return callback(null, configOverridePath || conf.getDefaultPath(), _.isString(configOverridePath));
|
||||
},
|
||||
function initConfig(configPath, configPathSupplied, callback) {
|
||||
const configFile = configPath + 'config.hjson';
|
||||
conf.init(resolvePath(configFile), function configInit(err) {
|
||||
|
||||
//
|
||||
// If the user supplied a path and we can't read/parse it
|
||||
// then it's a fatal error
|
||||
//
|
||||
if(err) {
|
||||
if('ENOENT' === err.code) {
|
||||
if(configPathSupplied) {
|
||||
console.error('Configuration file does not exist: ' + configFile);
|
||||
} else {
|
||||
configPathSupplied = null; // make non-fatal; we'll go with defaults
|
||||
}
|
||||
} else {
|
||||
console.error(err.toString());
|
||||
}
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function initSystem(callback) {
|
||||
initialize(function init(err) {
|
||||
if(err) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
// note this is escaped:
|
||||
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
||||
console.info(FULL_COPYRIGHT);
|
||||
if(!err) {
|
||||
console.info(banner);
|
||||
}
|
||||
console.info('System started!');
|
||||
});
|
||||
//
|
||||
// If the user supplied a path and we can't read/parse it
|
||||
// then it's a fatal error
|
||||
//
|
||||
if(err) {
|
||||
if('ENOENT' === err.code) {
|
||||
if(configPathSupplied) {
|
||||
console.error('Configuration file does not exist: ' + configFile);
|
||||
} else {
|
||||
configPathSupplied = null; // make non-fatal; we'll go with defaults
|
||||
}
|
||||
} else {
|
||||
console.error(err.toString());
|
||||
}
|
||||
}
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function initSystem(callback) {
|
||||
initialize(function init(err) {
|
||||
if(err) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
// note this is escaped:
|
||||
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
||||
console.info(FULL_COPYRIGHT);
|
||||
if(!err) {
|
||||
console.info(banner);
|
||||
}
|
||||
console.info('System started!');
|
||||
});
|
||||
|
||||
if(err) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
}
|
||||
}
|
||||
);
|
||||
if(err) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function shutdownSystem() {
|
||||
const msg = 'Process interrupted. Shutting down...';
|
||||
console.info(msg);
|
||||
logger.log.info(msg);
|
||||
const msg = 'Process interrupted. Shutting down...';
|
||||
console.info(msg);
|
||||
logger.log.info(msg);
|
||||
|
||||
async.series(
|
||||
[
|
||||
function closeConnections(callback) {
|
||||
const ClientConns = require('./client_connections.js');
|
||||
const activeConnections = ClientConns.getActiveConnections();
|
||||
let i = activeConnections.length;
|
||||
while(i--) {
|
||||
const activeTerm = activeConnections[i].term;
|
||||
if(activeTerm) {
|
||||
activeTerm.write('\n\nServer is shutting down NOW! Disconnecting...\n\n');
|
||||
}
|
||||
ClientConns.removeClient(activeConnections[i]);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
function stopListeningServers(callback) {
|
||||
return require('./listening_server.js').shutdown( () => {
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
},
|
||||
function stopEventScheduler(callback) {
|
||||
if(initServices.eventScheduler) {
|
||||
return initServices.eventScheduler.shutdown( () => {
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
} else {
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function stopFileAreaWeb(callback) {
|
||||
require('./file_area_web.js').startup( () => {
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
},
|
||||
function stopMsgNetwork(callback) {
|
||||
require('./msg_network.js').shutdown(callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
console.info('Goodbye!');
|
||||
return process.exit();
|
||||
}
|
||||
);
|
||||
async.series(
|
||||
[
|
||||
function closeConnections(callback) {
|
||||
const ClientConns = require('./client_connections.js');
|
||||
const activeConnections = ClientConns.getActiveConnections();
|
||||
let i = activeConnections.length;
|
||||
while(i--) {
|
||||
const activeTerm = activeConnections[i].term;
|
||||
if(activeTerm) {
|
||||
activeTerm.write('\n\nServer is shutting down NOW! Disconnecting...\n\n');
|
||||
}
|
||||
ClientConns.removeClient(activeConnections[i]);
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
function stopListeningServers(callback) {
|
||||
return require('./listening_server.js').shutdown( () => {
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
},
|
||||
function stopEventScheduler(callback) {
|
||||
if(initServices.eventScheduler) {
|
||||
return initServices.eventScheduler.shutdown( () => {
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
} else {
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function stopFileAreaWeb(callback) {
|
||||
require('./file_area_web.js').startup( () => {
|
||||
return callback(null); // ignore err
|
||||
});
|
||||
},
|
||||
function stopMsgNetwork(callback) {
|
||||
require('./msg_network.js').shutdown(callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
console.info('Goodbye!');
|
||||
return process.exit();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function initialize(cb) {
|
||||
async.series(
|
||||
[
|
||||
function createMissingDirectories(callback) {
|
||||
async.each(Object.keys(conf.config.paths), function entry(pathKey, next) {
|
||||
mkdirs(conf.config.paths[pathKey], function dirCreated(err) {
|
||||
if(err) {
|
||||
console.error('Could not create path: ' + conf.config.paths[pathKey] + ': ' + err.toString());
|
||||
}
|
||||
return next(err);
|
||||
});
|
||||
}, function dirCreationComplete(err) {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function basicInit(callback) {
|
||||
logger.init();
|
||||
logger.log.info(
|
||||
{ version : require('../package.json').version },
|
||||
'**** ENiGMA½ Bulletin Board System Starting Up! ****');
|
||||
async.series(
|
||||
[
|
||||
function createMissingDirectories(callback) {
|
||||
async.each(Object.keys(conf.config.paths), function entry(pathKey, next) {
|
||||
mkdirs(conf.config.paths[pathKey], function dirCreated(err) {
|
||||
if(err) {
|
||||
console.error('Could not create path: ' + conf.config.paths[pathKey] + ': ' + err.toString());
|
||||
}
|
||||
return next(err);
|
||||
});
|
||||
}, function dirCreationComplete(err) {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function basicInit(callback) {
|
||||
logger.init();
|
||||
logger.log.info(
|
||||
{ version : require('../package.json').version },
|
||||
'**** ENiGMA½ Bulletin Board System Starting Up! ****');
|
||||
|
||||
process.on('SIGINT', shutdownSystem);
|
||||
process.on('SIGINT', shutdownSystem);
|
||||
|
||||
require('later').date.localTime(); // use local times for later.js/scheduling
|
||||
require('later').date.localTime(); // use local times for later.js/scheduling
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function initDatabases(callback) {
|
||||
return database.initializeDatabases(callback);
|
||||
},
|
||||
function initMimeTypes(callback) {
|
||||
return require('./mime_util.js').startup(callback);
|
||||
},
|
||||
function initStatLog(callback) {
|
||||
return require('./stat_log.js').init(callback);
|
||||
},
|
||||
function initConfigs(callback) {
|
||||
return require('./config_util.js').init(callback);
|
||||
},
|
||||
function initThemes(callback) {
|
||||
// Have to pull in here so it's after Config init
|
||||
require('./theme.js').initAvailableThemes( (err, themeCount) => {
|
||||
logger.log.info({ themeCount }, 'Themes initialized');
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function loadSysOpInformation(callback) {
|
||||
//
|
||||
// Copy over some +op information from the user DB -> system propertys.
|
||||
// * Makes this accessible for MCI codes, easy non-blocking access, etc.
|
||||
// * We do this every time as the op is free to change this information just
|
||||
// like any other user
|
||||
//
|
||||
const User = require('./user.js');
|
||||
return callback(null);
|
||||
},
|
||||
function initDatabases(callback) {
|
||||
return database.initializeDatabases(callback);
|
||||
},
|
||||
function initMimeTypes(callback) {
|
||||
return require('./mime_util.js').startup(callback);
|
||||
},
|
||||
function initStatLog(callback) {
|
||||
return require('./stat_log.js').init(callback);
|
||||
},
|
||||
function initConfigs(callback) {
|
||||
return require('./config_util.js').init(callback);
|
||||
},
|
||||
function initThemes(callback) {
|
||||
// Have to pull in here so it's after Config init
|
||||
require('./theme.js').initAvailableThemes( (err, themeCount) => {
|
||||
logger.log.info({ themeCount }, 'Themes initialized');
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function loadSysOpInformation(callback) {
|
||||
//
|
||||
// Copy over some +op information from the user DB -> system propertys.
|
||||
// * Makes this accessible for MCI codes, easy non-blocking access, etc.
|
||||
// * We do this every time as the op is free to change this information just
|
||||
// like any other user
|
||||
//
|
||||
const User = require('./user.js');
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function getOpUserName(next) {
|
||||
return User.getUserName(1, next);
|
||||
},
|
||||
function getOpProps(opUserName, next) {
|
||||
const propLoadOpts = {
|
||||
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
|
||||
};
|
||||
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
|
||||
return next(err, opUserName, opProps);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, opUserName, opProps) => {
|
||||
const StatLog = require('./stat_log.js');
|
||||
async.waterfall(
|
||||
[
|
||||
function getOpUserName(next) {
|
||||
return User.getUserName(1, next);
|
||||
},
|
||||
function getOpProps(opUserName, next) {
|
||||
const propLoadOpts = {
|
||||
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
|
||||
};
|
||||
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
|
||||
return next(err, opUserName, opProps);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, opUserName, opProps) => {
|
||||
const StatLog = require('./stat_log.js');
|
||||
|
||||
if(err) {
|
||||
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
|
||||
});
|
||||
} else {
|
||||
opProps.username = opUserName;
|
||||
if(err) {
|
||||
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
|
||||
});
|
||||
} else {
|
||||
opProps.username = opUserName;
|
||||
|
||||
_.each(opProps, (v, k) => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
||||
});
|
||||
}
|
||||
_.each(opProps, (v, k) => {
|
||||
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
||||
});
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initFileAreaStats(callback) {
|
||||
const getAreaStats = require('./file_base_area.js').getAreaStats;
|
||||
getAreaStats( (err, stats) => {
|
||||
if(!err) {
|
||||
const StatLog = require('./stat_log.js');
|
||||
StatLog.setNonPeristentSystemStat('file_base_area_stats', stats);
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initFileAreaStats(callback) {
|
||||
const getAreaStats = require('./file_base_area.js').getAreaStats;
|
||||
getAreaStats( (err, stats) => {
|
||||
if(!err) {
|
||||
const StatLog = require('./stat_log.js');
|
||||
StatLog.setNonPeristentSystemStat('file_base_area_stats', stats);
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
},
|
||||
function initMCI(callback) {
|
||||
return require('./predefined_mci.js').init(callback);
|
||||
},
|
||||
function readyMessageNetworkSupport(callback) {
|
||||
return require('./msg_network.js').startup(callback);
|
||||
},
|
||||
function readyEvents(callback) {
|
||||
return require('./events.js').startup(callback);
|
||||
},
|
||||
function listenConnections(callback) {
|
||||
return require('./listening_server.js').startup(callback);
|
||||
},
|
||||
function readyFileBaseArea(callback) {
|
||||
return require('./file_base_area.js').startup(callback);
|
||||
},
|
||||
function readyFileAreaWeb(callback) {
|
||||
return require('./file_area_web.js').startup(callback);
|
||||
},
|
||||
function readyPasswordReset(callback) {
|
||||
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;
|
||||
return WebPasswordReset.startup(callback);
|
||||
},
|
||||
function readyEventScheduler(callback) {
|
||||
const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule;
|
||||
EventSchedulerModule.loadAndStart( (err, modInst) => {
|
||||
initServices.eventScheduler = modInst;
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function onComplete(err) {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
return callback(null);
|
||||
});
|
||||
},
|
||||
function initMCI(callback) {
|
||||
return require('./predefined_mci.js').init(callback);
|
||||
},
|
||||
function readyMessageNetworkSupport(callback) {
|
||||
return require('./msg_network.js').startup(callback);
|
||||
},
|
||||
function readyEvents(callback) {
|
||||
return require('./events.js').startup(callback);
|
||||
},
|
||||
function listenConnections(callback) {
|
||||
return require('./listening_server.js').startup(callback);
|
||||
},
|
||||
function readyFileBaseArea(callback) {
|
||||
return require('./file_base_area.js').startup(callback);
|
||||
},
|
||||
function readyFileAreaWeb(callback) {
|
||||
return require('./file_area_web.js').startup(callback);
|
||||
},
|
||||
function readyPasswordReset(callback) {
|
||||
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;
|
||||
return WebPasswordReset.startup(callback);
|
||||
},
|
||||
function readyEventScheduler(callback) {
|
||||
const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule;
|
||||
EventSchedulerModule.loadAndStart( (err, modInst) => {
|
||||
initServices.eventScheduler = modInst;
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function onComplete(err) {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
282
core/bbs_link.js
282
core/bbs_link.js
|
@ -37,171 +37,171 @@ const packageJson = require('../package.json');
|
|||
// :TODO: ENH: Support nodeMax and tooManyArt
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'BBSLink',
|
||||
desc : 'BBSLink Access Module',
|
||||
author : 'NuSkooler',
|
||||
name : 'BBSLink',
|
||||
desc : 'BBSLink Access Module',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class BBSLinkModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'games.bbslink.net';
|
||||
this.config.port = this.config.port || 23;
|
||||
}
|
||||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'games.bbslink.net';
|
||||
this.config.port = this.config.port || 23;
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
let token;
|
||||
let randomKey;
|
||||
let clientTerminated;
|
||||
const self = this;
|
||||
initSequence() {
|
||||
let token;
|
||||
let randomKey;
|
||||
let clientTerminated;
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(_.isString(self.config.sysCode) &&
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(_.isString(self.config.sysCode) &&
|
||||
_.isString(self.config.authCode) &&
|
||||
_.isString(self.config.schemeCode) &&
|
||||
_.isString(self.config.door))
|
||||
{
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('Configuration is missing option(s)'));
|
||||
}
|
||||
},
|
||||
function acquireToken(callback) {
|
||||
//
|
||||
// Acquire an authentication token
|
||||
//
|
||||
crypto.randomBytes(16, function rand(ex, buf) {
|
||||
if(ex) {
|
||||
callback(ex);
|
||||
} else {
|
||||
randomKey = buf.toString('base64').substr(0, 6);
|
||||
self.simpleHttpRequest('/token.php?key=' + randomKey, null, function resp(err, body) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
token = body.trim();
|
||||
self.client.log.trace( { token : token }, 'BBSLink token');
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
function authenticateToken(callback) {
|
||||
//
|
||||
// Authenticate the token we acquired previously
|
||||
//
|
||||
var headers = {
|
||||
'X-User' : self.client.user.userId.toString(),
|
||||
'X-System' : self.config.sysCode,
|
||||
'X-Auth' : crypto.createHash('md5').update(self.config.authCode + token).digest('hex'),
|
||||
'X-Code' : crypto.createHash('md5').update(self.config.schemeCode + token).digest('hex'),
|
||||
'X-Rows' : self.client.term.termHeight.toString(),
|
||||
'X-Key' : randomKey,
|
||||
'X-Door' : self.config.door,
|
||||
'X-Token' : token,
|
||||
'X-Type' : 'enigma-bbs',
|
||||
'X-Version' : packageJson.version,
|
||||
};
|
||||
{
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('Configuration is missing option(s)'));
|
||||
}
|
||||
},
|
||||
function acquireToken(callback) {
|
||||
//
|
||||
// Acquire an authentication token
|
||||
//
|
||||
crypto.randomBytes(16, function rand(ex, buf) {
|
||||
if(ex) {
|
||||
callback(ex);
|
||||
} else {
|
||||
randomKey = buf.toString('base64').substr(0, 6);
|
||||
self.simpleHttpRequest('/token.php?key=' + randomKey, null, function resp(err, body) {
|
||||
if(err) {
|
||||
callback(err);
|
||||
} else {
|
||||
token = body.trim();
|
||||
self.client.log.trace( { token : token }, 'BBSLink token');
|
||||
callback(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
function authenticateToken(callback) {
|
||||
//
|
||||
// Authenticate the token we acquired previously
|
||||
//
|
||||
var headers = {
|
||||
'X-User' : self.client.user.userId.toString(),
|
||||
'X-System' : self.config.sysCode,
|
||||
'X-Auth' : crypto.createHash('md5').update(self.config.authCode + token).digest('hex'),
|
||||
'X-Code' : crypto.createHash('md5').update(self.config.schemeCode + token).digest('hex'),
|
||||
'X-Rows' : self.client.term.termHeight.toString(),
|
||||
'X-Key' : randomKey,
|
||||
'X-Door' : self.config.door,
|
||||
'X-Token' : token,
|
||||
'X-Type' : 'enigma-bbs',
|
||||
'X-Version' : packageJson.version,
|
||||
};
|
||||
|
||||
self.simpleHttpRequest('/auth.php?key=' + randomKey, headers, function resp(err, body) {
|
||||
var status = body.trim();
|
||||
self.simpleHttpRequest('/auth.php?key=' + randomKey, headers, function resp(err, body) {
|
||||
var status = body.trim();
|
||||
|
||||
if('complete' === status) {
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('Bad authentication status: ' + status));
|
||||
}
|
||||
});
|
||||
},
|
||||
function createTelnetBridge(callback) {
|
||||
//
|
||||
// Authentication with BBSLink successful. Now, we need to create a telnet
|
||||
// bridge from us to them
|
||||
//
|
||||
var connectOpts = {
|
||||
port : self.config.port,
|
||||
host : self.config.host,
|
||||
};
|
||||
if('complete' === status) {
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('Bad authentication status: ' + status));
|
||||
}
|
||||
});
|
||||
},
|
||||
function createTelnetBridge(callback) {
|
||||
//
|
||||
// Authentication with BBSLink successful. Now, we need to create a telnet
|
||||
// bridge from us to them
|
||||
//
|
||||
var connectOpts = {
|
||||
port : self.config.port,
|
||||
host : self.config.host,
|
||||
};
|
||||
|
||||
var clientTerminated;
|
||||
var clientTerminated;
|
||||
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write(' Connecting to BBSLink.net, please wait...\n');
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write(' Connecting to BBSLink.net, please wait...\n');
|
||||
|
||||
var bridgeConnection = net.createConnection(connectOpts, function connected() {
|
||||
self.client.log.info(connectOpts, 'BBSLink bridge connection established');
|
||||
var bridgeConnection = net.createConnection(connectOpts, function connected() {
|
||||
self.client.log.info(connectOpts, 'BBSLink bridge connection established');
|
||||
|
||||
self.client.term.output.pipe(bridgeConnection);
|
||||
self.client.term.output.pipe(bridgeConnection);
|
||||
|
||||
self.client.once('end', function clientEnd() {
|
||||
self.client.log.info('Connection ended. Terminating BBSLink connection');
|
||||
clientTerminated = true;
|
||||
bridgeConnection.end();
|
||||
});
|
||||
});
|
||||
self.client.once('end', function clientEnd() {
|
||||
self.client.log.info('Connection ended. Terminating BBSLink connection');
|
||||
clientTerminated = true;
|
||||
bridgeConnection.end();
|
||||
});
|
||||
});
|
||||
|
||||
var restorePipe = function() {
|
||||
self.client.term.output.unpipe(bridgeConnection);
|
||||
self.client.term.output.resume();
|
||||
};
|
||||
var restorePipe = function() {
|
||||
self.client.term.output.unpipe(bridgeConnection);
|
||||
self.client.term.output.resume();
|
||||
};
|
||||
|
||||
bridgeConnection.on('data', function incomingData(data) {
|
||||
// pass along
|
||||
// :TODO: just pipe this as well
|
||||
self.client.term.rawWrite(data);
|
||||
});
|
||||
bridgeConnection.on('data', function incomingData(data) {
|
||||
// pass along
|
||||
// :TODO: just pipe this as well
|
||||
self.client.term.rawWrite(data);
|
||||
});
|
||||
|
||||
bridgeConnection.on('end', function connectionEnd() {
|
||||
restorePipe();
|
||||
callback(clientTerminated ? new Error('Client connection terminated') : null);
|
||||
});
|
||||
bridgeConnection.on('end', function connectionEnd() {
|
||||
restorePipe();
|
||||
callback(clientTerminated ? new Error('Client connection terminated') : null);
|
||||
});
|
||||
|
||||
bridgeConnection.on('error', function error(err) {
|
||||
self.client.log.info('BBSLink bridge connection error: ' + err.message);
|
||||
restorePipe();
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
|
||||
}
|
||||
bridgeConnection.on('error', function error(err) {
|
||||
self.client.log.info('BBSLink bridge connection error: ' + err.message);
|
||||
restorePipe();
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
|
||||
}
|
||||
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
simpleHttpRequest(path, headers, cb) {
|
||||
const getOpts = {
|
||||
host : this.config.host,
|
||||
path : path,
|
||||
headers : headers,
|
||||
};
|
||||
simpleHttpRequest(path, headers, cb) {
|
||||
const getOpts = {
|
||||
host : this.config.host,
|
||||
path : path,
|
||||
headers : headers,
|
||||
};
|
||||
|
||||
const req = http.get(getOpts, function response(resp) {
|
||||
let data = '';
|
||||
const req = http.get(getOpts, function response(resp) {
|
||||
let data = '';
|
||||
|
||||
resp.on('data', function chunk(c) {
|
||||
data += c;
|
||||
});
|
||||
resp.on('data', function chunk(c) {
|
||||
data += c;
|
||||
});
|
||||
|
||||
resp.on('end', function respEnd() {
|
||||
cb(null, data);
|
||||
req.end();
|
||||
});
|
||||
});
|
||||
resp.on('end', function respEnd() {
|
||||
cb(null, data);
|
||||
req.end();
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', function reqErr(err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
req.on('error', function reqErr(err) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
708
core/bbs_list.js
708
core/bbs_list.js
|
@ -5,8 +5,8 @@
|
|||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
|
||||
const {
|
||||
getModDatabasePath,
|
||||
getTransactionDatabase
|
||||
getModDatabasePath,
|
||||
getTransactionDatabase
|
||||
} = require('./database.js');
|
||||
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
|
@ -23,397 +23,397 @@ const _ = require('lodash');
|
|||
// :TODO: add notes field
|
||||
|
||||
const moduleInfo = exports.moduleInfo = {
|
||||
name : 'BBS List',
|
||||
desc : 'List of other BBSes',
|
||||
author : 'Andrew Pamment',
|
||||
packageName : 'com.magickabbs.enigma.bbslist'
|
||||
name : 'BBS List',
|
||||
desc : 'List of other BBSes',
|
||||
author : 'Andrew Pamment',
|
||||
packageName : 'com.magickabbs.enigma.bbslist'
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
view : {
|
||||
BBSList : 1,
|
||||
SelectedBBSName : 2,
|
||||
SelectedBBSSysOp : 3,
|
||||
SelectedBBSTelnet : 4,
|
||||
SelectedBBSWww : 5,
|
||||
SelectedBBSLoc : 6,
|
||||
SelectedBBSSoftware : 7,
|
||||
SelectedBBSNotes : 8,
|
||||
SelectedBBSSubmitter : 9,
|
||||
},
|
||||
add : {
|
||||
BBSName : 1,
|
||||
Sysop : 2,
|
||||
Telnet : 3,
|
||||
Www : 4,
|
||||
Location : 5,
|
||||
Software : 6,
|
||||
Notes : 7,
|
||||
Error : 8,
|
||||
}
|
||||
view : {
|
||||
BBSList : 1,
|
||||
SelectedBBSName : 2,
|
||||
SelectedBBSSysOp : 3,
|
||||
SelectedBBSTelnet : 4,
|
||||
SelectedBBSWww : 5,
|
||||
SelectedBBSLoc : 6,
|
||||
SelectedBBSSoftware : 7,
|
||||
SelectedBBSNotes : 8,
|
||||
SelectedBBSSubmitter : 9,
|
||||
},
|
||||
add : {
|
||||
BBSName : 1,
|
||||
Sysop : 2,
|
||||
Telnet : 3,
|
||||
Www : 4,
|
||||
Location : 5,
|
||||
Software : 6,
|
||||
Notes : 7,
|
||||
Error : 8,
|
||||
}
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
View : 0,
|
||||
Add : 1,
|
||||
View : 0,
|
||||
Add : 1,
|
||||
};
|
||||
|
||||
const SELECTED_MCI_NAME_TO_ENTRY = {
|
||||
SelectedBBSName : 'bbsName',
|
||||
SelectedBBSSysOp : 'sysOp',
|
||||
SelectedBBSTelnet : 'telnet',
|
||||
SelectedBBSWww : 'www',
|
||||
SelectedBBSLoc : 'location',
|
||||
SelectedBBSSoftware : 'software',
|
||||
SelectedBBSSubmitter : 'submitter',
|
||||
SelectedBBSSubmitterId : 'submitterUserId',
|
||||
SelectedBBSNotes : 'notes',
|
||||
SelectedBBSName : 'bbsName',
|
||||
SelectedBBSSysOp : 'sysOp',
|
||||
SelectedBBSTelnet : 'telnet',
|
||||
SelectedBBSWww : 'www',
|
||||
SelectedBBSLoc : 'location',
|
||||
SelectedBBSSoftware : 'software',
|
||||
SelectedBBSSubmitter : 'submitter',
|
||||
SelectedBBSSubmitterId : 'submitterUserId',
|
||||
SelectedBBSNotes : 'notes',
|
||||
};
|
||||
|
||||
exports.getModule = class BBSListModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const self = this;
|
||||
this.menuMethods = {
|
||||
//
|
||||
// Validators
|
||||
//
|
||||
viewValidationListener : function(err, cb) {
|
||||
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
|
||||
if(errMsgView) {
|
||||
if(err) {
|
||||
errMsgView.setText(err.message);
|
||||
} else {
|
||||
errMsgView.clearText();
|
||||
}
|
||||
}
|
||||
const self = this;
|
||||
this.menuMethods = {
|
||||
//
|
||||
// Validators
|
||||
//
|
||||
viewValidationListener : function(err, cb) {
|
||||
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
|
||||
if(errMsgView) {
|
||||
if(err) {
|
||||
errMsgView.setText(err.message);
|
||||
} else {
|
||||
errMsgView.clearText();
|
||||
}
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
//
|
||||
// Key & submit handlers
|
||||
//
|
||||
addBBS : function(formData, extraArgs, cb) {
|
||||
self.displayAddScreen(cb);
|
||||
},
|
||||
deleteBBS : function(formData, extraArgs, cb) {
|
||||
if(!_.isNumber(self.selectedBBS) || 0 === self.entries.length) {
|
||||
return cb(null);
|
||||
}
|
||||
//
|
||||
// Key & submit handlers
|
||||
//
|
||||
addBBS : function(formData, extraArgs, cb) {
|
||||
self.displayAddScreen(cb);
|
||||
},
|
||||
deleteBBS : function(formData, extraArgs, cb) {
|
||||
if(!_.isNumber(self.selectedBBS) || 0 === self.entries.length) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
|
||||
|
||||
if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) {
|
||||
// must be owner or +op
|
||||
return cb(null);
|
||||
}
|
||||
if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) {
|
||||
// must be owner or +op
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const entry = self.entries[self.selectedBBS];
|
||||
if(!entry) {
|
||||
return cb(null);
|
||||
}
|
||||
const entry = self.entries[self.selectedBBS];
|
||||
if(!entry) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
self.database.run(
|
||||
`DELETE FROM bbs_list
|
||||
self.database.run(
|
||||
`DELETE FROM bbs_list
|
||||
WHERE id=?;`,
|
||||
[ entry.id ],
|
||||
err => {
|
||||
if (err) {
|
||||
self.client.log.error( { err : err }, 'Error deleting from BBS list');
|
||||
} else {
|
||||
self.entries.splice(self.selectedBBS, 1);
|
||||
[ entry.id ],
|
||||
err => {
|
||||
if (err) {
|
||||
self.client.log.error( { err : err }, 'Error deleting from BBS list');
|
||||
} else {
|
||||
self.entries.splice(self.selectedBBS, 1);
|
||||
|
||||
self.setEntries(entriesView);
|
||||
self.setEntries(entriesView);
|
||||
|
||||
if(self.entries.length > 0) {
|
||||
entriesView.focusPrevious();
|
||||
}
|
||||
if(self.entries.length > 0) {
|
||||
entriesView.focusPrevious();
|
||||
}
|
||||
|
||||
self.viewControllers.view.redrawAll();
|
||||
}
|
||||
self.viewControllers.view.redrawAll();
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
submitBBS : function(formData, extraArgs, cb) {
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
submitBBS : function(formData, extraArgs, cb) {
|
||||
|
||||
let ok = true;
|
||||
[ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => {
|
||||
if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) {
|
||||
ok = false;
|
||||
}
|
||||
});
|
||||
if(!ok) {
|
||||
// validators should prevent this!
|
||||
return cb(null);
|
||||
}
|
||||
let ok = true;
|
||||
[ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => {
|
||||
if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) {
|
||||
ok = false;
|
||||
}
|
||||
});
|
||||
if(!ok) {
|
||||
// validators should prevent this!
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
self.database.run(
|
||||
`INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes)
|
||||
self.database.run(
|
||||
`INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||
[
|
||||
formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www,
|
||||
formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { err : err }, 'Error adding to BBS list');
|
||||
}
|
||||
[
|
||||
formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www,
|
||||
formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { err : err }, 'Error adding to BBS list');
|
||||
}
|
||||
|
||||
self.clearAddForm();
|
||||
self.displayBBSList(true, cb);
|
||||
}
|
||||
);
|
||||
},
|
||||
cancelSubmit : function(formData, extraArgs, cb) {
|
||||
self.clearAddForm();
|
||||
self.displayBBSList(true, cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
self.clearAddForm();
|
||||
self.displayBBSList(true, cb);
|
||||
}
|
||||
);
|
||||
},
|
||||
cancelSubmit : function(formData, extraArgs, cb) {
|
||||
self.clearAddForm();
|
||||
self.displayBBSList(true, cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
self.displayBBSList(false, callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||
}
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
initSequence() {
|
||||
const self = this;
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
self.displayBBSList(false, callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||
}
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
drawSelectedEntry(entry) {
|
||||
if(!entry) {
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
this.setViewText('view', MciViewIds.view[mciName], '');
|
||||
});
|
||||
} else {
|
||||
const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)';
|
||||
drawSelectedEntry(entry) {
|
||||
if(!entry) {
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
this.setViewText('view', MciViewIds.view[mciName], '');
|
||||
});
|
||||
} else {
|
||||
const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)';
|
||||
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]];
|
||||
if(MciViewIds.view[mciName]) {
|
||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||
const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]];
|
||||
if(MciViewIds.view[mciName]) {
|
||||
|
||||
if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId) {
|
||||
this.setViewText('view',MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry));
|
||||
} else {
|
||||
this.setViewText('view',MciViewIds.view[mciName], t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId) {
|
||||
this.setViewText('view',MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry));
|
||||
} else {
|
||||
this.setViewText('view',MciViewIds.view[mciName], t);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setEntries(entriesView) {
|
||||
const config = this.menuConfig.config;
|
||||
const listFormat = config.listFormat || '{bbsName}';
|
||||
const focusListFormat = config.focusListFormat || '{bbsName}';
|
||||
setEntries(entriesView) {
|
||||
const config = this.menuConfig.config;
|
||||
const listFormat = config.listFormat || '{bbsName}';
|
||||
const focusListFormat = config.focusListFormat || '{bbsName}';
|
||||
|
||||
entriesView.setItems(this.entries.map( e => stringFormat(listFormat, e) ) );
|
||||
entriesView.setFocusItems(this.entries.map( e => stringFormat(focusListFormat, e) ) );
|
||||
}
|
||||
entriesView.setItems(this.entries.map( e => stringFormat(listFormat, e) ) );
|
||||
entriesView.setFocusItems(this.entries.map( e => stringFormat(focusListFormat, e) ) );
|
||||
}
|
||||
|
||||
displayBBSList(clearScreen, cb) {
|
||||
const self = this;
|
||||
displayBBSList(clearScreen, cb) {
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if(self.viewControllers.add) {
|
||||
self.viewControllers.add.setFocus(false);
|
||||
}
|
||||
if (clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.entries,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if(self.viewControllers.add) {
|
||||
self.viewControllers.add.setFocus(false);
|
||||
}
|
||||
if (clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.entries,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.View,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.View,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
|
||||
self.entries = [];
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
|
||||
self.entries = [];
|
||||
|
||||
self.database.each(
|
||||
`SELECT id, bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes
|
||||
self.database.each(
|
||||
`SELECT id, bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes
|
||||
FROM bbs_list;`,
|
||||
(err, row) => {
|
||||
if (!err) {
|
||||
self.entries.push({
|
||||
id : row.id,
|
||||
bbsName : row.bbs_name,
|
||||
sysOp : row.sysop,
|
||||
telnet : row.telnet,
|
||||
www : row.www,
|
||||
location : row.location,
|
||||
software : row.software,
|
||||
submitterUserId : row.submitter_user_id,
|
||||
notes : row.notes,
|
||||
});
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return callback(err, entriesView);
|
||||
}
|
||||
);
|
||||
},
|
||||
function getUserNames(entriesView, callback) {
|
||||
async.each(self.entries, (entry, next) => {
|
||||
User.getUserName(entry.submitterUserId, (err, username) => {
|
||||
if(username) {
|
||||
entry.submitter = username;
|
||||
} else {
|
||||
entry.submitter = 'N/A';
|
||||
}
|
||||
return next();
|
||||
});
|
||||
}, () => {
|
||||
return callback(null, entriesView);
|
||||
});
|
||||
},
|
||||
function populateEntries(entriesView, callback) {
|
||||
self.setEntries(entriesView);
|
||||
(err, row) => {
|
||||
if (!err) {
|
||||
self.entries.push({
|
||||
id : row.id,
|
||||
bbsName : row.bbs_name,
|
||||
sysOp : row.sysop,
|
||||
telnet : row.telnet,
|
||||
www : row.www,
|
||||
location : row.location,
|
||||
software : row.software,
|
||||
submitterUserId : row.submitter_user_id,
|
||||
notes : row.notes,
|
||||
});
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return callback(err, entriesView);
|
||||
}
|
||||
);
|
||||
},
|
||||
function getUserNames(entriesView, callback) {
|
||||
async.each(self.entries, (entry, next) => {
|
||||
User.getUserName(entry.submitterUserId, (err, username) => {
|
||||
if(username) {
|
||||
entry.submitter = username;
|
||||
} else {
|
||||
entry.submitter = 'N/A';
|
||||
}
|
||||
return next();
|
||||
});
|
||||
}, () => {
|
||||
return callback(null, entriesView);
|
||||
});
|
||||
},
|
||||
function populateEntries(entriesView, callback) {
|
||||
self.setEntries(entriesView);
|
||||
|
||||
entriesView.on('index update', idx => {
|
||||
const entry = self.entries[idx];
|
||||
entriesView.on('index update', idx => {
|
||||
const entry = self.entries[idx];
|
||||
|
||||
self.drawSelectedEntry(entry);
|
||||
self.drawSelectedEntry(entry);
|
||||
|
||||
if(!entry) {
|
||||
self.selectedBBS = -1;
|
||||
} else {
|
||||
self.selectedBBS = idx;
|
||||
}
|
||||
});
|
||||
if(!entry) {
|
||||
self.selectedBBS = -1;
|
||||
} else {
|
||||
self.selectedBBS = idx;
|
||||
}
|
||||
});
|
||||
|
||||
if (self.selectedBBS >= 0) {
|
||||
entriesView.setFocusItemIndex(self.selectedBBS);
|
||||
self.drawSelectedEntry(self.entries[self.selectedBBS]);
|
||||
} else if (self.entries.length > 0) {
|
||||
self.selectedBBS = 0;
|
||||
entriesView.setFocusItemIndex(0);
|
||||
self.drawSelectedEntry(self.entries[0]);
|
||||
}
|
||||
if (self.selectedBBS >= 0) {
|
||||
entriesView.setFocusItemIndex(self.selectedBBS);
|
||||
self.drawSelectedEntry(self.entries[self.selectedBBS]);
|
||||
} else if (self.entries.length > 0) {
|
||||
self.selectedBBS = 0;
|
||||
entriesView.setFocusItemIndex(0);
|
||||
self.drawSelectedEntry(self.entries[0]);
|
||||
}
|
||||
|
||||
entriesView.redraw();
|
||||
entriesView.redraw();
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayAddScreen(cb) {
|
||||
const self = this;
|
||||
displayAddScreen(cb) {
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.add,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.add,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.Add,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.Add,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciViewIds.add.BBSName);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciViewIds.add.BBSName);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
clearAddForm() {
|
||||
[ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => {
|
||||
this.setViewText('add', MciViewIds.add[mciName], '');
|
||||
});
|
||||
}
|
||||
clearAddForm() {
|
||||
[ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => {
|
||||
this.setViewText('add', MciViewIds.add[mciName], '');
|
||||
});
|
||||
}
|
||||
|
||||
initDatabase(cb) {
|
||||
const self = this;
|
||||
initDatabase(cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function openDatabase(callback) {
|
||||
self.database = getTransactionDatabase(new sqlite3.Database(
|
||||
getModDatabasePath(moduleInfo),
|
||||
callback
|
||||
));
|
||||
},
|
||||
function createTables(callback) {
|
||||
self.database.serialize( () => {
|
||||
self.database.run(
|
||||
`CREATE TABLE IF NOT EXISTS bbs_list (
|
||||
async.series(
|
||||
[
|
||||
function openDatabase(callback) {
|
||||
self.database = getTransactionDatabase(new sqlite3.Database(
|
||||
getModDatabasePath(moduleInfo),
|
||||
callback
|
||||
));
|
||||
},
|
||||
function createTables(callback) {
|
||||
self.database.serialize( () => {
|
||||
self.database.run(
|
||||
`CREATE TABLE IF NOT EXISTS bbs_list (
|
||||
id INTEGER PRIMARY KEY,
|
||||
bbs_name VARCHAR NOT NULL,
|
||||
sysop VARCHAR NOT NULL,
|
||||
|
@ -424,20 +424,20 @@ exports.getModule = class BBSListModule extends MenuModule {
|
|||
submitter_user_id INTEGER NOT NULL,
|
||||
notes VARCHAR
|
||||
);`
|
||||
);
|
||||
});
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
beforeArt(cb) {
|
||||
super.beforeArt(err => {
|
||||
return err ? cb(err) : this.initDatabase(cb);
|
||||
});
|
||||
}
|
||||
beforeArt(cb) {
|
||||
super.beforeArt(err => {
|
||||
return err ? cb(err) : this.initDatabase(cb);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,24 +8,24 @@ const util = require('util');
|
|||
exports.ButtonView = ButtonView;
|
||||
|
||||
function ButtonView(options) {
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.justify = miscUtil.valueWithDefault(options.justify, 'center');
|
||||
options.cursor = miscUtil.valueWithDefault(options.cursor, 'hide');
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.justify = miscUtil.valueWithDefault(options.justify, 'center');
|
||||
options.cursor = miscUtil.valueWithDefault(options.cursor, 'hide');
|
||||
|
||||
TextView.call(this, options);
|
||||
TextView.call(this, options);
|
||||
}
|
||||
|
||||
util.inherits(ButtonView, TextView);
|
||||
|
||||
ButtonView.prototype.onKeyPress = function(ch, key) {
|
||||
if(this.isKeyMapped('accept', key.name) || ' ' === ch) {
|
||||
this.submitData = 'accept';
|
||||
this.emit('action', 'accept');
|
||||
delete this.submitData;
|
||||
} else {
|
||||
ButtonView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
}
|
||||
if(this.isKeyMapped('accept', key.name) || ' ' === ch) {
|
||||
this.submitData = 'accept';
|
||||
this.emit('action', 'accept');
|
||||
delete this.submitData;
|
||||
} else {
|
||||
ButtonView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
}
|
||||
};
|
||||
/*
|
||||
ButtonView.prototype.onKeyPress = function(ch, key) {
|
||||
|
@ -39,5 +39,5 @@ ButtonView.prototype.onKeyPress = function(ch, key) {
|
|||
*/
|
||||
|
||||
ButtonView.prototype.getData = function() {
|
||||
return this.submitData || null;
|
||||
return this.submitData || null;
|
||||
};
|
||||
|
|
718
core/client.js
718
core/client.js
|
@ -58,442 +58,442 @@ const RE_DEV_ATTR_RESPONSE_ANYWHERE = /(?:\u001b\[)[=?]([0-9a-zA-Z;]+)(c)/;
|
|||
const RE_META_KEYCODE_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
|
||||
const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
||||
const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
||||
'(\\d+)(?:;(\\d+))?([~^$])',
|
||||
'(?:M([@ #!a`])(.)(.))', // mouse stuff
|
||||
'(?:1;)?(\\d+)?([a-zA-Z@])'
|
||||
'(\\d+)(?:;(\\d+))?([~^$])',
|
||||
'(?:M([@ #!a`])(.)(.))', // mouse stuff
|
||||
'(?:1;)?(\\d+)?([a-zA-Z@])'
|
||||
].join('|') + ')');
|
||||
|
||||
const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
|
||||
const RE_ESC_CODE_ANYWHERE = new RegExp( [
|
||||
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||
RE_META_KEYCODE_ANYWHERE.source,
|
||||
RE_DSR_RESPONSE_ANYWHERE.source,
|
||||
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
|
||||
/\u001b./.source
|
||||
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||
RE_META_KEYCODE_ANYWHERE.source,
|
||||
RE_DSR_RESPONSE_ANYWHERE.source,
|
||||
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
|
||||
/\u001b./.source
|
||||
].join('|'));
|
||||
|
||||
|
||||
function Client(/*input, output*/) {
|
||||
stream.call(this);
|
||||
stream.call(this);
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
this.user = new User();
|
||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||
this.lastKeyPressMs = Date.now();
|
||||
this.menuStack = new MenuStack(this);
|
||||
this.acs = new ACS(this);
|
||||
this.mciCache = {};
|
||||
this.user = new User();
|
||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||
this.lastKeyPressMs = Date.now();
|
||||
this.menuStack = new MenuStack(this);
|
||||
this.acs = new ACS(this);
|
||||
this.mciCache = {};
|
||||
|
||||
this.clearMciCache = function() {
|
||||
this.mciCache = {};
|
||||
};
|
||||
this.clearMciCache = function() {
|
||||
this.mciCache = {};
|
||||
};
|
||||
|
||||
Object.defineProperty(this, 'node', {
|
||||
get : function() {
|
||||
return self.session.id + 1;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'node', {
|
||||
get : function() {
|
||||
return self.session.id + 1;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'currentMenuModule', {
|
||||
get : function() {
|
||||
return self.menuStack.currentModule;
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'currentMenuModule', {
|
||||
get : function() {
|
||||
return self.menuStack.currentModule;
|
||||
}
|
||||
});
|
||||
|
||||
this.setTemporaryDirectDataHandler = function(handler) {
|
||||
this.input.removeAllListeners('data');
|
||||
this.input.on('data', handler);
|
||||
};
|
||||
this.setTemporaryDirectDataHandler = function(handler) {
|
||||
this.input.removeAllListeners('data');
|
||||
this.input.on('data', handler);
|
||||
};
|
||||
|
||||
this.restoreDataHandler = function() {
|
||||
this.input.removeAllListeners('data');
|
||||
this.input.on('data', this.dataHandler);
|
||||
};
|
||||
this.restoreDataHandler = function() {
|
||||
this.input.removeAllListeners('data');
|
||||
this.input.on('data', this.dataHandler);
|
||||
};
|
||||
|
||||
Events.on(Events.getSystemEvents().ThemeChanged, ( { themeId } ) => {
|
||||
if(_.get(this.currentTheme, 'info.themeId') === themeId) {
|
||||
this.currentTheme = require('./theme.js').getAvailableThemes().get(themeId);
|
||||
}
|
||||
});
|
||||
Events.on(Events.getSystemEvents().ThemeChanged, ( { themeId } ) => {
|
||||
if(_.get(this.currentTheme, 'info.themeId') === themeId) {
|
||||
this.currentTheme = require('./theme.js').getAvailableThemes().get(themeId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//
|
||||
// Peek at incoming |data| and emit events for any special
|
||||
// handling that may include:
|
||||
// * Keyboard input
|
||||
// * ANSI CSR's and the like
|
||||
//
|
||||
// References:
|
||||
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
||||
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
||||
//
|
||||
this.getTermClient = function(deviceAttr) {
|
||||
let termClient = {
|
||||
//
|
||||
// See http://www.fbl.cz/arctel/download/techman.pdf
|
||||
//
|
||||
// Known clients:
|
||||
// * Irssi ConnectBot (Android)
|
||||
//
|
||||
'63;1;2' : 'arctel',
|
||||
'50;86;84;88' : 'vtx',
|
||||
}[deviceAttr];
|
||||
//
|
||||
// Peek at incoming |data| and emit events for any special
|
||||
// handling that may include:
|
||||
// * Keyboard input
|
||||
// * ANSI CSR's and the like
|
||||
//
|
||||
// References:
|
||||
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
||||
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
||||
//
|
||||
this.getTermClient = function(deviceAttr) {
|
||||
let termClient = {
|
||||
//
|
||||
// See http://www.fbl.cz/arctel/download/techman.pdf
|
||||
//
|
||||
// Known clients:
|
||||
// * Irssi ConnectBot (Android)
|
||||
//
|
||||
'63;1;2' : 'arctel',
|
||||
'50;86;84;88' : 'vtx',
|
||||
}[deviceAttr];
|
||||
|
||||
if(!termClient) {
|
||||
if(_.startsWith(deviceAttr, '67;84;101;114;109')) {
|
||||
//
|
||||
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
||||
//
|
||||
// Known clients:
|
||||
// * SyncTERM
|
||||
//
|
||||
termClient = 'cterm';
|
||||
}
|
||||
}
|
||||
if(!termClient) {
|
||||
if(_.startsWith(deviceAttr, '67;84;101;114;109')) {
|
||||
//
|
||||
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
||||
//
|
||||
// Known clients:
|
||||
// * SyncTERM
|
||||
//
|
||||
termClient = 'cterm';
|
||||
}
|
||||
}
|
||||
|
||||
return termClient;
|
||||
};
|
||||
return termClient;
|
||||
};
|
||||
|
||||
this.isMouseInput = function(data) {
|
||||
return /\x1b\[M/.test(data) || // eslint-disable-line no-control-regex
|
||||
this.isMouseInput = function(data) {
|
||||
return /\x1b\[M/.test(data) || // eslint-disable-line no-control-regex
|
||||
/\u001b\[M([\x00\u0020-\uffff]{3})/.test(data) || // eslint-disable-line no-control-regex
|
||||
/\u001b\[(\d+;\d+;\d+)M/.test(data) ||
|
||||
/\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) ||
|
||||
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) ||
|
||||
/\u001b\[24([0135])~\[(\d+),(\d+)\]\r/.test(data) ||
|
||||
/\u001b\[(O|I)/.test(data);
|
||||
};
|
||||
};
|
||||
|
||||
this.getKeyComponentsFromCode = function(code) {
|
||||
return {
|
||||
// xterm/gnome
|
||||
'OP' : { name : 'f1' },
|
||||
'OQ' : { name : 'f2' },
|
||||
'OR' : { name : 'f3' },
|
||||
'OS' : { name : 'f4' },
|
||||
this.getKeyComponentsFromCode = function(code) {
|
||||
return {
|
||||
// xterm/gnome
|
||||
'OP' : { name : 'f1' },
|
||||
'OQ' : { name : 'f2' },
|
||||
'OR' : { name : 'f3' },
|
||||
'OS' : { name : 'f4' },
|
||||
|
||||
'OA' : { name : 'up arrow' },
|
||||
'OB' : { name : 'down arrow' },
|
||||
'OC' : { name : 'right arrow' },
|
||||
'OD' : { name : 'left arrow' },
|
||||
'OE' : { name : 'clear' },
|
||||
'OF' : { name : 'end' },
|
||||
'OH' : { name : 'home' },
|
||||
'OA' : { name : 'up arrow' },
|
||||
'OB' : { name : 'down arrow' },
|
||||
'OC' : { name : 'right arrow' },
|
||||
'OD' : { name : 'left arrow' },
|
||||
'OE' : { name : 'clear' },
|
||||
'OF' : { name : 'end' },
|
||||
'OH' : { name : 'home' },
|
||||
|
||||
// xterm/rxvt
|
||||
'[11~' : { name : 'f1' },
|
||||
'[12~' : { name : 'f2' },
|
||||
'[13~' : { name : 'f3' },
|
||||
'[14~' : { name : 'f4' },
|
||||
// xterm/rxvt
|
||||
'[11~' : { name : 'f1' },
|
||||
'[12~' : { name : 'f2' },
|
||||
'[13~' : { name : 'f3' },
|
||||
'[14~' : { name : 'f4' },
|
||||
|
||||
'[1~' : { name : 'home' },
|
||||
'[2~' : { name : 'insert' },
|
||||
'[3~' : { name : 'delete' },
|
||||
'[4~' : { name : 'end' },
|
||||
'[5~' : { name : 'page up' },
|
||||
'[6~' : { name : 'page down' },
|
||||
'[1~' : { name : 'home' },
|
||||
'[2~' : { name : 'insert' },
|
||||
'[3~' : { name : 'delete' },
|
||||
'[4~' : { name : 'end' },
|
||||
'[5~' : { name : 'page up' },
|
||||
'[6~' : { name : 'page down' },
|
||||
|
||||
// Cygwin & libuv
|
||||
'[[A' : { name : 'f1' },
|
||||
'[[B' : { name : 'f2' },
|
||||
'[[C' : { name : 'f3' },
|
||||
'[[D' : { name : 'f4' },
|
||||
'[[E' : { name : 'f5' },
|
||||
// Cygwin & libuv
|
||||
'[[A' : { name : 'f1' },
|
||||
'[[B' : { name : 'f2' },
|
||||
'[[C' : { name : 'f3' },
|
||||
'[[D' : { name : 'f4' },
|
||||
'[[E' : { name : 'f5' },
|
||||
|
||||
// Common impls
|
||||
'[15~' : { name : 'f5' },
|
||||
'[17~' : { name : 'f6' },
|
||||
'[18~' : { name : 'f7' },
|
||||
'[19~' : { name : 'f8' },
|
||||
'[20~' : { name : 'f9' },
|
||||
'[21~' : { name : 'f10' },
|
||||
'[23~' : { name : 'f11' },
|
||||
'[24~' : { name : 'f12' },
|
||||
// Common impls
|
||||
'[15~' : { name : 'f5' },
|
||||
'[17~' : { name : 'f6' },
|
||||
'[18~' : { name : 'f7' },
|
||||
'[19~' : { name : 'f8' },
|
||||
'[20~' : { name : 'f9' },
|
||||
'[21~' : { name : 'f10' },
|
||||
'[23~' : { name : 'f11' },
|
||||
'[24~' : { name : 'f12' },
|
||||
|
||||
// xterm
|
||||
'[A' : { name : 'up arrow' },
|
||||
'[B' : { name : 'down arrow' },
|
||||
'[C' : { name : 'right arrow' },
|
||||
'[D' : { name : 'left arrow' },
|
||||
'[E' : { name : 'clear' },
|
||||
'[F' : { name : 'end' },
|
||||
'[H' : { name : 'home' },
|
||||
// xterm
|
||||
'[A' : { name : 'up arrow' },
|
||||
'[B' : { name : 'down arrow' },
|
||||
'[C' : { name : 'right arrow' },
|
||||
'[D' : { name : 'left arrow' },
|
||||
'[E' : { name : 'clear' },
|
||||
'[F' : { name : 'end' },
|
||||
'[H' : { name : 'home' },
|
||||
|
||||
// PuTTY
|
||||
'[[5~' : { name : 'page up' },
|
||||
'[[6~' : { name : 'page down' },
|
||||
// PuTTY
|
||||
'[[5~' : { name : 'page up' },
|
||||
'[[6~' : { name : 'page down' },
|
||||
|
||||
// rvxt
|
||||
'[7~' : { name : 'home' },
|
||||
'[8~' : { name : 'end' },
|
||||
// rvxt
|
||||
'[7~' : { name : 'home' },
|
||||
'[8~' : { name : 'end' },
|
||||
|
||||
// rxvt with modifiers
|
||||
'[a' : { name : 'up arrow', shift : true },
|
||||
'[b' : { name : 'down arrow', shift : true },
|
||||
'[c' : { name : 'right arrow', shift : true },
|
||||
'[d' : { name : 'left arrow', shift : true },
|
||||
'[e' : { name : 'clear', shift : true },
|
||||
// rxvt with modifiers
|
||||
'[a' : { name : 'up arrow', shift : true },
|
||||
'[b' : { name : 'down arrow', shift : true },
|
||||
'[c' : { name : 'right arrow', shift : true },
|
||||
'[d' : { name : 'left arrow', shift : true },
|
||||
'[e' : { name : 'clear', shift : true },
|
||||
|
||||
'[2$' : { name : 'insert', shift : true },
|
||||
'[3$' : { name : 'delete', shift : true },
|
||||
'[5$' : { name : 'page up', shift : true },
|
||||
'[6$' : { name : 'page down', shift : true },
|
||||
'[7$' : { name : 'home', shift : true },
|
||||
'[8$' : { name : 'end', shift : true },
|
||||
'[2$' : { name : 'insert', shift : true },
|
||||
'[3$' : { name : 'delete', shift : true },
|
||||
'[5$' : { name : 'page up', shift : true },
|
||||
'[6$' : { name : 'page down', shift : true },
|
||||
'[7$' : { name : 'home', shift : true },
|
||||
'[8$' : { name : 'end', shift : true },
|
||||
|
||||
'Oa' : { name : 'up arrow', ctrl : true },
|
||||
'Ob' : { name : 'down arrow', ctrl : true },
|
||||
'Oc' : { name : 'right arrow', ctrl : true },
|
||||
'Od' : { name : 'left arrow', ctrl : true },
|
||||
'Oe' : { name : 'clear', ctrl : true },
|
||||
'Oa' : { name : 'up arrow', ctrl : true },
|
||||
'Ob' : { name : 'down arrow', ctrl : true },
|
||||
'Oc' : { name : 'right arrow', ctrl : true },
|
||||
'Od' : { name : 'left arrow', ctrl : true },
|
||||
'Oe' : { name : 'clear', ctrl : true },
|
||||
|
||||
'[2^' : { name : 'insert', ctrl : true },
|
||||
'[3^' : { name : 'delete', ctrl : true },
|
||||
'[5^' : { name : 'page up', ctrl : true },
|
||||
'[6^' : { name : 'page down', ctrl : true },
|
||||
'[7^' : { name : 'home', ctrl : true },
|
||||
'[8^' : { name : 'end', ctrl : true },
|
||||
'[2^' : { name : 'insert', ctrl : true },
|
||||
'[3^' : { name : 'delete', ctrl : true },
|
||||
'[5^' : { name : 'page up', ctrl : true },
|
||||
'[6^' : { name : 'page down', ctrl : true },
|
||||
'[7^' : { name : 'home', ctrl : true },
|
||||
'[8^' : { name : 'end', ctrl : true },
|
||||
|
||||
// SyncTERM / EtherTerm
|
||||
'[K' : { name : 'end' },
|
||||
'[@' : { name : 'insert' },
|
||||
'[V' : { name : 'page up' },
|
||||
'[U' : { name : 'page down' },
|
||||
// SyncTERM / EtherTerm
|
||||
'[K' : { name : 'end' },
|
||||
'[@' : { name : 'insert' },
|
||||
'[V' : { name : 'page up' },
|
||||
'[U' : { name : 'page down' },
|
||||
|
||||
// other
|
||||
'[Z' : { name : 'tab', shift : true },
|
||||
}[code];
|
||||
};
|
||||
// other
|
||||
'[Z' : { name : 'tab', shift : true },
|
||||
}[code];
|
||||
};
|
||||
|
||||
this.on('data', function clientData(data) {
|
||||
// create a uniform format that can be parsed below
|
||||
if(data[0] > 127 && undefined === data[1]) {
|
||||
data[0] -= 128;
|
||||
data = '\u001b' + data.toString('utf-8');
|
||||
} else {
|
||||
data = data.toString('utf-8');
|
||||
}
|
||||
this.on('data', function clientData(data) {
|
||||
// create a uniform format that can be parsed below
|
||||
if(data[0] > 127 && undefined === data[1]) {
|
||||
data[0] -= 128;
|
||||
data = '\u001b' + data.toString('utf-8');
|
||||
} else {
|
||||
data = data.toString('utf-8');
|
||||
}
|
||||
|
||||
if(self.isMouseInput(data)) {
|
||||
return;
|
||||
}
|
||||
if(self.isMouseInput(data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var buf = [];
|
||||
var m;
|
||||
while((m = RE_ESC_CODE_ANYWHERE.exec(data))) {
|
||||
buf = buf.concat(data.slice(0, m.index).split(''));
|
||||
buf.push(m[0]);
|
||||
data = data.slice(m.index + m[0].length);
|
||||
}
|
||||
var buf = [];
|
||||
var m;
|
||||
while((m = RE_ESC_CODE_ANYWHERE.exec(data))) {
|
||||
buf = buf.concat(data.slice(0, m.index).split(''));
|
||||
buf.push(m[0]);
|
||||
data = data.slice(m.index + m[0].length);
|
||||
}
|
||||
|
||||
buf = buf.concat(data.split('')); // remainder
|
||||
buf = buf.concat(data.split('')); // remainder
|
||||
|
||||
buf.forEach(function bufPart(s) {
|
||||
var key = {
|
||||
seq : s,
|
||||
name : undefined,
|
||||
ctrl : false,
|
||||
meta : false,
|
||||
shift : false,
|
||||
};
|
||||
buf.forEach(function bufPart(s) {
|
||||
var key = {
|
||||
seq : s,
|
||||
name : undefined,
|
||||
ctrl : false,
|
||||
meta : false,
|
||||
shift : false,
|
||||
};
|
||||
|
||||
var parts;
|
||||
var parts;
|
||||
|
||||
if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
|
||||
if('R' === parts[2]) {
|
||||
const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) );
|
||||
if(2 === cprArgs.length) {
|
||||
if(self.cprOffset) {
|
||||
cprArgs[0] = cprArgs[0] + self.cprOffset;
|
||||
cprArgs[1] = cprArgs[1] + self.cprOffset;
|
||||
}
|
||||
self.emit('cursor position report', cprArgs);
|
||||
}
|
||||
}
|
||||
} else if((parts = RE_DEV_ATTR_RESPONSE_ANYWHERE.exec(s))) {
|
||||
assert('c' === parts[2]);
|
||||
var termClient = self.getTermClient(parts[1]);
|
||||
if(termClient) {
|
||||
self.term.termClient = termClient;
|
||||
}
|
||||
} else if('\r' === s) {
|
||||
key.name = 'return';
|
||||
} else if('\n' === s) {
|
||||
key.name = 'line feed';
|
||||
} else if('\t' === s) {
|
||||
key.name = 'tab';
|
||||
} else if('\x7f' === s) {
|
||||
//
|
||||
// Backspace vs delete is a crazy thing, especially in *nix.
|
||||
// - ANSI-BBS uses 0x7f for DEL
|
||||
// - xterm et. al clients send 0x7f for backspace... ugg.
|
||||
//
|
||||
// See http://www.hypexr.org/linux_ruboff.php
|
||||
// And a great discussion @ https://lists.debian.org/debian-i18n/1998/04/msg00015.html
|
||||
//
|
||||
if(self.term.isNixTerm()) {
|
||||
key.name = 'backspace';
|
||||
} else {
|
||||
key.name = 'delete';
|
||||
}
|
||||
} else if ('\b' === s || '\x1b\x7f' === s || '\x1b\b' === s) {
|
||||
// backspace, CTRL-H
|
||||
key.name = 'backspace';
|
||||
key.meta = ('\x1b' === s.charAt(0));
|
||||
} else if('\x1b' === s || '\x1b\x1b' === s) {
|
||||
key.name = 'escape';
|
||||
key.meta = (2 === s.length);
|
||||
} else if (' ' === s || '\x1b ' === s) {
|
||||
// rather annoying that space can come in other than just " "
|
||||
key.name = 'space';
|
||||
key.meta = (2 === s.length);
|
||||
} else if(1 === s.length && s <= '\x1a') {
|
||||
// CTRL-<letter>
|
||||
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
||||
key.ctrl = true;
|
||||
} else if(1 === s.length && s >= 'a' && s <= 'z') {
|
||||
// normal, lowercased letter
|
||||
key.name = s;
|
||||
} else if(1 === s.length && s >= 'A' && s <= 'Z') {
|
||||
key.name = s.toLowerCase();
|
||||
key.shift = true;
|
||||
} else if ((parts = RE_META_KEYCODE.exec(s))) {
|
||||
// meta with character key
|
||||
key.name = parts[1].toLowerCase();
|
||||
key.meta = true;
|
||||
key.shift = /^[A-Z]$/.test(parts[1]);
|
||||
} else if((parts = RE_FUNCTION_KEYCODE.exec(s))) {
|
||||
var code =
|
||||
if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
|
||||
if('R' === parts[2]) {
|
||||
const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) );
|
||||
if(2 === cprArgs.length) {
|
||||
if(self.cprOffset) {
|
||||
cprArgs[0] = cprArgs[0] + self.cprOffset;
|
||||
cprArgs[1] = cprArgs[1] + self.cprOffset;
|
||||
}
|
||||
self.emit('cursor position report', cprArgs);
|
||||
}
|
||||
}
|
||||
} else if((parts = RE_DEV_ATTR_RESPONSE_ANYWHERE.exec(s))) {
|
||||
assert('c' === parts[2]);
|
||||
var termClient = self.getTermClient(parts[1]);
|
||||
if(termClient) {
|
||||
self.term.termClient = termClient;
|
||||
}
|
||||
} else if('\r' === s) {
|
||||
key.name = 'return';
|
||||
} else if('\n' === s) {
|
||||
key.name = 'line feed';
|
||||
} else if('\t' === s) {
|
||||
key.name = 'tab';
|
||||
} else if('\x7f' === s) {
|
||||
//
|
||||
// Backspace vs delete is a crazy thing, especially in *nix.
|
||||
// - ANSI-BBS uses 0x7f for DEL
|
||||
// - xterm et. al clients send 0x7f for backspace... ugg.
|
||||
//
|
||||
// See http://www.hypexr.org/linux_ruboff.php
|
||||
// And a great discussion @ https://lists.debian.org/debian-i18n/1998/04/msg00015.html
|
||||
//
|
||||
if(self.term.isNixTerm()) {
|
||||
key.name = 'backspace';
|
||||
} else {
|
||||
key.name = 'delete';
|
||||
}
|
||||
} else if ('\b' === s || '\x1b\x7f' === s || '\x1b\b' === s) {
|
||||
// backspace, CTRL-H
|
||||
key.name = 'backspace';
|
||||
key.meta = ('\x1b' === s.charAt(0));
|
||||
} else if('\x1b' === s || '\x1b\x1b' === s) {
|
||||
key.name = 'escape';
|
||||
key.meta = (2 === s.length);
|
||||
} else if (' ' === s || '\x1b ' === s) {
|
||||
// rather annoying that space can come in other than just " "
|
||||
key.name = 'space';
|
||||
key.meta = (2 === s.length);
|
||||
} else if(1 === s.length && s <= '\x1a') {
|
||||
// CTRL-<letter>
|
||||
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
||||
key.ctrl = true;
|
||||
} else if(1 === s.length && s >= 'a' && s <= 'z') {
|
||||
// normal, lowercased letter
|
||||
key.name = s;
|
||||
} else if(1 === s.length && s >= 'A' && s <= 'Z') {
|
||||
key.name = s.toLowerCase();
|
||||
key.shift = true;
|
||||
} else if ((parts = RE_META_KEYCODE.exec(s))) {
|
||||
// meta with character key
|
||||
key.name = parts[1].toLowerCase();
|
||||
key.meta = true;
|
||||
key.shift = /^[A-Z]$/.test(parts[1]);
|
||||
} else if((parts = RE_FUNCTION_KEYCODE.exec(s))) {
|
||||
var code =
|
||||
(parts[1] || '') + (parts[2] || '') +
|
||||
(parts[4] || '') + (parts[9] || '');
|
||||
|
||||
var modifier = (parts[3] || parts[8] || 1) - 1;
|
||||
var modifier = (parts[3] || parts[8] || 1) - 1;
|
||||
|
||||
key.ctrl = !!(modifier & 4);
|
||||
key.meta = !!(modifier & 10);
|
||||
key.shift = !!(modifier & 1);
|
||||
key.code = code;
|
||||
key.ctrl = !!(modifier & 4);
|
||||
key.meta = !!(modifier & 10);
|
||||
key.shift = !!(modifier & 1);
|
||||
key.code = code;
|
||||
|
||||
_.assign(key, self.getKeyComponentsFromCode(code));
|
||||
}
|
||||
_.assign(key, self.getKeyComponentsFromCode(code));
|
||||
}
|
||||
|
||||
var ch;
|
||||
if(1 === s.length) {
|
||||
ch = s;
|
||||
} else if('space' === key.name) {
|
||||
// stupid hack to always get space as a regular char
|
||||
ch = ' ';
|
||||
}
|
||||
var ch;
|
||||
if(1 === s.length) {
|
||||
ch = s;
|
||||
} else if('space' === key.name) {
|
||||
// stupid hack to always get space as a regular char
|
||||
ch = ' ';
|
||||
}
|
||||
|
||||
if(_.isUndefined(key.name)) {
|
||||
key = undefined;
|
||||
} else {
|
||||
//
|
||||
// Adjust name for CTRL/Shift/Meta modifiers
|
||||
//
|
||||
key.name =
|
||||
if(_.isUndefined(key.name)) {
|
||||
key = undefined;
|
||||
} else {
|
||||
//
|
||||
// Adjust name for CTRL/Shift/Meta modifiers
|
||||
//
|
||||
key.name =
|
||||
(key.ctrl ? 'ctrl + ' : '') +
|
||||
(key.meta ? 'meta + ' : '') +
|
||||
(key.shift ? 'shift + ' : '') +
|
||||
key.name;
|
||||
}
|
||||
}
|
||||
|
||||
if(key || ch) {
|
||||
if(Config().logging.traceUserKeyboardInput) {
|
||||
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input'); // jshint ignore:line
|
||||
}
|
||||
if(key || ch) {
|
||||
if(Config().logging.traceUserKeyboardInput) {
|
||||
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input'); // jshint ignore:line
|
||||
}
|
||||
|
||||
self.lastKeyPressMs = Date.now();
|
||||
self.lastKeyPressMs = Date.now();
|
||||
|
||||
if(!self.ignoreInput) {
|
||||
self.emit('key press', ch, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if(!self.ignoreInput) {
|
||||
self.emit('key press', ch, key);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
require('util').inherits(Client, stream);
|
||||
|
||||
Client.prototype.setInputOutput = function(input, output) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
|
||||
this.term = new term.ClientTerminal(this.output);
|
||||
this.term = new term.ClientTerminal(this.output);
|
||||
};
|
||||
|
||||
Client.prototype.setTermType = function(termType) {
|
||||
this.term.env.TERM = termType;
|
||||
this.term.termType = termType;
|
||||
this.term.env.TERM = termType;
|
||||
this.term.termType = termType;
|
||||
|
||||
this.log.debug( { termType : termType }, 'Set terminal type');
|
||||
this.log.debug( { termType : termType }, 'Set terminal type');
|
||||
};
|
||||
|
||||
Client.prototype.startIdleMonitor = function() {
|
||||
this.lastKeyPressMs = Date.now();
|
||||
this.lastKeyPressMs = Date.now();
|
||||
|
||||
//
|
||||
// Every 1m, check for idle.
|
||||
//
|
||||
this.idleCheck = setInterval( () => {
|
||||
const nowMs = Date.now();
|
||||
//
|
||||
// Every 1m, check for idle.
|
||||
//
|
||||
this.idleCheck = setInterval( () => {
|
||||
const nowMs = Date.now();
|
||||
|
||||
const idleLogoutSeconds = this.user.isAuthenticated() ?
|
||||
Config().misc.idleLogoutSeconds :
|
||||
Config().misc.preAuthIdleLogoutSeconds;
|
||||
const idleLogoutSeconds = this.user.isAuthenticated() ?
|
||||
Config().misc.idleLogoutSeconds :
|
||||
Config().misc.preAuthIdleLogoutSeconds;
|
||||
|
||||
if(nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000)) {
|
||||
this.emit('idle timeout');
|
||||
}
|
||||
}, 1000 * 60);
|
||||
if(nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000)) {
|
||||
this.emit('idle timeout');
|
||||
}
|
||||
}, 1000 * 60);
|
||||
};
|
||||
|
||||
Client.prototype.stopIdleMonitor = function() {
|
||||
clearInterval(this.idleCheck);
|
||||
clearInterval(this.idleCheck);
|
||||
};
|
||||
|
||||
Client.prototype.end = function () {
|
||||
if(this.term) {
|
||||
this.term.disconnect();
|
||||
}
|
||||
if(this.term) {
|
||||
this.term.disconnect();
|
||||
}
|
||||
|
||||
var currentModule = this.menuStack.getCurrentModule;
|
||||
var currentModule = this.menuStack.getCurrentModule;
|
||||
|
||||
if(currentModule) {
|
||||
currentModule.leave();
|
||||
}
|
||||
if(currentModule) {
|
||||
currentModule.leave();
|
||||
}
|
||||
|
||||
this.stopIdleMonitor();
|
||||
this.stopIdleMonitor();
|
||||
|
||||
try {
|
||||
//
|
||||
// We can end up calling 'end' before TTY/etc. is established, e.g. with SSH
|
||||
//
|
||||
// :TODO: is this OK?
|
||||
return this.output.end.apply(this.output, arguments);
|
||||
} catch(e) {
|
||||
// TypeError
|
||||
}
|
||||
try {
|
||||
//
|
||||
// We can end up calling 'end' before TTY/etc. is established, e.g. with SSH
|
||||
//
|
||||
// :TODO: is this OK?
|
||||
return this.output.end.apply(this.output, arguments);
|
||||
} catch(e) {
|
||||
// TypeError
|
||||
}
|
||||
};
|
||||
|
||||
Client.prototype.destroy = function () {
|
||||
return this.output.destroy.apply(this.output, arguments);
|
||||
return this.output.destroy.apply(this.output, arguments);
|
||||
};
|
||||
|
||||
Client.prototype.destroySoon = function () {
|
||||
return this.output.destroySoon.apply(this.output, arguments);
|
||||
return this.output.destroySoon.apply(this.output, arguments);
|
||||
};
|
||||
|
||||
Client.prototype.waitForKeyPress = function(cb) {
|
||||
this.once('key press', function kp(ch, key) {
|
||||
cb(ch, key);
|
||||
});
|
||||
this.once('key press', function kp(ch, key) {
|
||||
cb(ch, key);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.isLocal = function() {
|
||||
// :TODO: Handle ipv6 better
|
||||
return [ '127.0.0.1', '::ffff:127.0.0.1' ].includes(this.remoteAddress);
|
||||
// :TODO: Handle ipv6 better
|
||||
return [ '127.0.0.1', '::ffff:127.0.0.1' ].includes(this.remoteAddress);
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -502,41 +502,41 @@ Client.prototype.isLocal = function() {
|
|||
|
||||
// :TODO: getDefaultHandler(name) -- handlers in default_handlers.js or something
|
||||
Client.prototype.defaultHandlerMissingMod = function() {
|
||||
var self = this;
|
||||
var self = this;
|
||||
|
||||
function handler(err) {
|
||||
self.log.error(err);
|
||||
function handler(err) {
|
||||
self.log.error(err);
|
||||
|
||||
self.term.write(ansi.resetScreen());
|
||||
self.term.write('An unrecoverable error has been encountered!\n');
|
||||
self.term.write('This has been logged for your SysOp to review.\n');
|
||||
self.term.write('\nGoodbye!\n');
|
||||
self.term.write(ansi.resetScreen());
|
||||
self.term.write('An unrecoverable error has been encountered!\n');
|
||||
self.term.write('This has been logged for your SysOp to review.\n');
|
||||
self.term.write('\nGoodbye!\n');
|
||||
|
||||
|
||||
//self.term.write(err);
|
||||
//self.term.write(err);
|
||||
|
||||
//if(miscUtil.isDevelopment() && err.stack) {
|
||||
// self.term.write('\n' + err.stack + '\n');
|
||||
//}
|
||||
//if(miscUtil.isDevelopment() && err.stack) {
|
||||
// self.term.write('\n' + err.stack + '\n');
|
||||
//}
|
||||
|
||||
self.end();
|
||||
}
|
||||
self.end();
|
||||
}
|
||||
|
||||
return handler;
|
||||
return handler;
|
||||
};
|
||||
|
||||
Client.prototype.terminalSupports = function(query) {
|
||||
const termClient = this.term.termClient;
|
||||
const termClient = this.term.termClient;
|
||||
|
||||
switch(query) {
|
||||
case 'vtx_audio' :
|
||||
// https://github.com/codewar65/VTX_ClientServer/blob/master/vtx.txt
|
||||
return 'vtx' === termClient;
|
||||
switch(query) {
|
||||
case 'vtx_audio' :
|
||||
// https://github.com/codewar65/VTX_ClientServer/blob/master/vtx.txt
|
||||
return 'vtx' === termClient;
|
||||
|
||||
case 'vtx_hyperlink' :
|
||||
return 'vtx' === termClient;
|
||||
case 'vtx_hyperlink' :
|
||||
return 'vtx' === termClient;
|
||||
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
default :
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -23,98 +23,98 @@ function getActiveConnections() { return clientConnections; }
|
|||
|
||||
function getActiveNodeList(authUsersOnly) {
|
||||
|
||||
if(!_.isBoolean(authUsersOnly)) {
|
||||
authUsersOnly = true;
|
||||
}
|
||||
if(!_.isBoolean(authUsersOnly)) {
|
||||
authUsersOnly = true;
|
||||
}
|
||||
|
||||
const now = moment();
|
||||
const now = moment();
|
||||
|
||||
const activeConnections = getActiveConnections().filter(ac => {
|
||||
return ((authUsersOnly && ac.user.isAuthenticated()) || !authUsersOnly);
|
||||
});
|
||||
const activeConnections = getActiveConnections().filter(ac => {
|
||||
return ((authUsersOnly && ac.user.isAuthenticated()) || !authUsersOnly);
|
||||
});
|
||||
|
||||
return _.map(activeConnections, ac => {
|
||||
const entry = {
|
||||
node : ac.node,
|
||||
authenticated : ac.user.isAuthenticated(),
|
||||
userId : ac.user.userId,
|
||||
action : _.has(ac, 'currentMenuModule.menuConfig.desc') ? ac.currentMenuModule.menuConfig.desc : 'Unknown',
|
||||
};
|
||||
return _.map(activeConnections, ac => {
|
||||
const entry = {
|
||||
node : ac.node,
|
||||
authenticated : ac.user.isAuthenticated(),
|
||||
userId : ac.user.userId,
|
||||
action : _.has(ac, 'currentMenuModule.menuConfig.desc') ? ac.currentMenuModule.menuConfig.desc : 'Unknown',
|
||||
};
|
||||
|
||||
//
|
||||
// There may be a connection, but not a logged in user as of yet
|
||||
//
|
||||
if(ac.user.isAuthenticated()) {
|
||||
entry.userName = ac.user.username;
|
||||
entry.realName = ac.user.properties.real_name;
|
||||
entry.location = ac.user.properties.location;
|
||||
entry.affils = ac.user.properties.affiliation;
|
||||
//
|
||||
// There may be a connection, but not a logged in user as of yet
|
||||
//
|
||||
if(ac.user.isAuthenticated()) {
|
||||
entry.userName = ac.user.username;
|
||||
entry.realName = ac.user.properties.real_name;
|
||||
entry.location = ac.user.properties.location;
|
||||
entry.affils = ac.user.properties.affiliation;
|
||||
|
||||
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
||||
entry.timeOn = moment.duration(diff, 'minutes');
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
||||
entry.timeOn = moment.duration(diff, 'minutes');
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
|
||||
function addNewClient(client, clientSock) {
|
||||
const id = client.session.id = clientConnections.push(client) - 1;
|
||||
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
||||
const id = client.session.id = clientConnections.push(client) - 1;
|
||||
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
||||
|
||||
// create a uniqe identifier one-time ID for this session
|
||||
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
|
||||
// create a uniqe identifier one-time ID for this session
|
||||
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
|
||||
|
||||
// Create a client specific logger
|
||||
// Note that this will be updated @ login with additional information
|
||||
client.log = logger.log.child( { clientId : id, sessionId : client.session.uniqueId } );
|
||||
// Create a client specific logger
|
||||
// Note that this will be updated @ login with additional information
|
||||
client.log = logger.log.child( { clientId : id, sessionId : client.session.uniqueId } );
|
||||
|
||||
const connInfo = {
|
||||
remoteAddress : remoteAddress,
|
||||
serverName : client.session.serverName,
|
||||
isSecure : client.session.isSecure,
|
||||
};
|
||||
const connInfo = {
|
||||
remoteAddress : remoteAddress,
|
||||
serverName : client.session.serverName,
|
||||
isSecure : client.session.isSecure,
|
||||
};
|
||||
|
||||
if(client.log.debug()) {
|
||||
connInfo.port = clientSock.localPort;
|
||||
connInfo.family = clientSock.localFamily;
|
||||
}
|
||||
if(client.log.debug()) {
|
||||
connInfo.port = clientSock.localPort;
|
||||
connInfo.family = clientSock.localFamily;
|
||||
}
|
||||
|
||||
client.log.info(connInfo, 'Client connected');
|
||||
client.log.info(connInfo, 'Client connected');
|
||||
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ClientConnected,
|
||||
{ client : client, connectionCount : clientConnections.length }
|
||||
);
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ClientConnected,
|
||||
{ client : client, connectionCount : clientConnections.length }
|
||||
);
|
||||
|
||||
return id;
|
||||
return id;
|
||||
}
|
||||
|
||||
function removeClient(client) {
|
||||
client.end();
|
||||
client.end();
|
||||
|
||||
const i = clientConnections.indexOf(client);
|
||||
if(i > -1) {
|
||||
clientConnections.splice(i, 1);
|
||||
const i = clientConnections.indexOf(client);
|
||||
if(i > -1) {
|
||||
clientConnections.splice(i, 1);
|
||||
|
||||
logger.log.info(
|
||||
{
|
||||
connectionCount : clientConnections.length,
|
||||
clientId : client.session.id
|
||||
},
|
||||
'Client disconnected'
|
||||
);
|
||||
logger.log.info(
|
||||
{
|
||||
connectionCount : clientConnections.length,
|
||||
clientId : client.session.id
|
||||
},
|
||||
'Client disconnected'
|
||||
);
|
||||
|
||||
if(client.user && client.user.isValid()) {
|
||||
Events.emit(Events.getSystemEvents().UserLogoff, { user : client.user } );
|
||||
}
|
||||
if(client.user && client.user.isValid()) {
|
||||
Events.emit(Events.getSystemEvents().UserLogoff, { user : client.user } );
|
||||
}
|
||||
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ClientDisconnected,
|
||||
{ client : client, connectionCount : clientConnections.length }
|
||||
);
|
||||
}
|
||||
Events.emit(
|
||||
Events.getSystemEvents().ClientDisconnected,
|
||||
{ client : client, connectionCount : clientConnections.length }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getConnectionByUserId(userId) {
|
||||
return getActiveConnections().find( ac => userId === ac.user.userId );
|
||||
return getActiveConnections().find( ac => userId === ac.user.userId );
|
||||
}
|
||||
|
|
|
@ -13,185 +13,185 @@ var _ = require('lodash');
|
|||
exports.ClientTerminal = ClientTerminal;
|
||||
|
||||
function ClientTerminal(output) {
|
||||
this.output = output;
|
||||
this.output = output;
|
||||
|
||||
var outputEncoding = 'cp437';
|
||||
assert(iconv.encodingExists(outputEncoding));
|
||||
var outputEncoding = 'cp437';
|
||||
assert(iconv.encodingExists(outputEncoding));
|
||||
|
||||
// convert line feeds such as \n -> \r\n
|
||||
this.convertLF = true;
|
||||
// convert line feeds such as \n -> \r\n
|
||||
this.convertLF = true;
|
||||
|
||||
//
|
||||
// Some terminal we handle specially
|
||||
// They can also be found in this.env{}
|
||||
//
|
||||
var termType = 'unknown';
|
||||
var termHeight = 0;
|
||||
var termWidth = 0;
|
||||
var termClient = 'unknown';
|
||||
//
|
||||
// Some terminal we handle specially
|
||||
// They can also be found in this.env{}
|
||||
//
|
||||
var termType = 'unknown';
|
||||
var termHeight = 0;
|
||||
var termWidth = 0;
|
||||
var termClient = 'unknown';
|
||||
|
||||
this.currentSyncFont = 'not_set';
|
||||
this.currentSyncFont = 'not_set';
|
||||
|
||||
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
||||
this.env = {};
|
||||
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
||||
this.env = {};
|
||||
|
||||
Object.defineProperty(this, 'outputEncoding', {
|
||||
get : function() {
|
||||
return outputEncoding;
|
||||
},
|
||||
set : function(enc) {
|
||||
if(iconv.encodingExists(enc)) {
|
||||
outputEncoding = enc;
|
||||
} else {
|
||||
Log.warn({ encoding : enc }, 'Unknown encoding');
|
||||
}
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'outputEncoding', {
|
||||
get : function() {
|
||||
return outputEncoding;
|
||||
},
|
||||
set : function(enc) {
|
||||
if(iconv.encodingExists(enc)) {
|
||||
outputEncoding = enc;
|
||||
} else {
|
||||
Log.warn({ encoding : enc }, 'Unknown encoding');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'termType', {
|
||||
get : function() {
|
||||
return termType;
|
||||
},
|
||||
set : function(ttype) {
|
||||
termType = ttype.toLowerCase();
|
||||
Object.defineProperty(this, 'termType', {
|
||||
get : function() {
|
||||
return termType;
|
||||
},
|
||||
set : function(ttype) {
|
||||
termType = ttype.toLowerCase();
|
||||
|
||||
if(this.isANSI()) {
|
||||
this.outputEncoding = 'cp437';
|
||||
} else {
|
||||
// :TODO: See how x84 does this -- only set if local/remote are binary
|
||||
this.outputEncoding = 'utf8';
|
||||
}
|
||||
if(this.isANSI()) {
|
||||
this.outputEncoding = 'cp437';
|
||||
} else {
|
||||
// :TODO: See how x84 does this -- only set if local/remote are binary
|
||||
this.outputEncoding = 'utf8';
|
||||
}
|
||||
|
||||
// :TODO: according to this: http://mud-dev.wikidot.com/article:telnet-client-identification
|
||||
// Windows telnet will send "VTNT". If so, set termClient='windows'
|
||||
// there are some others on the page as well
|
||||
// :TODO: according to this: http://mud-dev.wikidot.com/article:telnet-client-identification
|
||||
// Windows telnet will send "VTNT". If so, set termClient='windows'
|
||||
// there are some others on the page as well
|
||||
|
||||
Log.debug( { encoding : this.outputEncoding }, 'Set output encoding due to terminal type change');
|
||||
}
|
||||
});
|
||||
Log.debug( { encoding : this.outputEncoding }, 'Set output encoding due to terminal type change');
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'termWidth', {
|
||||
get : function() {
|
||||
return termWidth;
|
||||
},
|
||||
set : function(width) {
|
||||
if(width > 0) {
|
||||
termWidth = width;
|
||||
}
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'termWidth', {
|
||||
get : function() {
|
||||
return termWidth;
|
||||
},
|
||||
set : function(width) {
|
||||
if(width > 0) {
|
||||
termWidth = width;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'termHeight', {
|
||||
get : function() {
|
||||
return termHeight;
|
||||
},
|
||||
set : function(height) {
|
||||
if(height > 0) {
|
||||
termHeight = height;
|
||||
}
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'termHeight', {
|
||||
get : function() {
|
||||
return termHeight;
|
||||
},
|
||||
set : function(height) {
|
||||
if(height > 0) {
|
||||
termHeight = height;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'termClient', {
|
||||
get : function() {
|
||||
return termClient;
|
||||
},
|
||||
set : function(tc) {
|
||||
termClient = tc;
|
||||
Object.defineProperty(this, 'termClient', {
|
||||
get : function() {
|
||||
return termClient;
|
||||
},
|
||||
set : function(tc) {
|
||||
termClient = tc;
|
||||
|
||||
Log.debug( { termClient : this.termClient }, 'Set known terminal client');
|
||||
}
|
||||
});
|
||||
Log.debug( { termClient : this.termClient }, 'Set known terminal client');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ClientTerminal.prototype.disconnect = function() {
|
||||
this.output = null;
|
||||
this.output = null;
|
||||
};
|
||||
|
||||
ClientTerminal.prototype.isNixTerm = function() {
|
||||
//
|
||||
// Standard *nix type terminals
|
||||
//
|
||||
if(this.termType.startsWith('xterm')) {
|
||||
return true;
|
||||
}
|
||||
//
|
||||
// Standard *nix type terminals
|
||||
//
|
||||
if(this.termType.startsWith('xterm')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return [ 'xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator' ].includes(this.termType);
|
||||
return [ 'xterm', 'linux', 'screen', 'dumb', 'rxvt', 'konsole', 'gnome', 'x11 terminal emulator' ].includes(this.termType);
|
||||
};
|
||||
|
||||
ClientTerminal.prototype.isANSI = function() {
|
||||
//
|
||||
// ANSI terminals should be encoded to CP437
|
||||
//
|
||||
// Some terminal types provided by Mercyful Fate / Enthral:
|
||||
// ANSI-BBS
|
||||
// PC-ANSI
|
||||
// QANSI
|
||||
// SCOANSI
|
||||
// VT100
|
||||
// QNX
|
||||
//
|
||||
// Reports from various terminals
|
||||
//
|
||||
// syncterm:
|
||||
// * SyncTERM
|
||||
//
|
||||
// xterm:
|
||||
// * PuTTY
|
||||
//
|
||||
// ansi-bbs:
|
||||
// * fTelnet
|
||||
//
|
||||
// pcansi:
|
||||
// * ZOC
|
||||
//
|
||||
// screen:
|
||||
// * ConnectBot (Android)
|
||||
//
|
||||
// linux:
|
||||
// * JuiceSSH (note: TERM=linux also)
|
||||
//
|
||||
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].includes(this.termType);
|
||||
//
|
||||
// ANSI terminals should be encoded to CP437
|
||||
//
|
||||
// Some terminal types provided by Mercyful Fate / Enthral:
|
||||
// ANSI-BBS
|
||||
// PC-ANSI
|
||||
// QANSI
|
||||
// SCOANSI
|
||||
// VT100
|
||||
// QNX
|
||||
//
|
||||
// Reports from various terminals
|
||||
//
|
||||
// syncterm:
|
||||
// * SyncTERM
|
||||
//
|
||||
// xterm:
|
||||
// * PuTTY
|
||||
//
|
||||
// ansi-bbs:
|
||||
// * fTelnet
|
||||
//
|
||||
// pcansi:
|
||||
// * ZOC
|
||||
//
|
||||
// screen:
|
||||
// * ConnectBot (Android)
|
||||
//
|
||||
// linux:
|
||||
// * JuiceSSH (note: TERM=linux also)
|
||||
//
|
||||
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].includes(this.termType);
|
||||
};
|
||||
|
||||
// :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it)
|
||||
|
||||
ClientTerminal.prototype.write = function(s, convertLineFeeds, cb) {
|
||||
this.rawWrite(this.encode(s, convertLineFeeds), cb);
|
||||
this.rawWrite(this.encode(s, convertLineFeeds), cb);
|
||||
};
|
||||
|
||||
ClientTerminal.prototype.rawWrite = function(s, cb) {
|
||||
if(this.output) {
|
||||
this.output.write(s, err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
if(this.output) {
|
||||
this.output.write(s, err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(err) {
|
||||
Log.warn( { error : err.message }, 'Failed writing to socket');
|
||||
}
|
||||
});
|
||||
}
|
||||
if(err) {
|
||||
Log.warn( { error : err.message }, 'Failed writing to socket');
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
ClientTerminal.prototype.pipeWrite = function(s, spec, cb) {
|
||||
spec = spec || 'renegade';
|
||||
spec = spec || 'renegade';
|
||||
|
||||
var conv = {
|
||||
enigma : enigmaToAnsi,
|
||||
renegade : renegadeToAnsi,
|
||||
}[spec] || renegadeToAnsi;
|
||||
var conv = {
|
||||
enigma : enigmaToAnsi,
|
||||
renegade : renegadeToAnsi,
|
||||
}[spec] || renegadeToAnsi;
|
||||
|
||||
this.write(conv(s, this), null, cb); // null = use default for |convertLineFeeds|
|
||||
this.write(conv(s, this), null, cb); // null = use default for |convertLineFeeds|
|
||||
};
|
||||
|
||||
ClientTerminal.prototype.encode = function(s, convertLineFeeds) {
|
||||
convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF;
|
||||
convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF;
|
||||
|
||||
if(convertLineFeeds && _.isString(s)) {
|
||||
s = s.replace(/\n/g, '\r\n');
|
||||
}
|
||||
return iconv.encode(s, this.outputEncoding);
|
||||
if(convertLineFeeds && _.isString(s)) {
|
||||
s = s.replace(/\n/g, '\r\n');
|
||||
}
|
||||
return iconv.encode(s, this.outputEncoding);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -28,139 +28,139 @@ exports.controlCodesToAnsi = controlCodesToAnsi;
|
|||
|
||||
// :TODO: rid of enigmaToAnsi() -- never really use. Instead, create bbsToAnsi() that supports renegade, PCB, WWIV, etc...
|
||||
function enigmaToAnsi(s, client) {
|
||||
if(-1 == s.indexOf('|')) {
|
||||
return s; // no pipe codes present
|
||||
}
|
||||
if(-1 == s.indexOf('|')) {
|
||||
return s; // no pipe codes present
|
||||
}
|
||||
|
||||
var result = '';
|
||||
var re = /\|([A-Z\d]{2}|\|)/g;
|
||||
var m;
|
||||
var lastIndex = 0;
|
||||
while((m = re.exec(s))) {
|
||||
var val = m[1];
|
||||
var result = '';
|
||||
var re = /\|([A-Z\d]{2}|\|)/g;
|
||||
var m;
|
||||
var lastIndex = 0;
|
||||
while((m = re.exec(s))) {
|
||||
var val = m[1];
|
||||
|
||||
if('|' == val) {
|
||||
result += '|';
|
||||
continue;
|
||||
}
|
||||
if('|' == val) {
|
||||
result += '|';
|
||||
continue;
|
||||
}
|
||||
|
||||
// convert to number
|
||||
val = parseInt(val, 10);
|
||||
if(isNaN(val)) {
|
||||
//
|
||||
// ENiGMA MCI code? Only available if |client|
|
||||
// is supplied.
|
||||
//
|
||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||
}
|
||||
// convert to number
|
||||
val = parseInt(val, 10);
|
||||
if(isNaN(val)) {
|
||||
//
|
||||
// ENiGMA MCI code? Only available if |client|
|
||||
// is supplied.
|
||||
//
|
||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||
}
|
||||
|
||||
if(_.isString(val)) {
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||
} else {
|
||||
assert(val >= 0 && val <= 47);
|
||||
if(_.isString(val)) {
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||
} else {
|
||||
assert(val >= 0 && val <= 47);
|
||||
|
||||
var attr = '';
|
||||
if(7 == val) {
|
||||
attr = ansi.sgr('normal');
|
||||
} else if (val < 7 || val >= 16) {
|
||||
attr = ansi.sgr(['normal', val]);
|
||||
} else if (val <= 15) {
|
||||
attr = ansi.sgr(['normal', val - 8, 'bold']);
|
||||
}
|
||||
var attr = '';
|
||||
if(7 == val) {
|
||||
attr = ansi.sgr('normal');
|
||||
} else if (val < 7 || val >= 16) {
|
||||
attr = ansi.sgr(['normal', val]);
|
||||
} else if (val <= 15) {
|
||||
attr = ansi.sgr(['normal', val - 8, 'bold']);
|
||||
}
|
||||
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
}
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
}
|
||||
|
||||
lastIndex = re.lastIndex;
|
||||
}
|
||||
lastIndex = re.lastIndex;
|
||||
}
|
||||
|
||||
result = (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
result = (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
function stripEnigmaCodes(s) {
|
||||
return s.replace(/\|[A-Z\d]{2}/g, '');
|
||||
return s.replace(/\|[A-Z\d]{2}/g, '');
|
||||
}
|
||||
|
||||
function enigmaStrLen(s) {
|
||||
return stripEnigmaCodes(s).length;
|
||||
return stripEnigmaCodes(s).length;
|
||||
}
|
||||
|
||||
function ansiSgrFromRenegadeColorCode(cc) {
|
||||
return ansi.sgr({
|
||||
0 : [ 'reset', 'black' ],
|
||||
1 : [ 'reset', 'blue' ],
|
||||
2 : [ 'reset', 'green' ],
|
||||
3 : [ 'reset', 'cyan' ],
|
||||
4 : [ 'reset', 'red' ],
|
||||
5 : [ 'reset', 'magenta' ],
|
||||
6 : [ 'reset', 'yellow' ],
|
||||
7 : [ 'reset', 'white' ],
|
||||
return ansi.sgr({
|
||||
0 : [ 'reset', 'black' ],
|
||||
1 : [ 'reset', 'blue' ],
|
||||
2 : [ 'reset', 'green' ],
|
||||
3 : [ 'reset', 'cyan' ],
|
||||
4 : [ 'reset', 'red' ],
|
||||
5 : [ 'reset', 'magenta' ],
|
||||
6 : [ 'reset', 'yellow' ],
|
||||
7 : [ 'reset', 'white' ],
|
||||
|
||||
8 : [ 'bold', 'black' ],
|
||||
9 : [ 'bold', 'blue' ],
|
||||
10 : [ 'bold', 'green' ],
|
||||
11 : [ 'bold', 'cyan' ],
|
||||
12 : [ 'bold', 'red' ],
|
||||
13 : [ 'bold', 'magenta' ],
|
||||
14 : [ 'bold', 'yellow' ],
|
||||
15 : [ 'bold', 'white' ],
|
||||
8 : [ 'bold', 'black' ],
|
||||
9 : [ 'bold', 'blue' ],
|
||||
10 : [ 'bold', 'green' ],
|
||||
11 : [ 'bold', 'cyan' ],
|
||||
12 : [ 'bold', 'red' ],
|
||||
13 : [ 'bold', 'magenta' ],
|
||||
14 : [ 'bold', 'yellow' ],
|
||||
15 : [ 'bold', 'white' ],
|
||||
|
||||
16 : [ 'blackBG' ],
|
||||
17 : [ 'blueBG' ],
|
||||
18 : [ 'greenBG' ],
|
||||
19 : [ 'cyanBG' ],
|
||||
20 : [ 'redBG' ],
|
||||
21 : [ 'magentaBG' ],
|
||||
22 : [ 'yellowBG' ],
|
||||
23 : [ 'whiteBG' ],
|
||||
16 : [ 'blackBG' ],
|
||||
17 : [ 'blueBG' ],
|
||||
18 : [ 'greenBG' ],
|
||||
19 : [ 'cyanBG' ],
|
||||
20 : [ 'redBG' ],
|
||||
21 : [ 'magentaBG' ],
|
||||
22 : [ 'yellowBG' ],
|
||||
23 : [ 'whiteBG' ],
|
||||
|
||||
24 : [ 'blink', 'blackBG' ],
|
||||
25 : [ 'blink', 'blueBG' ],
|
||||
26 : [ 'blink', 'greenBG' ],
|
||||
27 : [ 'blink', 'cyanBG' ],
|
||||
28 : [ 'blink', 'redBG' ],
|
||||
29 : [ 'blink', 'magentaBG' ],
|
||||
30 : [ 'blink', 'yellowBG' ],
|
||||
31 : [ 'blink', 'whiteBG' ],
|
||||
}[cc] || 'normal');
|
||||
24 : [ 'blink', 'blackBG' ],
|
||||
25 : [ 'blink', 'blueBG' ],
|
||||
26 : [ 'blink', 'greenBG' ],
|
||||
27 : [ 'blink', 'cyanBG' ],
|
||||
28 : [ 'blink', 'redBG' ],
|
||||
29 : [ 'blink', 'magentaBG' ],
|
||||
30 : [ 'blink', 'yellowBG' ],
|
||||
31 : [ 'blink', 'whiteBG' ],
|
||||
}[cc] || 'normal');
|
||||
}
|
||||
|
||||
function renegadeToAnsi(s, client) {
|
||||
if(-1 == s.indexOf('|')) {
|
||||
return s; // no pipe codes present
|
||||
}
|
||||
if(-1 == s.indexOf('|')) {
|
||||
return s; // no pipe codes present
|
||||
}
|
||||
|
||||
var result = '';
|
||||
var re = /\|([A-Z\d]{2}|\|)/g;
|
||||
var m;
|
||||
var lastIndex = 0;
|
||||
while((m = re.exec(s))) {
|
||||
var val = m[1];
|
||||
var result = '';
|
||||
var re = /\|([A-Z\d]{2}|\|)/g;
|
||||
var m;
|
||||
var lastIndex = 0;
|
||||
while((m = re.exec(s))) {
|
||||
var val = m[1];
|
||||
|
||||
if('|' == val) {
|
||||
result += '|';
|
||||
continue;
|
||||
}
|
||||
if('|' == val) {
|
||||
result += '|';
|
||||
continue;
|
||||
}
|
||||
|
||||
// convert to number
|
||||
val = parseInt(val, 10);
|
||||
if(isNaN(val)) {
|
||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||
}
|
||||
// convert to number
|
||||
val = parseInt(val, 10);
|
||||
if(isNaN(val)) {
|
||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||
}
|
||||
|
||||
if(_.isString(val)) {
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||
} else {
|
||||
const attr = ansiSgrFromRenegadeColorCode(val);
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
}
|
||||
if(_.isString(val)) {
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||
} else {
|
||||
const attr = ansiSgrFromRenegadeColorCode(val);
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
||||
}
|
||||
|
||||
lastIndex = re.lastIndex;
|
||||
}
|
||||
lastIndex = re.lastIndex;
|
||||
}
|
||||
|
||||
return (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
return (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -180,113 +180,113 @@ function renegadeToAnsi(s, client) {
|
|||
// * http://wiki.synchro.net/custom:colors
|
||||
//
|
||||
function controlCodesToAnsi(s, client) {
|
||||
const RE = /(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)/g; // eslint-disable-line no-control-regex
|
||||
const RE = /(\|([A-Z0-9]{2})|\|)|(@X([0-9A-F]{2}))|(@([0-9A-F]{2})@)|(\x03[0-9]|\x03)/g; // eslint-disable-line no-control-regex
|
||||
|
||||
let m;
|
||||
let result = '';
|
||||
let lastIndex = 0;
|
||||
let v;
|
||||
let fg;
|
||||
let bg;
|
||||
let m;
|
||||
let result = '';
|
||||
let lastIndex = 0;
|
||||
let v;
|
||||
let fg;
|
||||
let bg;
|
||||
|
||||
while((m = RE.exec(s))) {
|
||||
switch(m[0].charAt(0)) {
|
||||
case '|' :
|
||||
// Renegade or ENiGMA MCI
|
||||
v = parseInt(m[2], 10);
|
||||
while((m = RE.exec(s))) {
|
||||
switch(m[0].charAt(0)) {
|
||||
case '|' :
|
||||
// Renegade or ENiGMA MCI
|
||||
v = parseInt(m[2], 10);
|
||||
|
||||
if(isNaN(v)) {
|
||||
v = getPredefinedMCIValue(client, m[2]) || m[0]; // value itself or literal
|
||||
}
|
||||
if(isNaN(v)) {
|
||||
v = getPredefinedMCIValue(client, m[2]) || m[0]; // value itself or literal
|
||||
}
|
||||
|
||||
if(_.isString(v)) {
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
} else {
|
||||
v = ansiSgrFromRenegadeColorCode(v);
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
}
|
||||
break;
|
||||
if(_.isString(v)) {
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
} else {
|
||||
v = ansiSgrFromRenegadeColorCode(v);
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
}
|
||||
break;
|
||||
|
||||
case '@' :
|
||||
// PCBoard @X## or Wildcat! @##@
|
||||
if('@' === m[0].substr(-1)) {
|
||||
// Wildcat!
|
||||
v = m[6];
|
||||
} else {
|
||||
v = m[4];
|
||||
}
|
||||
case '@' :
|
||||
// PCBoard @X## or Wildcat! @##@
|
||||
if('@' === m[0].substr(-1)) {
|
||||
// Wildcat!
|
||||
v = m[6];
|
||||
} else {
|
||||
v = m[4];
|
||||
}
|
||||
|
||||
fg = {
|
||||
0 : [ 'reset', 'black' ],
|
||||
1 : [ 'reset', 'blue' ],
|
||||
2 : [ 'reset', 'green' ],
|
||||
3 : [ 'reset', 'cyan' ],
|
||||
4 : [ 'reset', 'red' ],
|
||||
5 : [ 'reset', 'magenta' ],
|
||||
6 : [ 'reset', 'yellow' ],
|
||||
7 : [ 'reset', 'white' ],
|
||||
fg = {
|
||||
0 : [ 'reset', 'black' ],
|
||||
1 : [ 'reset', 'blue' ],
|
||||
2 : [ 'reset', 'green' ],
|
||||
3 : [ 'reset', 'cyan' ],
|
||||
4 : [ 'reset', 'red' ],
|
||||
5 : [ 'reset', 'magenta' ],
|
||||
6 : [ 'reset', 'yellow' ],
|
||||
7 : [ 'reset', 'white' ],
|
||||
|
||||
8 : [ 'blink', 'black' ],
|
||||
9 : [ 'blink', 'blue' ],
|
||||
A : [ 'blink', 'green' ],
|
||||
B : [ 'blink', 'cyan' ],
|
||||
C : [ 'blink', 'red' ],
|
||||
D : [ 'blink', 'magenta' ],
|
||||
E : [ 'blink', 'yellow' ],
|
||||
F : [ 'blink', 'white' ],
|
||||
}[v.charAt(0)] || ['normal'];
|
||||
8 : [ 'blink', 'black' ],
|
||||
9 : [ 'blink', 'blue' ],
|
||||
A : [ 'blink', 'green' ],
|
||||
B : [ 'blink', 'cyan' ],
|
||||
C : [ 'blink', 'red' ],
|
||||
D : [ 'blink', 'magenta' ],
|
||||
E : [ 'blink', 'yellow' ],
|
||||
F : [ 'blink', 'white' ],
|
||||
}[v.charAt(0)] || ['normal'];
|
||||
|
||||
bg = {
|
||||
0 : [ 'blackBG' ],
|
||||
1 : [ 'blueBG' ],
|
||||
2 : [ 'greenBG' ],
|
||||
3 : [ 'cyanBG' ],
|
||||
4 : [ 'redBG' ],
|
||||
5 : [ 'magentaBG' ],
|
||||
6 : [ 'yellowBG' ],
|
||||
7 : [ 'whiteBG' ],
|
||||
bg = {
|
||||
0 : [ 'blackBG' ],
|
||||
1 : [ 'blueBG' ],
|
||||
2 : [ 'greenBG' ],
|
||||
3 : [ 'cyanBG' ],
|
||||
4 : [ 'redBG' ],
|
||||
5 : [ 'magentaBG' ],
|
||||
6 : [ 'yellowBG' ],
|
||||
7 : [ 'whiteBG' ],
|
||||
|
||||
8 : [ 'bold', 'blackBG' ],
|
||||
9 : [ 'bold', 'blueBG' ],
|
||||
A : [ 'bold', 'greenBG' ],
|
||||
B : [ 'bold', 'cyanBG' ],
|
||||
C : [ 'bold', 'redBG' ],
|
||||
D : [ 'bold', 'magentaBG' ],
|
||||
E : [ 'bold', 'yellowBG' ],
|
||||
F : [ 'bold', 'whiteBG' ],
|
||||
}[v.charAt(1)] || [ 'normal' ];
|
||||
8 : [ 'bold', 'blackBG' ],
|
||||
9 : [ 'bold', 'blueBG' ],
|
||||
A : [ 'bold', 'greenBG' ],
|
||||
B : [ 'bold', 'cyanBG' ],
|
||||
C : [ 'bold', 'redBG' ],
|
||||
D : [ 'bold', 'magentaBG' ],
|
||||
E : [ 'bold', 'yellowBG' ],
|
||||
F : [ 'bold', 'whiteBG' ],
|
||||
}[v.charAt(1)] || [ 'normal' ];
|
||||
|
||||
v = ansi.sgr(fg.concat(bg));
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
break;
|
||||
v = ansi.sgr(fg.concat(bg));
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
break;
|
||||
|
||||
case '\x03' :
|
||||
v = parseInt(m[8], 10);
|
||||
case '\x03' :
|
||||
v = parseInt(m[8], 10);
|
||||
|
||||
if(isNaN(v)) {
|
||||
v += m[0];
|
||||
} else {
|
||||
v = ansi.sgr({
|
||||
0 : [ 'reset', 'black' ],
|
||||
1 : [ 'bold', 'cyan' ],
|
||||
2 : [ 'bold', 'yellow' ],
|
||||
3 : [ 'reset', 'magenta' ],
|
||||
4 : [ 'bold', 'white', 'blueBG' ],
|
||||
5 : [ 'reset', 'green' ],
|
||||
6 : [ 'bold', 'blink', 'red' ],
|
||||
7 : [ 'bold', 'blue' ],
|
||||
8 : [ 'reset', 'blue' ],
|
||||
9 : [ 'reset', 'cyan' ],
|
||||
}[v] || 'normal');
|
||||
}
|
||||
if(isNaN(v)) {
|
||||
v += m[0];
|
||||
} else {
|
||||
v = ansi.sgr({
|
||||
0 : [ 'reset', 'black' ],
|
||||
1 : [ 'bold', 'cyan' ],
|
||||
2 : [ 'bold', 'yellow' ],
|
||||
3 : [ 'reset', 'magenta' ],
|
||||
4 : [ 'bold', 'white', 'blueBG' ],
|
||||
5 : [ 'reset', 'green' ],
|
||||
6 : [ 'bold', 'blink', 'red' ],
|
||||
7 : [ 'bold', 'blue' ],
|
||||
8 : [ 'reset', 'blue' ],
|
||||
9 : [ 'reset', 'cyan' ],
|
||||
}[v] || 'normal');
|
||||
}
|
||||
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lastIndex = RE.lastIndex;
|
||||
}
|
||||
lastIndex = RE.lastIndex;
|
||||
}
|
||||
|
||||
return (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
return (0 === result.length ? s : result + s.substr(lastIndex));
|
||||
}
|
|
@ -11,107 +11,107 @@ const _ = require('lodash');
|
|||
const RLogin = require('rlogin');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'CombatNet',
|
||||
desc : 'CombatNet Access Module',
|
||||
author : 'Dave Stephens',
|
||||
name : 'CombatNet',
|
||||
desc : 'CombatNet Access Module',
|
||||
author : 'Dave Stephens',
|
||||
};
|
||||
|
||||
exports.getModule = class CombatNetModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
// establish defaults
|
||||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'bbs.combatnet.us';
|
||||
this.config.rloginPort = this.config.rloginPort || 4513;
|
||||
}
|
||||
// establish defaults
|
||||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'bbs.combatnet.us';
|
||||
this.config.rloginPort = this.config.rloginPort || 4513;
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(!_.isString(self.config.password)) {
|
||||
return callback(new Error('Config requires "password"!'));
|
||||
}
|
||||
if(!_.isString(self.config.bbsTag)) {
|
||||
return callback(new Error('Config requires "bbsTag"!'));
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function establishRloginConnection(callback) {
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to CombatNet, please wait...\n');
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(!_.isString(self.config.password)) {
|
||||
return callback(new Error('Config requires "password"!'));
|
||||
}
|
||||
if(!_.isString(self.config.bbsTag)) {
|
||||
return callback(new Error('Config requires "bbsTag"!'));
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function establishRloginConnection(callback) {
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to CombatNet, please wait...\n');
|
||||
|
||||
const restorePipeToNormal = function() {
|
||||
if(self.client.term.output) {
|
||||
self.client.term.output.removeListener('data', sendToRloginBuffer);
|
||||
}
|
||||
};
|
||||
const restorePipeToNormal = function() {
|
||||
if(self.client.term.output) {
|
||||
self.client.term.output.removeListener('data', sendToRloginBuffer);
|
||||
}
|
||||
};
|
||||
|
||||
const rlogin = new RLogin(
|
||||
{ 'clientUsername' : self.config.password,
|
||||
'serverUsername' : `${self.config.bbsTag}${self.client.user.username}`,
|
||||
'host' : self.config.host,
|
||||
'port' : self.config.rloginPort,
|
||||
'terminalType' : self.client.term.termClient,
|
||||
'terminalSpeed' : 57600
|
||||
}
|
||||
);
|
||||
const rlogin = new RLogin(
|
||||
{ 'clientUsername' : self.config.password,
|
||||
'serverUsername' : `${self.config.bbsTag}${self.client.user.username}`,
|
||||
'host' : self.config.host,
|
||||
'port' : self.config.rloginPort,
|
||||
'terminalType' : self.client.term.termClient,
|
||||
'terminalSpeed' : 57600
|
||||
}
|
||||
);
|
||||
|
||||
// If there was an error ...
|
||||
rlogin.on('error', err => {
|
||||
self.client.log.info(`CombatNet rlogin client error: ${err.message}`);
|
||||
restorePipeToNormal();
|
||||
return callback(err);
|
||||
});
|
||||
// If there was an error ...
|
||||
rlogin.on('error', err => {
|
||||
self.client.log.info(`CombatNet rlogin client error: ${err.message}`);
|
||||
restorePipeToNormal();
|
||||
return callback(err);
|
||||
});
|
||||
|
||||
// If we've been disconnected ...
|
||||
rlogin.on('disconnect', () => {
|
||||
self.client.log.info('Disconnected from CombatNet');
|
||||
restorePipeToNormal();
|
||||
return callback(null);
|
||||
});
|
||||
// If we've been disconnected ...
|
||||
rlogin.on('disconnect', () => {
|
||||
self.client.log.info('Disconnected from CombatNet');
|
||||
restorePipeToNormal();
|
||||
return callback(null);
|
||||
});
|
||||
|
||||
function sendToRloginBuffer(buffer) {
|
||||
rlogin.send(buffer);
|
||||
}
|
||||
function sendToRloginBuffer(buffer) {
|
||||
rlogin.send(buffer);
|
||||
}
|
||||
|
||||
rlogin.on('connect',
|
||||
/* The 'connect' event handler will be supplied with one argument,
|
||||
rlogin.on('connect',
|
||||
/* The 'connect' event handler will be supplied with one argument,
|
||||
a boolean indicating whether or not the connection was established. */
|
||||
|
||||
function(state) {
|
||||
if(state) {
|
||||
self.client.log.info('Connected to CombatNet');
|
||||
self.client.term.output.on('data', sendToRloginBuffer);
|
||||
function(state) {
|
||||
if(state) {
|
||||
self.client.log.info('Connected to CombatNet');
|
||||
self.client.term.output.on('data', sendToRloginBuffer);
|
||||
|
||||
} else {
|
||||
return callback(new Error('Failed to establish establish CombatNet connection'));
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
return callback(new Error('Failed to establish establish CombatNet connection'));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// If data (a Buffer) has been received from the server ...
|
||||
rlogin.on('data', (data) => {
|
||||
self.client.term.rawWrite(data);
|
||||
});
|
||||
// If data (a Buffer) has been received from the server ...
|
||||
rlogin.on('data', (data) => {
|
||||
self.client.term.rawWrite(data);
|
||||
});
|
||||
|
||||
// connect...
|
||||
rlogin.connect();
|
||||
// connect...
|
||||
rlogin.connect();
|
||||
|
||||
// note: no explicit callback() until we're finished!
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'CombatNet error');
|
||||
}
|
||||
// note: no explicit callback() until we're finished!
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'CombatNet error');
|
||||
}
|
||||
|
||||
// if the client is still here, go to previous
|
||||
self.prevMenu();
|
||||
}
|
||||
);
|
||||
}
|
||||
// if the client is still here, go to previous
|
||||
self.prevMenu();
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,19 +12,19 @@ exports.sortAreasOrConfs = sortAreasOrConfs;
|
|||
// Otherwise, use a locale comparison on the sort key or name as a fallback
|
||||
//
|
||||
function sortAreasOrConfs(areasOrConfs, type) {
|
||||
let entryA;
|
||||
let entryB;
|
||||
let entryA;
|
||||
let entryB;
|
||||
|
||||
areasOrConfs.sort((a, b) => {
|
||||
entryA = type ? a[type] : a;
|
||||
entryB = type ? b[type] : b;
|
||||
areasOrConfs.sort((a, b) => {
|
||||
entryA = type ? a[type] : a;
|
||||
entryB = type ? b[type] : b;
|
||||
|
||||
if(_.isNumber(entryA.sort) && _.isNumber(entryB.sort)) {
|
||||
return entryA.sort - entryB.sort;
|
||||
} else {
|
||||
const keyA = entryA.sort ? entryA.sort.toString() : entryA.name;
|
||||
const keyB = entryB.sort ? entryB.sort.toString() : entryB.name;
|
||||
return keyA.localeCompare(keyB, { sensitivity : false, numeric : true } ); // "natural" compare
|
||||
}
|
||||
});
|
||||
if(_.isNumber(entryA.sort) && _.isNumber(entryB.sort)) {
|
||||
return entryA.sort - entryB.sort;
|
||||
} else {
|
||||
const keyA = entryA.sort ? entryA.sort.toString() : entryA.name;
|
||||
const keyB = entryB.sort ? entryB.sort.toString() : entryB.name;
|
||||
return keyA.localeCompare(keyB, { sensitivity : false, numeric : true } ); // "natural" compare
|
||||
}
|
||||
});
|
||||
}
|
1422
core/config.js
1422
core/config.js
File diff suppressed because it is too large
Load Diff
|
@ -9,64 +9,64 @@ const sane = require('sane');
|
|||
|
||||
module.exports = new class ConfigCache
|
||||
{
|
||||
constructor() {
|
||||
this.cache = new Map(); // path->parsed config
|
||||
}
|
||||
constructor() {
|
||||
this.cache = new Map(); // path->parsed config
|
||||
}
|
||||
|
||||
getConfigWithOptions(options, cb) {
|
||||
const cached = this.cache.has(options.filePath);
|
||||
getConfigWithOptions(options, cb) {
|
||||
const cached = this.cache.has(options.filePath);
|
||||
|
||||
if(options.forceReCache || !cached) {
|
||||
this.recacheConfigFromFile(options.filePath, (err, config) => {
|
||||
if(!err && !cached) {
|
||||
if(!options.noWatch) {
|
||||
const watcher = sane(
|
||||
paths.dirname(options.filePath),
|
||||
{
|
||||
glob : `**/${paths.basename(options.filePath)}`
|
||||
}
|
||||
);
|
||||
if(options.forceReCache || !cached) {
|
||||
this.recacheConfigFromFile(options.filePath, (err, config) => {
|
||||
if(!err && !cached) {
|
||||
if(!options.noWatch) {
|
||||
const watcher = sane(
|
||||
paths.dirname(options.filePath),
|
||||
{
|
||||
glob : `**/${paths.basename(options.filePath)}`
|
||||
}
|
||||
);
|
||||
|
||||
watcher.on('change', (fileName, fileRoot) => {
|
||||
require('./logger.js').log.info( { fileName, fileRoot }, 'Configuration file changed; re-caching');
|
||||
watcher.on('change', (fileName, fileRoot) => {
|
||||
require('./logger.js').log.info( { fileName, fileRoot }, 'Configuration file changed; re-caching');
|
||||
|
||||
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => {
|
||||
if(!err) {
|
||||
if(options.callback) {
|
||||
options.callback( { fileName, fileRoot } );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return cb(err, config, true);
|
||||
});
|
||||
} else {
|
||||
return cb(null, this.cache.get(options.filePath), false);
|
||||
}
|
||||
}
|
||||
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => {
|
||||
if(!err) {
|
||||
if(options.callback) {
|
||||
options.callback( { fileName, fileRoot } );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
return cb(err, config, true);
|
||||
});
|
||||
} else {
|
||||
return cb(null, this.cache.get(options.filePath), false);
|
||||
}
|
||||
}
|
||||
|
||||
getConfig(filePath, cb) {
|
||||
return this.getConfigWithOptions( { filePath }, cb);
|
||||
}
|
||||
getConfig(filePath, cb) {
|
||||
return this.getConfigWithOptions( { filePath }, cb);
|
||||
}
|
||||
|
||||
recacheConfigFromFile(path, cb) {
|
||||
fs.readFile(path, { encoding : 'utf-8' }, (err, data) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
recacheConfigFromFile(path, cb) {
|
||||
fs.readFile(path, { encoding : 'utf-8' }, (err, data) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
let parsed;
|
||||
try {
|
||||
parsed = hjson.parse(data);
|
||||
this.cache.set(path, parsed);
|
||||
} catch(e) {
|
||||
require('./logger.js').log.error( { filePath : path, error : e.message }, 'Failed to re-cache' );
|
||||
return cb(e);
|
||||
}
|
||||
let parsed;
|
||||
try {
|
||||
parsed = hjson.parse(data);
|
||||
this.cache.set(path, parsed);
|
||||
} catch(e) {
|
||||
require('./logger.js').log.error( { filePath : path, error : e.message }, 'Failed to re-cache' );
|
||||
return cb(e);
|
||||
}
|
||||
|
||||
return cb(null, parsed);
|
||||
});
|
||||
}
|
||||
return cb(null, parsed);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,54 +13,54 @@ exports.init = init;
|
|||
exports.getFullConfig = getFullConfig;
|
||||
|
||||
function getConfigPath(filePath) {
|
||||
// |filePath| is assumed to be in the config path if it's only a file name
|
||||
if('.' === paths.dirname(filePath)) {
|
||||
filePath = paths.join(Config().paths.config, filePath);
|
||||
}
|
||||
return filePath;
|
||||
// |filePath| is assumed to be in the config path if it's only a file name
|
||||
if('.' === paths.dirname(filePath)) {
|
||||
filePath = paths.join(Config().paths.config, filePath);
|
||||
}
|
||||
return filePath;
|
||||
}
|
||||
|
||||
function init(cb) {
|
||||
// pre-cache menu.hjson and prompt.hjson + establish events
|
||||
const changed = ( { fileName, fileRoot } ) => {
|
||||
const reCachedPath = paths.join(fileRoot, fileName);
|
||||
if(reCachedPath === getConfigPath(Config().general.menuFile)) {
|
||||
Events.emit(Events.getSystemEvents().MenusChanged);
|
||||
} else if(reCachedPath === getConfigPath(Config().general.promptFile)) {
|
||||
Events.emit(Events.getSystemEvents().PromptsChanged);
|
||||
}
|
||||
};
|
||||
// pre-cache menu.hjson and prompt.hjson + establish events
|
||||
const changed = ( { fileName, fileRoot } ) => {
|
||||
const reCachedPath = paths.join(fileRoot, fileName);
|
||||
if(reCachedPath === getConfigPath(Config().general.menuFile)) {
|
||||
Events.emit(Events.getSystemEvents().MenusChanged);
|
||||
} else if(reCachedPath === getConfigPath(Config().general.promptFile)) {
|
||||
Events.emit(Events.getSystemEvents().PromptsChanged);
|
||||
}
|
||||
};
|
||||
|
||||
const config = Config();
|
||||
async.series(
|
||||
[
|
||||
function menu(callback) {
|
||||
return ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : getConfigPath(config.general.menuFile),
|
||||
callback : changed,
|
||||
},
|
||||
callback
|
||||
);
|
||||
},
|
||||
function prompt(callback) {
|
||||
return ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : getConfigPath(config.general.promptFile),
|
||||
callback : changed,
|
||||
},
|
||||
callback
|
||||
);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
const config = Config();
|
||||
async.series(
|
||||
[
|
||||
function menu(callback) {
|
||||
return ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : getConfigPath(config.general.menuFile),
|
||||
callback : changed,
|
||||
},
|
||||
callback
|
||||
);
|
||||
},
|
||||
function prompt(callback) {
|
||||
return ConfigCache.getConfigWithOptions(
|
||||
{
|
||||
filePath : getConfigPath(config.general.promptFile),
|
||||
callback : changed,
|
||||
},
|
||||
callback
|
||||
);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getFullConfig(filePath, cb) {
|
||||
ConfigCache.getConfig(getConfigPath(filePath), (err, config) => {
|
||||
return cb(err, config);
|
||||
});
|
||||
ConfigCache.getConfig(getConfigPath(filePath), (err, config) => {
|
||||
return cb(err, config);
|
||||
});
|
||||
}
|
||||
|
|
268
core/connect.js
268
core/connect.js
|
@ -11,177 +11,177 @@ const async = require('async');
|
|||
exports.connectEntry = connectEntry;
|
||||
|
||||
function ansiDiscoverHomePosition(client, cb) {
|
||||
//
|
||||
// We want to find the home position. ANSI-BBS and most terminals
|
||||
// utilize 1,1 as home. However, some terminals such as ConnectBot
|
||||
// think of home as 0,0. If this is the case, we need to offset
|
||||
// our positioning to accomodate for such.
|
||||
//
|
||||
const done = function(err) {
|
||||
client.removeListener('cursor position report', cprListener);
|
||||
clearTimeout(giveUpTimer);
|
||||
return cb(err);
|
||||
};
|
||||
//
|
||||
// We want to find the home position. ANSI-BBS and most terminals
|
||||
// utilize 1,1 as home. However, some terminals such as ConnectBot
|
||||
// think of home as 0,0. If this is the case, we need to offset
|
||||
// our positioning to accomodate for such.
|
||||
//
|
||||
const done = function(err) {
|
||||
client.removeListener('cursor position report', cprListener);
|
||||
clearTimeout(giveUpTimer);
|
||||
return cb(err);
|
||||
};
|
||||
|
||||
const cprListener = function(pos) {
|
||||
const h = pos[0];
|
||||
const w = pos[1];
|
||||
const cprListener = function(pos) {
|
||||
const h = pos[0];
|
||||
const w = pos[1];
|
||||
|
||||
//
|
||||
// We expect either 0,0, or 1,1. Anything else will be filed as bad data
|
||||
//
|
||||
if(h > 1 || w > 1) {
|
||||
client.log.warn( { height : h, width : w }, 'Ignoring ANSI home position CPR due to unexpected values');
|
||||
return done(new Error('Home position CPR expected to be 0,0, or 1,1'));
|
||||
}
|
||||
//
|
||||
// We expect either 0,0, or 1,1. Anything else will be filed as bad data
|
||||
//
|
||||
if(h > 1 || w > 1) {
|
||||
client.log.warn( { height : h, width : w }, 'Ignoring ANSI home position CPR due to unexpected values');
|
||||
return done(new Error('Home position CPR expected to be 0,0, or 1,1'));
|
||||
}
|
||||
|
||||
if(0 === h & 0 === w) {
|
||||
//
|
||||
// Store a CPR offset in the client. All CPR's from this point on will offset by this amount
|
||||
//
|
||||
client.log.info('Setting CPR offset to 1');
|
||||
client.cprOffset = 1;
|
||||
}
|
||||
if(0 === h & 0 === w) {
|
||||
//
|
||||
// Store a CPR offset in the client. All CPR's from this point on will offset by this amount
|
||||
//
|
||||
client.log.info('Setting CPR offset to 1');
|
||||
client.cprOffset = 1;
|
||||
}
|
||||
|
||||
return done(null);
|
||||
};
|
||||
return done(null);
|
||||
};
|
||||
|
||||
client.once('cursor position report', cprListener);
|
||||
client.once('cursor position report', cprListener);
|
||||
|
||||
const giveUpTimer = setTimeout( () => {
|
||||
return done(new Error('Giving up on home position CPR'));
|
||||
}, 3000); // 3s
|
||||
const giveUpTimer = setTimeout( () => {
|
||||
return done(new Error('Giving up on home position CPR'));
|
||||
}, 3000); // 3s
|
||||
|
||||
client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos
|
||||
client.term.write(`${ansi.goHome()}${ansi.queryPos()}`); // go home, query pos
|
||||
}
|
||||
|
||||
function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||
return cb(null);
|
||||
}
|
||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
const done = function(err) {
|
||||
client.removeListener('cursor position report', cprListener);
|
||||
clearTimeout(giveUpTimer);
|
||||
return cb(err);
|
||||
};
|
||||
const done = function(err) {
|
||||
client.removeListener('cursor position report', cprListener);
|
||||
clearTimeout(giveUpTimer);
|
||||
return cb(err);
|
||||
};
|
||||
|
||||
const cprListener = function(pos) {
|
||||
//
|
||||
// If we've already found out, disregard
|
||||
//
|
||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||
return done(null);
|
||||
}
|
||||
const cprListener = function(pos) {
|
||||
//
|
||||
// If we've already found out, disregard
|
||||
//
|
||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||
return done(null);
|
||||
}
|
||||
|
||||
const h = pos[0];
|
||||
const w = pos[1];
|
||||
const h = pos[0];
|
||||
const w = pos[1];
|
||||
|
||||
//
|
||||
// Netrunner for example gives us 1x1 here. Not really useful. Ignore
|
||||
// values that seem obviously bad.
|
||||
//
|
||||
if(h < 10 || w < 10) {
|
||||
client.log.warn(
|
||||
{ height : h, width : w },
|
||||
'Ignoring ANSI CPR screen size query response due to very small values');
|
||||
return done(new Error('Term size <= 10 considered invalid'));
|
||||
}
|
||||
//
|
||||
// Netrunner for example gives us 1x1 here. Not really useful. Ignore
|
||||
// values that seem obviously bad.
|
||||
//
|
||||
if(h < 10 || w < 10) {
|
||||
client.log.warn(
|
||||
{ height : h, width : w },
|
||||
'Ignoring ANSI CPR screen size query response due to very small values');
|
||||
return done(new Error('Term size <= 10 considered invalid'));
|
||||
}
|
||||
|
||||
client.term.termHeight = h;
|
||||
client.term.termWidth = w;
|
||||
client.term.termHeight = h;
|
||||
client.term.termWidth = w;
|
||||
|
||||
client.log.debug(
|
||||
{
|
||||
termWidth : client.term.termWidth,
|
||||
termHeight : client.term.termHeight,
|
||||
source : 'ANSI CPR'
|
||||
},
|
||||
'Window size updated'
|
||||
);
|
||||
client.log.debug(
|
||||
{
|
||||
termWidth : client.term.termWidth,
|
||||
termHeight : client.term.termHeight,
|
||||
source : 'ANSI CPR'
|
||||
},
|
||||
'Window size updated'
|
||||
);
|
||||
|
||||
return done(null);
|
||||
};
|
||||
return done(null);
|
||||
};
|
||||
|
||||
client.once('cursor position report', cprListener);
|
||||
client.once('cursor position report', cprListener);
|
||||
|
||||
// give up after 2s
|
||||
const giveUpTimer = setTimeout( () => {
|
||||
return done(new Error('No term size established by CPR within timeout'));
|
||||
}, 2000);
|
||||
// give up after 2s
|
||||
const giveUpTimer = setTimeout( () => {
|
||||
return done(new Error('No term size established by CPR within timeout'));
|
||||
}, 2000);
|
||||
|
||||
// Start the process: Query for CPR
|
||||
client.term.rawWrite(ansi.queryScreenSize());
|
||||
// Start the process: Query for CPR
|
||||
client.term.rawWrite(ansi.queryScreenSize());
|
||||
}
|
||||
|
||||
function prepareTerminal(term) {
|
||||
term.rawWrite(ansi.normal());
|
||||
//term.rawWrite(ansi.disableVT100LineWrapping());
|
||||
// :TODO: set xterm stuff -- see x84/others
|
||||
term.rawWrite(ansi.normal());
|
||||
//term.rawWrite(ansi.disableVT100LineWrapping());
|
||||
// :TODO: set xterm stuff -- see x84/others
|
||||
}
|
||||
|
||||
function displayBanner(term) {
|
||||
// note: intentional formatting:
|
||||
term.pipeWrite(`
|
||||
// note: intentional formatting:
|
||||
term.pipeWrite(`
|
||||
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|
||||
|06Copyright (c) 2014-2018 Bryan Ashby |14- |12http://l33t.codes/
|
||||
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|
||||
|00`
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
function connectEntry(client, nextMenu) {
|
||||
const term = client.term;
|
||||
const term = client.term;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function basicPrepWork(callback) {
|
||||
term.rawWrite(ansi.queryDeviceAttributes(0));
|
||||
return callback(null);
|
||||
},
|
||||
function discoverHomePosition(callback) {
|
||||
ansiDiscoverHomePosition(client, () => {
|
||||
// :TODO: If CPR for home fully fails, we should bail out on the connection with an error, e.g. ANSI support required
|
||||
return callback(null); // we try to continue anyway
|
||||
});
|
||||
},
|
||||
function queryTermSizeByNonStandardAnsi(callback) {
|
||||
ansiQueryTermSizeIfNeeded(client, err => {
|
||||
if(err) {
|
||||
//
|
||||
// Check again; We may have got via NAWS/similar before CPR completed.
|
||||
//
|
||||
if(0 === term.termHeight || 0 === term.termWidth) {
|
||||
//
|
||||
// We still don't have something good for term height/width.
|
||||
// Default to DOS size 80x25.
|
||||
//
|
||||
// :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing???
|
||||
client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!');
|
||||
async.series(
|
||||
[
|
||||
function basicPrepWork(callback) {
|
||||
term.rawWrite(ansi.queryDeviceAttributes(0));
|
||||
return callback(null);
|
||||
},
|
||||
function discoverHomePosition(callback) {
|
||||
ansiDiscoverHomePosition(client, () => {
|
||||
// :TODO: If CPR for home fully fails, we should bail out on the connection with an error, e.g. ANSI support required
|
||||
return callback(null); // we try to continue anyway
|
||||
});
|
||||
},
|
||||
function queryTermSizeByNonStandardAnsi(callback) {
|
||||
ansiQueryTermSizeIfNeeded(client, err => {
|
||||
if(err) {
|
||||
//
|
||||
// Check again; We may have got via NAWS/similar before CPR completed.
|
||||
//
|
||||
if(0 === term.termHeight || 0 === term.termWidth) {
|
||||
//
|
||||
// We still don't have something good for term height/width.
|
||||
// Default to DOS size 80x25.
|
||||
//
|
||||
// :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing???
|
||||
client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!');
|
||||
|
||||
term.termHeight = 25;
|
||||
term.termWidth = 80;
|
||||
}
|
||||
}
|
||||
term.termHeight = 25;
|
||||
term.termWidth = 80;
|
||||
}
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
},
|
||||
],
|
||||
() => {
|
||||
prepareTerminal(term);
|
||||
return callback(null);
|
||||
});
|
||||
},
|
||||
],
|
||||
() => {
|
||||
prepareTerminal(term);
|
||||
|
||||
//
|
||||
// Always show an ENiGMA½ banner
|
||||
//
|
||||
displayBanner(term);
|
||||
//
|
||||
// Always show an ENiGMA½ banner
|
||||
//
|
||||
displayBanner(term);
|
||||
|
||||
// fire event
|
||||
Events.emit(Events.getSystemEvents().TermDetected, { client : client } );
|
||||
// fire event
|
||||
Events.emit(Events.getSystemEvents().TermDetected, { client : client } );
|
||||
|
||||
setTimeout( () => {
|
||||
return client.menuStack.goto(nextMenu);
|
||||
}, 500);
|
||||
}
|
||||
);
|
||||
setTimeout( () => {
|
||||
return client.menuStack.goto(nextMenu);
|
||||
}, 500);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
154
core/crc.js
154
core/crc.js
|
@ -2,90 +2,90 @@
|
|||
'use strict';
|
||||
|
||||
const CRC32_TABLE = new Int32Array([
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
|
||||
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
|
||||
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
||||
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
||||
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
|
||||
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
||||
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
||||
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
|
||||
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
||||
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
|
||||
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
||||
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
|
||||
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
|
||||
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
|
||||
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
|
||||
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
|
||||
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
||||
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
|
||||
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
||||
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
|
||||
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
|
||||
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
|
||||
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
|
||||
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
||||
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
|
||||
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
|
||||
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
||||
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
||||
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
|
||||
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
||||
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
||||
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
|
||||
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
||||
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
|
||||
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
||||
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
|
||||
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
|
||||
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
|
||||
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
|
||||
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
|
||||
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
||||
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
|
||||
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
||||
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
|
||||
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
|
||||
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
|
||||
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
|
||||
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
||||
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||
]);
|
||||
|
||||
exports.CRC32 = class CRC32 {
|
||||
constructor() {
|
||||
this.crc = -1;
|
||||
}
|
||||
constructor() {
|
||||
this.crc = -1;
|
||||
}
|
||||
|
||||
update(input) {
|
||||
input = Buffer.isBuffer(input) ? input : Buffer.from(input, 'binary');
|
||||
return input.length > 10240 ? this.update_8(input) : this.update_4(input);
|
||||
}
|
||||
update(input) {
|
||||
input = Buffer.isBuffer(input) ? input : Buffer.from(input, 'binary');
|
||||
return input.length > 10240 ? this.update_8(input) : this.update_4(input);
|
||||
}
|
||||
|
||||
update_4(input) {
|
||||
const len = input.length - 3;
|
||||
let i = 0;
|
||||
update_4(input) {
|
||||
const len = input.length - 3;
|
||||
let i = 0;
|
||||
|
||||
for(i = 0; i < len;) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
}
|
||||
while(i < len + 3) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||
}
|
||||
}
|
||||
for(i = 0; i < len;) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
}
|
||||
while(i < len + 3) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||
}
|
||||
}
|
||||
|
||||
update_8(input) {
|
||||
const len = input.length - 7;
|
||||
let i = 0;
|
||||
update_8(input) {
|
||||
const len = input.length - 7;
|
||||
let i = 0;
|
||||
|
||||
for(i = 0; i < len;) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
}
|
||||
while(i < len + 7) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||
}
|
||||
}
|
||||
for(i = 0; i < len;) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++]) & 0xff ];
|
||||
}
|
||||
while(i < len + 7) {
|
||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||
}
|
||||
}
|
||||
|
||||
finalize() {
|
||||
return (this.crc ^ (-1)) >>> 0;
|
||||
}
|
||||
finalize() {
|
||||
return (this.crc ^ (-1)) >>> 0;
|
||||
}
|
||||
};
|
||||
|
|
330
core/database.js
330
core/database.js
|
@ -25,98 +25,98 @@ exports.initializeDatabases = initializeDatabases;
|
|||
exports.dbs = dbs;
|
||||
|
||||
function getTransactionDatabase(db) {
|
||||
return sqlite3Trans.wrap(db);
|
||||
return sqlite3Trans.wrap(db);
|
||||
}
|
||||
|
||||
function getDatabasePath(name) {
|
||||
return paths.join(conf.config.paths.db, `${name}.sqlite3`);
|
||||
return paths.join(conf.config.paths.db, `${name}.sqlite3`);
|
||||
}
|
||||
|
||||
function getModDatabasePath(moduleInfo, suffix) {
|
||||
//
|
||||
// Mods that use a database are stored in Config.paths.modsDb (e.g. enigma-bbs/db/mods)
|
||||
// We expect that moduleInfo defines packageName which will be the base of the modules
|
||||
// filename. An optional suffix may be supplied as well.
|
||||
//
|
||||
const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
||||
//
|
||||
// Mods that use a database are stored in Config.paths.modsDb (e.g. enigma-bbs/db/mods)
|
||||
// We expect that moduleInfo defines packageName which will be the base of the modules
|
||||
// filename. An optional suffix may be supplied as well.
|
||||
//
|
||||
const HOST_RE = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
||||
|
||||
assert(_.isObject(moduleInfo));
|
||||
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
||||
assert(_.isObject(moduleInfo));
|
||||
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
||||
|
||||
let full = moduleInfo.packageName;
|
||||
if(suffix) {
|
||||
full += `.${suffix}`;
|
||||
}
|
||||
let full = moduleInfo.packageName;
|
||||
if(suffix) {
|
||||
full += `.${suffix}`;
|
||||
}
|
||||
|
||||
assert(
|
||||
(full.split('.').length > 1 && HOST_RE.test(full)),
|
||||
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation');
|
||||
assert(
|
||||
(full.split('.').length > 1 && HOST_RE.test(full)),
|
||||
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation');
|
||||
|
||||
return paths.join(conf.config.paths.modsDb, `${full}.sqlite3`);
|
||||
return paths.join(conf.config.paths.modsDb, `${full}.sqlite3`);
|
||||
}
|
||||
|
||||
function getISOTimestampString(ts) {
|
||||
ts = ts || moment();
|
||||
return ts.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||
ts = ts || moment();
|
||||
return ts.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||
}
|
||||
|
||||
function sanatizeString(s) {
|
||||
return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => { // eslint-disable-line no-control-regex
|
||||
switch (c) {
|
||||
case '\0' : return '\\0';
|
||||
case '\x08' : return '\\b';
|
||||
case '\x09' : return '\\t';
|
||||
case '\x1a' : return '\\z';
|
||||
case '\n' : return '\\n';
|
||||
case '\r' : return '\\r';
|
||||
return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => { // eslint-disable-line no-control-regex
|
||||
switch (c) {
|
||||
case '\0' : return '\\0';
|
||||
case '\x08' : return '\\b';
|
||||
case '\x09' : return '\\t';
|
||||
case '\x1a' : return '\\z';
|
||||
case '\n' : return '\\n';
|
||||
case '\r' : return '\\r';
|
||||
|
||||
case '"' :
|
||||
case '\'' :
|
||||
return `${c}${c}`;
|
||||
case '"' :
|
||||
case '\'' :
|
||||
return `${c}${c}`;
|
||||
|
||||
case '\\' :
|
||||
case '%' :
|
||||
return `\\${c}`;
|
||||
}
|
||||
});
|
||||
case '\\' :
|
||||
case '%' :
|
||||
return `\\${c}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initializeDatabases(cb) {
|
||||
async.eachSeries( [ 'system', 'user', 'message', 'file' ], (dbName, next) => {
|
||||
dbs[dbName] = sqlite3Trans.wrap(new sqlite3.Database(getDatabasePath(dbName), err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
async.eachSeries( [ 'system', 'user', 'message', 'file' ], (dbName, next) => {
|
||||
dbs[dbName] = sqlite3Trans.wrap(new sqlite3.Database(getDatabasePath(dbName), err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
dbs[dbName].serialize( () => {
|
||||
DB_INIT_TABLE[dbName]( () => {
|
||||
return next(null);
|
||||
});
|
||||
});
|
||||
}));
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
dbs[dbName].serialize( () => {
|
||||
DB_INIT_TABLE[dbName]( () => {
|
||||
return next(null);
|
||||
});
|
||||
});
|
||||
}));
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
function enableForeignKeys(db) {
|
||||
db.run('PRAGMA foreign_keys = ON;');
|
||||
db.run('PRAGMA foreign_keys = ON;');
|
||||
}
|
||||
|
||||
const DB_INIT_TABLE = {
|
||||
system : (cb) => {
|
||||
enableForeignKeys(dbs.system);
|
||||
system : (cb) => {
|
||||
enableForeignKeys(dbs.system);
|
||||
|
||||
// Various stat/event logging - see stat_log.js
|
||||
dbs.system.run(
|
||||
`CREATE TABLE IF NOT EXISTS system_stat (
|
||||
// Various stat/event logging - see stat_log.js
|
||||
dbs.system.run(
|
||||
`CREATE TABLE IF NOT EXISTS system_stat (
|
||||
stat_name VARCHAR PRIMARY KEY NOT NULL,
|
||||
stat_value VARCHAR NOT NULL
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.system.run(
|
||||
`CREATE TABLE IF NOT EXISTS system_event_log (
|
||||
dbs.system.run(
|
||||
`CREATE TABLE IF NOT EXISTS system_event_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
timestamp DATETIME NOT NULL,
|
||||
log_name VARCHAR NOT NULL,
|
||||
|
@ -124,10 +124,10 @@ const DB_INIT_TABLE = {
|
|||
|
||||
UNIQUE(timestamp, log_name)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.system.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_event_log (
|
||||
dbs.system.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_event_log (
|
||||
id INTEGER PRIMARY KEY,
|
||||
timestamp DATETIME NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
|
@ -136,58 +136,58 @@ const DB_INIT_TABLE = {
|
|||
|
||||
UNIQUE(timestamp, user_id, log_name)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
user : (cb) => {
|
||||
enableForeignKeys(dbs.user);
|
||||
user : (cb) => {
|
||||
enableForeignKeys(dbs.user);
|
||||
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user (
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_name VARCHAR NOT NULL,
|
||||
UNIQUE(user_name)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
// :TODO: create FK on delete/etc.
|
||||
// :TODO: create FK on delete/etc.
|
||||
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_property (
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_property (
|
||||
user_id INTEGER NOT NULL,
|
||||
prop_name VARCHAR NOT NULL,
|
||||
prop_value VARCHAR,
|
||||
UNIQUE(user_id, prop_name),
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_group_member (
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_group_member (
|
||||
group_name VARCHAR NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
UNIQUE(group_name, user_id)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_login_history (
|
||||
dbs.user.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_login_history (
|
||||
user_id INTEGER NOT NULL,
|
||||
user_name VARCHAR NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
message : (cb) => {
|
||||
enableForeignKeys(dbs.message);
|
||||
message : (cb) => {
|
||||
enableForeignKeys(dbs.message);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message (
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message (
|
||||
message_id INTEGER PRIMARY KEY,
|
||||
area_tag VARCHAR NOT NULL,
|
||||
message_uuid VARCHAR(36) NOT NULL,
|
||||
|
@ -200,47 +200,47 @@ const DB_INIT_TABLE = {
|
|||
view_count INTEGER NOT NULL DEFAULT 0,
|
||||
UNIQUE(message_uuid)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE INDEX IF NOT EXISTS message_by_area_tag_index
|
||||
dbs.message.run(
|
||||
`CREATE INDEX IF NOT EXISTS message_by_area_tag_index
|
||||
ON message (area_tag);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts4 (
|
||||
dbs.message.run(
|
||||
`CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts4 (
|
||||
content="message",
|
||||
subject,
|
||||
message
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN
|
||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN
|
||||
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN
|
||||
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message_meta (
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message_meta (
|
||||
message_id INTEGER NOT NULL,
|
||||
meta_category INTEGER NOT NULL,
|
||||
meta_name VARCHAR NOT NULL,
|
||||
|
@ -248,11 +248,11 @@ const DB_INIT_TABLE = {
|
|||
UNIQUE(message_id, meta_category, meta_name, meta_value),
|
||||
FOREIGN KEY(message_id) REFERENCES message(message_id) ON DELETE CASCADE
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
|
||||
// :TODO: need SQL to ensure cleaned up if delete from message?
|
||||
/*
|
||||
// :TODO: need SQL to ensure cleaned up if delete from message?
|
||||
/*
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS hash_tag (
|
||||
hash_tag_id INTEGER PRIMARY KEY,
|
||||
|
@ -270,33 +270,33 @@ const DB_INIT_TABLE = {
|
|||
);
|
||||
*/
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_message_area_last_read (
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS user_message_area_last_read (
|
||||
user_id INTEGER NOT NULL,
|
||||
area_tag VARCHAR NOT NULL,
|
||||
message_id INTEGER NOT NULL,
|
||||
UNIQUE(user_id, area_tag)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
||||
dbs.message.run(
|
||||
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
||||
scan_toss VARCHAR NOT NULL,
|
||||
area_tag VARCHAR NOT NULL,
|
||||
message_id INTEGER NOT NULL,
|
||||
UNIQUE(scan_toss, area_tag)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
file : (cb) => {
|
||||
enableForeignKeys(dbs.file);
|
||||
file : (cb) => {
|
||||
enableForeignKeys(dbs.file);
|
||||
|
||||
dbs.file.run(
|
||||
// :TODO: should any of this be unique -- file_sha256 unless dupes are allowed on the system
|
||||
`CREATE TABLE IF NOT EXISTS file (
|
||||
dbs.file.run(
|
||||
// :TODO: should any of this be unique -- file_sha256 unless dupes are allowed on the system
|
||||
`CREATE TABLE IF NOT EXISTS file (
|
||||
file_id INTEGER PRIMARY KEY,
|
||||
area_tag VARCHAR NOT NULL,
|
||||
file_sha256 VARCHAR NOT NULL,
|
||||
|
@ -306,105 +306,105 @@ const DB_INIT_TABLE = {
|
|||
desc_long, /* FTS @ file_fts */
|
||||
upload_timestamp DATETIME NOT NULL
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE INDEX IF NOT EXISTS file_by_area_tag_index
|
||||
dbs.file.run(
|
||||
`CREATE INDEX IF NOT EXISTS file_by_area_tag_index
|
||||
ON file (area_tag);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE INDEX IF NOT EXISTS file_by_sha256_index
|
||||
dbs.file.run(
|
||||
`CREATE INDEX IF NOT EXISTS file_by_sha256_index
|
||||
ON file (file_sha256);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts4 (
|
||||
dbs.file.run(
|
||||
`CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts4 (
|
||||
content="file",
|
||||
file_name,
|
||||
desc,
|
||||
desc_long
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
|
||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
||||
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_after_insert AFTER INSERT ON file BEGIN
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_after_insert AFTER INSERT ON file BEGIN
|
||||
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
||||
END;`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_meta (
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_meta (
|
||||
file_id INTEGER NOT NULL,
|
||||
meta_name VARCHAR NOT NULL,
|
||||
meta_value VARCHAR NOT NULL,
|
||||
UNIQUE(file_id, meta_name, meta_value),
|
||||
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS hash_tag (
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS hash_tag (
|
||||
hash_tag_id INTEGER PRIMARY KEY,
|
||||
hash_tag VARCHAR NOT NULL,
|
||||
|
||||
UNIQUE(hash_tag)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_hash_tag (
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_hash_tag (
|
||||
hash_tag_id INTEGER NOT NULL,
|
||||
file_id INTEGER NOT NULL,
|
||||
|
||||
UNIQUE(hash_tag_id, file_id)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_user_rating (
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_user_rating (
|
||||
file_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
rating INTEGER NOT NULL,
|
||||
|
||||
UNIQUE(file_id, user_id)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
||||
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
||||
expire_timestamp DATETIME NOT NULL
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_web_serve_batch (
|
||||
dbs.file.run(
|
||||
`CREATE TABLE IF NOT EXISTS file_web_serve_batch (
|
||||
hash_id VARCHAR NOT NULL,
|
||||
file_id INTEGER NOT NULL,
|
||||
|
||||
UNIQUE(hash_id, file_id)
|
||||
);`
|
||||
);
|
||||
);
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
return cb(null);
|
||||
}
|
||||
};
|
|
@ -7,66 +7,66 @@ const iconv = require('iconv-lite');
|
|||
const async = require('async');
|
||||
|
||||
module.exports = class DescriptIonFile {
|
||||
constructor() {
|
||||
this.entries = new Map();
|
||||
}
|
||||
constructor() {
|
||||
this.entries = new Map();
|
||||
}
|
||||
|
||||
get(fileName) {
|
||||
return this.entries.get(fileName);
|
||||
}
|
||||
get(fileName) {
|
||||
return this.entries.get(fileName);
|
||||
}
|
||||
|
||||
getDescription(fileName) {
|
||||
const entry = this.get(fileName);
|
||||
if(entry) {
|
||||
return entry.desc;
|
||||
}
|
||||
}
|
||||
getDescription(fileName) {
|
||||
const entry = this.get(fileName);
|
||||
if(entry) {
|
||||
return entry.desc;
|
||||
}
|
||||
}
|
||||
|
||||
static createFromFile(path, cb) {
|
||||
fs.readFile(path, (err, descData) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
static createFromFile(path, cb) {
|
||||
fs.readFile(path, (err, descData) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const descIonFile = new DescriptIonFile();
|
||||
const descIonFile = new DescriptIonFile();
|
||||
|
||||
// DESCRIPT.ION entries are terminated with a CR and/or LF
|
||||
const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g);
|
||||
// DESCRIPT.ION entries are terminated with a CR and/or LF
|
||||
const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g);
|
||||
|
||||
async.each(lines, (entryData, nextLine) => {
|
||||
//
|
||||
// We allow quoted (long) filenames or non-quoted filenames.
|
||||
// FILENAME<SPC>DESC<0x04><program data><CR/LF>
|
||||
//
|
||||
const parts = entryData.match(/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/); // eslint-disable-line no-control-regex
|
||||
if(!parts) {
|
||||
return nextLine(null);
|
||||
}
|
||||
async.each(lines, (entryData, nextLine) => {
|
||||
//
|
||||
// We allow quoted (long) filenames or non-quoted filenames.
|
||||
// FILENAME<SPC>DESC<0x04><program data><CR/LF>
|
||||
//
|
||||
const parts = entryData.match(/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/); // eslint-disable-line no-control-regex
|
||||
if(!parts) {
|
||||
return nextLine(null);
|
||||
}
|
||||
|
||||
const fileName = parts[1] || parts[2];
|
||||
const fileName = parts[1] || parts[2];
|
||||
|
||||
//
|
||||
// Un-escape CR/LF's
|
||||
// - escapped \r and/or \n
|
||||
// - BBBS style @n - See https://www.bbbs.net/sysop.html
|
||||
//
|
||||
const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n');
|
||||
//
|
||||
// Un-escape CR/LF's
|
||||
// - escapped \r and/or \n
|
||||
// - BBBS style @n - See https://www.bbbs.net/sysop.html
|
||||
//
|
||||
const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n');
|
||||
|
||||
descIonFile.entries.set(
|
||||
fileName,
|
||||
{
|
||||
desc : desc,
|
||||
programId : parts[4],
|
||||
programData : parts[5],
|
||||
}
|
||||
);
|
||||
descIonFile.entries.set(
|
||||
fileName,
|
||||
{
|
||||
desc : desc,
|
||||
programId : parts[4],
|
||||
programData : parts[5],
|
||||
}
|
||||
);
|
||||
|
||||
return nextLine(null);
|
||||
},
|
||||
() => {
|
||||
return cb(null, descIonFile);
|
||||
});
|
||||
});
|
||||
}
|
||||
return nextLine(null);
|
||||
},
|
||||
() => {
|
||||
return cb(null, descIonFile);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
206
core/door.js
206
core/door.js
|
@ -13,137 +13,137 @@ const createServer = require('net').createServer;
|
|||
exports.Door = Door;
|
||||
|
||||
function Door(client, exeInfo) {
|
||||
events.EventEmitter.call(this);
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
const self = this;
|
||||
this.client = client;
|
||||
this.exeInfo = exeInfo;
|
||||
this.exeInfo.encoding = (this.exeInfo.encoding || 'cp437').toLowerCase();
|
||||
let restored = false;
|
||||
const self = this;
|
||||
this.client = client;
|
||||
this.exeInfo = exeInfo;
|
||||
this.exeInfo.encoding = (this.exeInfo.encoding || 'cp437').toLowerCase();
|
||||
let restored = false;
|
||||
|
||||
//
|
||||
// Members of exeInfo:
|
||||
// cmd
|
||||
// args[]
|
||||
// env{}
|
||||
// cwd
|
||||
// io
|
||||
// encoding
|
||||
// dropFile
|
||||
// node
|
||||
// inhSocket
|
||||
//
|
||||
//
|
||||
// Members of exeInfo:
|
||||
// cmd
|
||||
// args[]
|
||||
// env{}
|
||||
// cwd
|
||||
// io
|
||||
// encoding
|
||||
// dropFile
|
||||
// node
|
||||
// inhSocket
|
||||
//
|
||||
|
||||
this.doorDataHandler = function(data) {
|
||||
self.client.term.write(decode(data, self.exeInfo.encoding));
|
||||
};
|
||||
this.doorDataHandler = function(data) {
|
||||
self.client.term.write(decode(data, self.exeInfo.encoding));
|
||||
};
|
||||
|
||||
this.restoreIo = function(piped) {
|
||||
if(!restored && self.client.term.output) {
|
||||
self.client.term.output.unpipe(piped);
|
||||
self.client.term.output.resume();
|
||||
restored = true;
|
||||
}
|
||||
};
|
||||
this.restoreIo = function(piped) {
|
||||
if(!restored && self.client.term.output) {
|
||||
self.client.term.output.unpipe(piped);
|
||||
self.client.term.output.resume();
|
||||
restored = true;
|
||||
}
|
||||
};
|
||||
|
||||
this.prepareSocketIoServer = function(cb) {
|
||||
if('socket' === self.exeInfo.io) {
|
||||
const sockServer = createServer(conn => {
|
||||
this.prepareSocketIoServer = function(cb) {
|
||||
if('socket' === self.exeInfo.io) {
|
||||
const sockServer = createServer(conn => {
|
||||
|
||||
sockServer.getConnections( (err, count) => {
|
||||
sockServer.getConnections( (err, count) => {
|
||||
|
||||
// We expect only one connection from our DOOR/emulator/etc.
|
||||
if(!err && count <= 1) {
|
||||
self.client.term.output.pipe(conn);
|
||||
// We expect only one connection from our DOOR/emulator/etc.
|
||||
if(!err && count <= 1) {
|
||||
self.client.term.output.pipe(conn);
|
||||
|
||||
conn.on('data', self.doorDataHandler);
|
||||
conn.on('data', self.doorDataHandler);
|
||||
|
||||
conn.once('end', () => {
|
||||
return self.restoreIo(conn);
|
||||
});
|
||||
conn.once('end', () => {
|
||||
return self.restoreIo(conn);
|
||||
});
|
||||
|
||||
conn.once('error', err => {
|
||||
self.client.log.info( { error : err.toString() }, 'Door socket server connection');
|
||||
return self.restoreIo(conn);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
conn.once('error', err => {
|
||||
self.client.log.info( { error : err.toString() }, 'Door socket server connection');
|
||||
return self.restoreIo(conn);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
sockServer.listen(0, () => {
|
||||
return cb(null, sockServer);
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
};
|
||||
sockServer.listen(0, () => {
|
||||
return cb(null, sockServer);
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
};
|
||||
|
||||
this.doorExited = function() {
|
||||
self.emit('finished');
|
||||
};
|
||||
this.doorExited = function() {
|
||||
self.emit('finished');
|
||||
};
|
||||
}
|
||||
|
||||
require('util').inherits(Door, events.EventEmitter);
|
||||
|
||||
Door.prototype.run = function() {
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
this.prepareSocketIoServer( (err, sockServer) => {
|
||||
if(err) {
|
||||
this.client.log.warn( { error : err.toString() }, 'Failed executing door');
|
||||
return self.doorExited();
|
||||
}
|
||||
this.prepareSocketIoServer( (err, sockServer) => {
|
||||
if(err) {
|
||||
this.client.log.warn( { error : err.toString() }, 'Failed executing door');
|
||||
return self.doorExited();
|
||||
}
|
||||
|
||||
// Expand arg strings, e.g. {dropFile} -> DOOR32.SYS
|
||||
// :TODO: Use .map() here
|
||||
let args = _.clone(self.exeInfo.args); // we need a copy so the original is not modified
|
||||
// Expand arg strings, e.g. {dropFile} -> DOOR32.SYS
|
||||
// :TODO: Use .map() here
|
||||
let args = _.clone(self.exeInfo.args); // we need a copy so the original is not modified
|
||||
|
||||
for(let i = 0; i < args.length; ++i) {
|
||||
args[i] = stringFormat(self.exeInfo.args[i], {
|
||||
dropFile : self.exeInfo.dropFile,
|
||||
node : self.exeInfo.node.toString(),
|
||||
srvPort : sockServer ? sockServer.address().port.toString() : '-1',
|
||||
userId : self.client.user.userId.toString(),
|
||||
});
|
||||
}
|
||||
for(let i = 0; i < args.length; ++i) {
|
||||
args[i] = stringFormat(self.exeInfo.args[i], {
|
||||
dropFile : self.exeInfo.dropFile,
|
||||
node : self.exeInfo.node.toString(),
|
||||
srvPort : sockServer ? sockServer.address().port.toString() : '-1',
|
||||
userId : self.client.user.userId.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
const door = pty.spawn(self.exeInfo.cmd, args, {
|
||||
cols : self.client.term.termWidth,
|
||||
rows : self.client.term.termHeight,
|
||||
// :TODO: cwd
|
||||
env : self.exeInfo.env,
|
||||
encoding : null, // we want to handle all encoding ourself
|
||||
});
|
||||
const door = pty.spawn(self.exeInfo.cmd, args, {
|
||||
cols : self.client.term.termWidth,
|
||||
rows : self.client.term.termHeight,
|
||||
// :TODO: cwd
|
||||
env : self.exeInfo.env,
|
||||
encoding : null, // we want to handle all encoding ourself
|
||||
});
|
||||
|
||||
if('stdio' === self.exeInfo.io) {
|
||||
self.client.log.debug('Using stdio for door I/O');
|
||||
if('stdio' === self.exeInfo.io) {
|
||||
self.client.log.debug('Using stdio for door I/O');
|
||||
|
||||
self.client.term.output.pipe(door);
|
||||
self.client.term.output.pipe(door);
|
||||
|
||||
door.on('data', self.doorDataHandler);
|
||||
door.on('data', self.doorDataHandler);
|
||||
|
||||
door.once('close', () => {
|
||||
return self.restoreIo(door);
|
||||
});
|
||||
} else if('socket' === self.exeInfo.io) {
|
||||
self.client.log.debug( { port : sockServer.address().port }, 'Using temporary socket server for door I/O');
|
||||
}
|
||||
door.once('close', () => {
|
||||
return self.restoreIo(door);
|
||||
});
|
||||
} else if('socket' === self.exeInfo.io) {
|
||||
self.client.log.debug( { port : sockServer.address().port }, 'Using temporary socket server for door I/O');
|
||||
}
|
||||
|
||||
door.once('exit', exitCode => {
|
||||
self.client.log.info( { exitCode : exitCode }, 'Door exited');
|
||||
door.once('exit', exitCode => {
|
||||
self.client.log.info( { exitCode : exitCode }, 'Door exited');
|
||||
|
||||
if(sockServer) {
|
||||
sockServer.close();
|
||||
}
|
||||
if(sockServer) {
|
||||
sockServer.close();
|
||||
}
|
||||
|
||||
// we may not get a close
|
||||
if('stdio' === self.exeInfo.io) {
|
||||
self.restoreIo(door);
|
||||
}
|
||||
// we may not get a close
|
||||
if('stdio' === self.exeInfo.io) {
|
||||
self.restoreIo(door);
|
||||
}
|
||||
|
||||
door.removeAllListeners();
|
||||
door.removeAllListeners();
|
||||
|
||||
return self.doorExited();
|
||||
});
|
||||
});
|
||||
return self.doorExited();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -11,121 +11,121 @@ const _ = require('lodash');
|
|||
const SSHClient = require('ssh2').Client;
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'DoorParty',
|
||||
desc : 'DoorParty Access Module',
|
||||
author : 'NuSkooler',
|
||||
name : 'DoorParty',
|
||||
desc : 'DoorParty Access Module',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class DoorPartyModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
// establish defaults
|
||||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'dp.throwbackbbs.com';
|
||||
this.config.sshPort = this.config.sshPort || 2022;
|
||||
this.config.rloginPort = this.config.rloginPort || 513;
|
||||
}
|
||||
// establish defaults
|
||||
this.config = options.menuConfig.config;
|
||||
this.config.host = this.config.host || 'dp.throwbackbbs.com';
|
||||
this.config.sshPort = this.config.sshPort || 2022;
|
||||
this.config.rloginPort = this.config.rloginPort || 513;
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
let clientTerminated;
|
||||
const self = this;
|
||||
initSequence() {
|
||||
let clientTerminated;
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(!_.isString(self.config.username)) {
|
||||
return callback(new Error('Config requires "username"!'));
|
||||
}
|
||||
if(!_.isString(self.config.password)) {
|
||||
return callback(new Error('Config requires "password"!'));
|
||||
}
|
||||
if(!_.isString(self.config.bbsTag)) {
|
||||
return callback(new Error('Config requires "bbsTag"!'));
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function establishSecureConnection(callback) {
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to DoorParty, please wait...\n');
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(!_.isString(self.config.username)) {
|
||||
return callback(new Error('Config requires "username"!'));
|
||||
}
|
||||
if(!_.isString(self.config.password)) {
|
||||
return callback(new Error('Config requires "password"!'));
|
||||
}
|
||||
if(!_.isString(self.config.bbsTag)) {
|
||||
return callback(new Error('Config requires "bbsTag"!'));
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function establishSecureConnection(callback) {
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to DoorParty, please wait...\n');
|
||||
|
||||
const sshClient = new SSHClient();
|
||||
const sshClient = new SSHClient();
|
||||
|
||||
let pipeRestored = false;
|
||||
let pipedStream;
|
||||
const restorePipe = function() {
|
||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
self.client.term.output.resume();
|
||||
}
|
||||
};
|
||||
let pipeRestored = false;
|
||||
let pipedStream;
|
||||
const restorePipe = function() {
|
||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
self.client.term.output.resume();
|
||||
}
|
||||
};
|
||||
|
||||
sshClient.on('ready', () => {
|
||||
// track client termination so we can clean up early
|
||||
self.client.once('end', () => {
|
||||
self.client.log.info('Connection ended. Terminating DoorParty connection');
|
||||
clientTerminated = true;
|
||||
sshClient.end();
|
||||
});
|
||||
sshClient.on('ready', () => {
|
||||
// track client termination so we can clean up early
|
||||
self.client.once('end', () => {
|
||||
self.client.log.info('Connection ended. Terminating DoorParty connection');
|
||||
clientTerminated = true;
|
||||
sshClient.end();
|
||||
});
|
||||
|
||||
// establish tunnel for rlogin
|
||||
sshClient.forwardOut('127.0.0.1', self.config.sshPort, self.config.host, self.config.rloginPort, (err, stream) => {
|
||||
if(err) {
|
||||
return callback(new Error('Failed to establish tunnel'));
|
||||
}
|
||||
// establish tunnel for rlogin
|
||||
sshClient.forwardOut('127.0.0.1', self.config.sshPort, self.config.host, self.config.rloginPort, (err, stream) => {
|
||||
if(err) {
|
||||
return callback(new Error('Failed to establish tunnel'));
|
||||
}
|
||||
|
||||
//
|
||||
// Send rlogin
|
||||
// DoorParty wants the "server username" portion to be in the format of [BBS_TAG]USERNAME, e.g.
|
||||
// [XA]nuskooler
|
||||
//
|
||||
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
||||
stream.write(rlogin);
|
||||
//
|
||||
// Send rlogin
|
||||
// DoorParty wants the "server username" portion to be in the format of [BBS_TAG]USERNAME, e.g.
|
||||
// [XA]nuskooler
|
||||
//
|
||||
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
||||
stream.write(rlogin);
|
||||
|
||||
pipedStream = stream; // :TODO: this is hacky...
|
||||
self.client.term.output.pipe(stream);
|
||||
pipedStream = stream; // :TODO: this is hacky...
|
||||
self.client.term.output.pipe(stream);
|
||||
|
||||
stream.on('data', d => {
|
||||
// :TODO: we should just pipe this...
|
||||
self.client.term.rawWrite(d);
|
||||
});
|
||||
stream.on('data', d => {
|
||||
// :TODO: we should just pipe this...
|
||||
self.client.term.rawWrite(d);
|
||||
});
|
||||
|
||||
stream.on('close', () => {
|
||||
restorePipe();
|
||||
sshClient.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
stream.on('close', () => {
|
||||
restorePipe();
|
||||
sshClient.end();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sshClient.on('error', err => {
|
||||
self.client.log.info(`DoorParty SSH client error: ${err.message}`);
|
||||
});
|
||||
sshClient.on('error', err => {
|
||||
self.client.log.info(`DoorParty SSH client error: ${err.message}`);
|
||||
});
|
||||
|
||||
sshClient.on('close', () => {
|
||||
restorePipe();
|
||||
callback(null);
|
||||
});
|
||||
sshClient.on('close', () => {
|
||||
restorePipe();
|
||||
callback(null);
|
||||
});
|
||||
|
||||
sshClient.connect( {
|
||||
host : self.config.host,
|
||||
port : self.config.sshPort,
|
||||
username : self.config.username,
|
||||
password : self.config.password,
|
||||
});
|
||||
sshClient.connect( {
|
||||
host : self.config.host,
|
||||
port : self.config.sshPort,
|
||||
username : self.config.username,
|
||||
password : self.config.password,
|
||||
});
|
||||
|
||||
// note: no explicit callback() until we're finished!
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'DoorParty error');
|
||||
}
|
||||
// note: no explicit callback() until we're finished!
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'DoorParty error');
|
||||
}
|
||||
|
||||
// if the client is stil here, go to previous
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
// if the client is stil here, go to previous
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -7,72 +7,72 @@ const FileEntry = require('./file_entry.js');
|
|||
const { partition } = require('lodash');
|
||||
|
||||
module.exports = class DownloadQueue {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
||||
if(!Array.isArray(this.client.user.downloadQueue)) {
|
||||
if(this.client.user.properties.dl_queue) {
|
||||
this.loadFromProperty(this.client.user.properties.dl_queue);
|
||||
} else {
|
||||
this.client.user.downloadQueue = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!Array.isArray(this.client.user.downloadQueue)) {
|
||||
if(this.client.user.properties.dl_queue) {
|
||||
this.loadFromProperty(this.client.user.properties.dl_queue);
|
||||
} else {
|
||||
this.client.user.downloadQueue = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get items() {
|
||||
return this.client.user.downloadQueue;
|
||||
}
|
||||
get items() {
|
||||
return this.client.user.downloadQueue;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.client.user.downloadQueue = [];
|
||||
}
|
||||
clear() {
|
||||
this.client.user.downloadQueue = [];
|
||||
}
|
||||
|
||||
toggle(fileEntry, systemFile=false) {
|
||||
if(this.isQueued(fileEntry)) {
|
||||
this.client.user.downloadQueue = this.client.user.downloadQueue.filter(e => fileEntry.fileId !== e.fileId);
|
||||
} else {
|
||||
this.add(fileEntry, systemFile);
|
||||
}
|
||||
}
|
||||
toggle(fileEntry, systemFile=false) {
|
||||
if(this.isQueued(fileEntry)) {
|
||||
this.client.user.downloadQueue = this.client.user.downloadQueue.filter(e => fileEntry.fileId !== e.fileId);
|
||||
} else {
|
||||
this.add(fileEntry, systemFile);
|
||||
}
|
||||
}
|
||||
|
||||
add(fileEntry, systemFile=false) {
|
||||
this.client.user.downloadQueue.push({
|
||||
fileId : fileEntry.fileId,
|
||||
areaTag : fileEntry.areaTag,
|
||||
fileName : fileEntry.fileName,
|
||||
path : fileEntry.filePath,
|
||||
byteSize : fileEntry.meta.byte_size || 0,
|
||||
systemFile : systemFile,
|
||||
});
|
||||
}
|
||||
add(fileEntry, systemFile=false) {
|
||||
this.client.user.downloadQueue.push({
|
||||
fileId : fileEntry.fileId,
|
||||
areaTag : fileEntry.areaTag,
|
||||
fileName : fileEntry.fileName,
|
||||
path : fileEntry.filePath,
|
||||
byteSize : fileEntry.meta.byte_size || 0,
|
||||
systemFile : systemFile,
|
||||
});
|
||||
}
|
||||
|
||||
removeItems(fileIds) {
|
||||
if(!Array.isArray(fileIds)) {
|
||||
fileIds = [ fileIds ];
|
||||
}
|
||||
removeItems(fileIds) {
|
||||
if(!Array.isArray(fileIds)) {
|
||||
fileIds = [ fileIds ];
|
||||
}
|
||||
|
||||
const [ remain, removed ] = partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
|
||||
this.client.user.downloadQueue = remain;
|
||||
return removed;
|
||||
}
|
||||
const [ remain, removed ] = partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
|
||||
this.client.user.downloadQueue = remain;
|
||||
return removed;
|
||||
}
|
||||
|
||||
isQueued(entryOrId) {
|
||||
if(entryOrId instanceof FileEntry) {
|
||||
entryOrId = entryOrId.fileId;
|
||||
}
|
||||
isQueued(entryOrId) {
|
||||
if(entryOrId instanceof FileEntry) {
|
||||
entryOrId = entryOrId.fileId;
|
||||
}
|
||||
|
||||
return this.client.user.downloadQueue.find(e => entryOrId === e.fileId) ? true : false;
|
||||
}
|
||||
return this.client.user.downloadQueue.find(e => entryOrId === e.fileId) ? true : false;
|
||||
}
|
||||
|
||||
toProperty() { return JSON.stringify(this.client.user.downloadQueue); }
|
||||
toProperty() { return JSON.stringify(this.client.user.downloadQueue); }
|
||||
|
||||
loadFromProperty(prop) {
|
||||
try {
|
||||
this.client.user.downloadQueue = JSON.parse(prop);
|
||||
} catch(e) {
|
||||
this.client.user.downloadQueue = [];
|
||||
loadFromProperty(prop) {
|
||||
try {
|
||||
this.client.user.downloadQueue = JSON.parse(prop);
|
||||
} catch(e) {
|
||||
this.client.user.downloadQueue = [];
|
||||
|
||||
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
|
||||
}
|
||||
}
|
||||
this.client.log.error( { error : e.message, property : prop }, 'Failed parsing download queue property');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
328
core/dropfile.js
328
core/dropfile.js
|
@ -23,189 +23,189 @@ exports.DropFile = DropFile;
|
|||
|
||||
function DropFile(client, fileType) {
|
||||
|
||||
var self = this;
|
||||
this.client = client;
|
||||
this.fileType = (fileType || 'DORINFO').toUpperCase();
|
||||
var self = this;
|
||||
this.client = client;
|
||||
this.fileType = (fileType || 'DORINFO').toUpperCase();
|
||||
|
||||
Object.defineProperty(this, 'fullPath', {
|
||||
get : function() {
|
||||
return paths.join(Config().paths.dropFiles, ('node' + self.client.node), self.fileName);
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'fullPath', {
|
||||
get : function() {
|
||||
return paths.join(Config().paths.dropFiles, ('node' + self.client.node), self.fileName);
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'fileName', {
|
||||
get : function() {
|
||||
return {
|
||||
DOOR : 'DOOR.SYS', // GAP BBS, many others
|
||||
DOOR32 : 'DOOR32.SYS', // EleBBS / Mystic, Syncronet, Maximus, Telegard, AdeptXBBS, ...
|
||||
CALLINFO : 'CALLINFO.BBS', // Citadel?
|
||||
DORINFO : self.getDoorInfoFileName(), // RBBS, RemoteAccess, QBBS, ...
|
||||
CHAIN : 'CHAIN.TXT', // WWIV
|
||||
CURRUSER : 'CURRUSER.BBS', // RyBBS
|
||||
SFDOORS : 'SFDOORS.DAT', // Spitfire
|
||||
PCBOARD : 'PCBOARD.SYS', // PCBoard
|
||||
TRIBBS : 'TRIBBS.SYS', // TriBBS
|
||||
USERINFO : 'USERINFO.DAT', // Wildcat! 3.0+
|
||||
JUMPER : 'JUMPER.DAT', // 2AM BBS
|
||||
SXDOOR : // System/X, dESiRE
|
||||
Object.defineProperty(this, 'fileName', {
|
||||
get : function() {
|
||||
return {
|
||||
DOOR : 'DOOR.SYS', // GAP BBS, many others
|
||||
DOOR32 : 'DOOR32.SYS', // EleBBS / Mystic, Syncronet, Maximus, Telegard, AdeptXBBS, ...
|
||||
CALLINFO : 'CALLINFO.BBS', // Citadel?
|
||||
DORINFO : self.getDoorInfoFileName(), // RBBS, RemoteAccess, QBBS, ...
|
||||
CHAIN : 'CHAIN.TXT', // WWIV
|
||||
CURRUSER : 'CURRUSER.BBS', // RyBBS
|
||||
SFDOORS : 'SFDOORS.DAT', // Spitfire
|
||||
PCBOARD : 'PCBOARD.SYS', // PCBoard
|
||||
TRIBBS : 'TRIBBS.SYS', // TriBBS
|
||||
USERINFO : 'USERINFO.DAT', // Wildcat! 3.0+
|
||||
JUMPER : 'JUMPER.DAT', // 2AM BBS
|
||||
SXDOOR : // System/X, dESiRE
|
||||
'SXDOOR.' + _.pad(self.client.node.toString(), 3, '0'),
|
||||
INFO : 'INFO.BBS', // Phoenix BBS
|
||||
}[self.fileType];
|
||||
}
|
||||
});
|
||||
INFO : 'INFO.BBS', // Phoenix BBS
|
||||
}[self.fileType];
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'dropFileContents', {
|
||||
get : function() {
|
||||
return {
|
||||
DOOR : self.getDoorSysBuffer(),
|
||||
DOOR32 : self.getDoor32Buffer(),
|
||||
DORINFO : self.getDoorInfoDefBuffer(),
|
||||
}[self.fileType];
|
||||
}
|
||||
});
|
||||
Object.defineProperty(this, 'dropFileContents', {
|
||||
get : function() {
|
||||
return {
|
||||
DOOR : self.getDoorSysBuffer(),
|
||||
DOOR32 : self.getDoor32Buffer(),
|
||||
DORINFO : self.getDoorInfoDefBuffer(),
|
||||
}[self.fileType];
|
||||
}
|
||||
});
|
||||
|
||||
this.getDoorInfoFileName = function() {
|
||||
var x;
|
||||
var node = self.client.node;
|
||||
if(10 === node) {
|
||||
x = 0;
|
||||
} else if(node < 10) {
|
||||
x = node;
|
||||
} else {
|
||||
x = String.fromCharCode('a'.charCodeAt(0) + (node - 11));
|
||||
}
|
||||
return 'DORINFO' + x + '.DEF';
|
||||
};
|
||||
this.getDoorInfoFileName = function() {
|
||||
var x;
|
||||
var node = self.client.node;
|
||||
if(10 === node) {
|
||||
x = 0;
|
||||
} else if(node < 10) {
|
||||
x = node;
|
||||
} else {
|
||||
x = String.fromCharCode('a'.charCodeAt(0) + (node - 11));
|
||||
}
|
||||
return 'DORINFO' + x + '.DEF';
|
||||
};
|
||||
|
||||
this.getDoorSysBuffer = function() {
|
||||
var up = self.client.user.properties;
|
||||
var now = moment();
|
||||
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
||||
this.getDoorSysBuffer = function() {
|
||||
var up = self.client.user.properties;
|
||||
var now = moment();
|
||||
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
||||
|
||||
// :TODO: fix time remaining
|
||||
// :TODO: fix default protocol -- user prop: transfer_protocol
|
||||
// :TODO: fix time remaining
|
||||
// :TODO: fix default protocol -- user prop: transfer_protocol
|
||||
|
||||
return iconv.encode( [
|
||||
'COM1:', // "Comm Port - COM0: = LOCAL MODE"
|
||||
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
|
||||
'8', // "Parity - 7 or 8"
|
||||
self.client.node.toString(), // "Node Number - 1 to 99"
|
||||
'57600', // "DTE Rate. Actual BPS rate to use. (kg)"
|
||||
'Y', // "Screen Display - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Printer Toggle - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Page Bell - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Caller Alarm - Y=On N=Off (Default to Y)"
|
||||
up.real_name || self.client.user.username, // "User Full Name"
|
||||
up.location || 'Anywhere', // "Calling From"
|
||||
'123-456-7890', // "Home Phone"
|
||||
'123-456-7890', // "Work/Data Phone"
|
||||
'NOPE', // "Password" (Note: this is never given out or even stored plaintext)
|
||||
secLevel, // "Security Level"
|
||||
up.login_count.toString(), // "Total Times On"
|
||||
now.format('MM/DD/YY'), // "Last Date Called"
|
||||
'15360', // "Seconds Remaining THIS call (for those that particular)"
|
||||
'256', // "Minutes Remaining THIS call"
|
||||
'GR', // "Graphics Mode - GR=Graph, NG=Non-Graph, 7E=7,E Caller"
|
||||
self.client.term.termHeight.toString(), // "Page Length"
|
||||
'N', // "User Mode - Y = Expert, N = Novice"
|
||||
'1,2,3,4,5,6,7', // "Conferences/Forums Registered In (ABCDEFG)"
|
||||
'1', // "Conference Exited To DOOR From (G)"
|
||||
'01/01/99', // "User Expiration Date (mm/dd/yy)"
|
||||
self.client.user.userId.toString(), // "User File's Record Number"
|
||||
'Z', // "Default Protocol - X, C, Y, G, I, N, Etc."
|
||||
// :TODO: fix up, down, etc. form user properties
|
||||
'0', // "Total Uploads"
|
||||
'0', // "Total Downloads"
|
||||
'0', // "Daily Download "K" Total"
|
||||
'999999', // "Daily Download Max. "K" Limit"
|
||||
moment(up.birthdate).format('MM/DD/YY'), // "Caller's Birthdate"
|
||||
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
|
||||
'X:\\GEN\\', // "Path to the GEN directory"
|
||||
StatLog.getSystemStat('sysop_username'), // "Sysop's Name (name BBS refers to Sysop as)"
|
||||
self.client.user.username, // "Alias name"
|
||||
'00:05', // "Event time (hh:mm)" (note: wat?)
|
||||
'Y', // "If its an error correcting connection (Y/N)"
|
||||
'Y', // "ANSI supported & caller using NG mode (Y/N)"
|
||||
'Y', // "Use Record Locking (Y/N)"
|
||||
'7', // "BBS Default Color (Standard IBM color code, ie, 1-15)"
|
||||
// :TODO: fix minutes here also:
|
||||
'256', // "Time Credits In Minutes (positive/negative)"
|
||||
'07/07/90', // "Last New Files Scan Date (mm/dd/yy)"
|
||||
// :TODO: fix last vs now times:
|
||||
now.format('hh:mm'), // "Time of This Call"
|
||||
now.format('hh:mm'), // "Time of Last Call (hh:mm)"
|
||||
'9999', // "Maximum daily files available"
|
||||
// :TODO: fix these stats:
|
||||
'0', // "Files d/led so far today"
|
||||
'0', // "Total "K" Bytes Uploaded"
|
||||
'0', // "Total "K" Bytes Downloaded"
|
||||
up.user_comment || 'None', // "User Comment"
|
||||
'0', // "Total Doors Opened"
|
||||
'0', // "Total Messages Left"
|
||||
return iconv.encode( [
|
||||
'COM1:', // "Comm Port - COM0: = LOCAL MODE"
|
||||
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
|
||||
'8', // "Parity - 7 or 8"
|
||||
self.client.node.toString(), // "Node Number - 1 to 99"
|
||||
'57600', // "DTE Rate. Actual BPS rate to use. (kg)"
|
||||
'Y', // "Screen Display - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Printer Toggle - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Page Bell - Y=On N=Off (Default to Y)"
|
||||
'Y', // "Caller Alarm - Y=On N=Off (Default to Y)"
|
||||
up.real_name || self.client.user.username, // "User Full Name"
|
||||
up.location || 'Anywhere', // "Calling From"
|
||||
'123-456-7890', // "Home Phone"
|
||||
'123-456-7890', // "Work/Data Phone"
|
||||
'NOPE', // "Password" (Note: this is never given out or even stored plaintext)
|
||||
secLevel, // "Security Level"
|
||||
up.login_count.toString(), // "Total Times On"
|
||||
now.format('MM/DD/YY'), // "Last Date Called"
|
||||
'15360', // "Seconds Remaining THIS call (for those that particular)"
|
||||
'256', // "Minutes Remaining THIS call"
|
||||
'GR', // "Graphics Mode - GR=Graph, NG=Non-Graph, 7E=7,E Caller"
|
||||
self.client.term.termHeight.toString(), // "Page Length"
|
||||
'N', // "User Mode - Y = Expert, N = Novice"
|
||||
'1,2,3,4,5,6,7', // "Conferences/Forums Registered In (ABCDEFG)"
|
||||
'1', // "Conference Exited To DOOR From (G)"
|
||||
'01/01/99', // "User Expiration Date (mm/dd/yy)"
|
||||
self.client.user.userId.toString(), // "User File's Record Number"
|
||||
'Z', // "Default Protocol - X, C, Y, G, I, N, Etc."
|
||||
// :TODO: fix up, down, etc. form user properties
|
||||
'0', // "Total Uploads"
|
||||
'0', // "Total Downloads"
|
||||
'0', // "Daily Download "K" Total"
|
||||
'999999', // "Daily Download Max. "K" Limit"
|
||||
moment(up.birthdate).format('MM/DD/YY'), // "Caller's Birthdate"
|
||||
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
|
||||
'X:\\GEN\\', // "Path to the GEN directory"
|
||||
StatLog.getSystemStat('sysop_username'), // "Sysop's Name (name BBS refers to Sysop as)"
|
||||
self.client.user.username, // "Alias name"
|
||||
'00:05', // "Event time (hh:mm)" (note: wat?)
|
||||
'Y', // "If its an error correcting connection (Y/N)"
|
||||
'Y', // "ANSI supported & caller using NG mode (Y/N)"
|
||||
'Y', // "Use Record Locking (Y/N)"
|
||||
'7', // "BBS Default Color (Standard IBM color code, ie, 1-15)"
|
||||
// :TODO: fix minutes here also:
|
||||
'256', // "Time Credits In Minutes (positive/negative)"
|
||||
'07/07/90', // "Last New Files Scan Date (mm/dd/yy)"
|
||||
// :TODO: fix last vs now times:
|
||||
now.format('hh:mm'), // "Time of This Call"
|
||||
now.format('hh:mm'), // "Time of Last Call (hh:mm)"
|
||||
'9999', // "Maximum daily files available"
|
||||
// :TODO: fix these stats:
|
||||
'0', // "Files d/led so far today"
|
||||
'0', // "Total "K" Bytes Uploaded"
|
||||
'0', // "Total "K" Bytes Downloaded"
|
||||
up.user_comment || 'None', // "User Comment"
|
||||
'0', // "Total Doors Opened"
|
||||
'0', // "Total Messages Left"
|
||||
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
};
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
};
|
||||
|
||||
this.getDoor32Buffer = function() {
|
||||
//
|
||||
// Resources:
|
||||
// * http://wiki.bbses.info/index.php/DOOR32.SYS
|
||||
//
|
||||
// :TODO: local/serial/telnet need to be configurable -- which also changes socket handle!
|
||||
return iconv.encode([
|
||||
'2', // :TODO: This needs to be configurable!
|
||||
// :TODO: Completely broken right now -- This need to be configurable & come from temp socket server most likely
|
||||
'-1', // self.client.output._handle.fd.toString(), // :TODO: ALWAYS -1 on Windows!
|
||||
'57600',
|
||||
Config().general.boardName,
|
||||
self.client.user.userId.toString(),
|
||||
self.client.user.properties.real_name || self.client.user.username,
|
||||
self.client.user.username,
|
||||
self.client.user.getLegacySecurityLevel().toString(),
|
||||
'546', // :TODO: Minutes left!
|
||||
'1', // ANSI
|
||||
self.client.node.toString(),
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
this.getDoor32Buffer = function() {
|
||||
//
|
||||
// Resources:
|
||||
// * http://wiki.bbses.info/index.php/DOOR32.SYS
|
||||
//
|
||||
// :TODO: local/serial/telnet need to be configurable -- which also changes socket handle!
|
||||
return iconv.encode([
|
||||
'2', // :TODO: This needs to be configurable!
|
||||
// :TODO: Completely broken right now -- This need to be configurable & come from temp socket server most likely
|
||||
'-1', // self.client.output._handle.fd.toString(), // :TODO: ALWAYS -1 on Windows!
|
||||
'57600',
|
||||
Config().general.boardName,
|
||||
self.client.user.userId.toString(),
|
||||
self.client.user.properties.real_name || self.client.user.username,
|
||||
self.client.user.username,
|
||||
self.client.user.getLegacySecurityLevel().toString(),
|
||||
'546', // :TODO: Minutes left!
|
||||
'1', // ANSI
|
||||
self.client.node.toString(),
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
this.getDoorInfoDefBuffer = function() {
|
||||
// :TODO: fix time remaining
|
||||
this.getDoorInfoDefBuffer = function() {
|
||||
// :TODO: fix time remaining
|
||||
|
||||
//
|
||||
// Resources:
|
||||
// * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm
|
||||
//
|
||||
// Note that usernames are just used for first/last names here
|
||||
//
|
||||
var opUn = /[^\s]*/.exec(StatLog.getSystemStat('sysop_username'))[0];
|
||||
var un = /[^\s]*/.exec(self.client.user.username)[0];
|
||||
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
||||
//
|
||||
// Resources:
|
||||
// * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm
|
||||
//
|
||||
// Note that usernames are just used for first/last names here
|
||||
//
|
||||
var opUn = /[^\s]*/.exec(StatLog.getSystemStat('sysop_username'))[0];
|
||||
var un = /[^\s]*/.exec(self.client.user.username)[0];
|
||||
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
||||
|
||||
return iconv.encode( [
|
||||
Config().general.boardName, // "The name of the system."
|
||||
opUn, // "The sysop's name up to the first space."
|
||||
opUn, // "The sysop's name following the first space."
|
||||
'COM1', // "The serial port the modem is connected to, or 0 if logged in on console."
|
||||
'57600', // "The current port (DTE) rate."
|
||||
'0', // "The number "0""
|
||||
un, // "The current user's name, up to the first space."
|
||||
un, // "The current user's name, following the first space."
|
||||
self.client.user.properties.location || '', // "Where the user lives, or a blank line if unknown."
|
||||
'1', // "The number "0" if TTY, or "1" if ANSI."
|
||||
secLevel, // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops."
|
||||
'546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software."
|
||||
'-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
};
|
||||
return iconv.encode( [
|
||||
Config().general.boardName, // "The name of the system."
|
||||
opUn, // "The sysop's name up to the first space."
|
||||
opUn, // "The sysop's name following the first space."
|
||||
'COM1', // "The serial port the modem is connected to, or 0 if logged in on console."
|
||||
'57600', // "The current port (DTE) rate."
|
||||
'0', // "The number "0""
|
||||
un, // "The current user's name, up to the first space."
|
||||
un, // "The current user's name, following the first space."
|
||||
self.client.user.properties.location || '', // "Where the user lives, or a blank line if unknown."
|
||||
'1', // "The number "0" if TTY, or "1" if ANSI."
|
||||
secLevel, // "The number 5 for problem users, 30 for regular users, 80 for Aides, and 100 for Sysops."
|
||||
'546', // "The number of minutes left in the current user's account, limited to 546 to keep from overflowing other software."
|
||||
'-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
|
||||
].join('\r\n') + '\r\n', 'cp437');
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
DropFile.fileTypes = [ 'DORINFO' ];
|
||||
|
||||
DropFile.prototype.createFile = function(cb) {
|
||||
fs.writeFile(this.fullPath, this.dropFileContents, function written(err) {
|
||||
cb(err);
|
||||
});
|
||||
fs.writeFile(this.fullPath, this.dropFileContents, function written(err) {
|
||||
cb(err);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -12,79 +12,79 @@ const _ = require('lodash');
|
|||
exports.EditTextView = EditTextView;
|
||||
|
||||
function EditTextView(options) {
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||
options.resizable = false;
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||
options.resizable = false;
|
||||
|
||||
TextView.call(this, options);
|
||||
TextView.call(this, options);
|
||||
|
||||
this.cursorPos = { row : 0, col : 0 };
|
||||
this.cursorPos = { row : 0, col : 0 };
|
||||
|
||||
this.clientBackspace = function() {
|
||||
const fillCharSGR = this.getStyleSGR(1) || this.getSGR();
|
||||
this.client.term.write(`\b${fillCharSGR}${this.fillChar}\b${this.getFocusSGR()}`);
|
||||
};
|
||||
this.clientBackspace = function() {
|
||||
const fillCharSGR = this.getStyleSGR(1) || this.getSGR();
|
||||
this.client.term.write(`\b${fillCharSGR}${this.fillChar}\b${this.getFocusSGR()}`);
|
||||
};
|
||||
}
|
||||
|
||||
require('util').inherits(EditTextView, TextView);
|
||||
|
||||
EditTextView.prototype.onKeyPress = function(ch, key) {
|
||||
if(key) {
|
||||
if(this.isKeyMapped('backspace', key.name)) {
|
||||
if(this.text.length > 0) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
if(key) {
|
||||
if(this.isKeyMapped('backspace', key.name)) {
|
||||
if(this.text.length > 0) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
|
||||
if(this.text.length >= this.dimens.width) {
|
||||
this.redraw();
|
||||
} else {
|
||||
this.cursorPos.col -= 1;
|
||||
if(this.cursorPos.col >= 0) {
|
||||
this.clientBackspace();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(this.text.length >= this.dimens.width) {
|
||||
this.redraw();
|
||||
} else {
|
||||
this.cursorPos.col -= 1;
|
||||
if(this.cursorPos.col >= 0) {
|
||||
this.clientBackspace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||
this.text = '';
|
||||
this.cursorPos.col = 0;
|
||||
this.setFocus(true); // resetting focus will redraw & adjust cursor
|
||||
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||
this.text = '';
|
||||
this.cursorPos.col = 0;
|
||||
this.setFocus(true); // resetting focus will redraw & adjust cursor
|
||||
|
||||
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
}
|
||||
}
|
||||
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
}
|
||||
}
|
||||
|
||||
if(ch && strUtil.isPrintable(ch)) {
|
||||
if(this.text.length < this.maxLength) {
|
||||
ch = strUtil.stylizeString(ch, this.textStyle);
|
||||
if(ch && strUtil.isPrintable(ch)) {
|
||||
if(this.text.length < this.maxLength) {
|
||||
ch = strUtil.stylizeString(ch, this.textStyle);
|
||||
|
||||
this.text += ch;
|
||||
this.text += ch;
|
||||
|
||||
if(this.text.length > this.dimens.width) {
|
||||
// no shortcuts - redraw the view
|
||||
this.redraw();
|
||||
} else {
|
||||
this.cursorPos.col += 1;
|
||||
if(this.text.length > this.dimens.width) {
|
||||
// no shortcuts - redraw the view
|
||||
this.redraw();
|
||||
} else {
|
||||
this.cursorPos.col += 1;
|
||||
|
||||
if(_.isString(this.textMaskChar)) {
|
||||
if(this.textMaskChar.length > 0) {
|
||||
this.client.term.write(this.textMaskChar);
|
||||
}
|
||||
} else {
|
||||
this.client.term.write(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(_.isString(this.textMaskChar)) {
|
||||
if(this.textMaskChar.length > 0) {
|
||||
this.client.term.write(this.textMaskChar);
|
||||
}
|
||||
} else {
|
||||
this.client.term.write(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
};
|
||||
|
||||
EditTextView.prototype.setText = function(text) {
|
||||
// draw & set |text|
|
||||
EditTextView.super_.prototype.setText.call(this, text);
|
||||
// draw & set |text|
|
||||
EditTextView.super_.prototype.setText.call(this, text);
|
||||
|
||||
// adjust local cursor tracking
|
||||
this.cursorPos = { row : 0, col : text.length };
|
||||
// adjust local cursor tracking
|
||||
this.cursorPos = { row : 0, col : text.length };
|
||||
};
|
||||
|
|
|
@ -13,20 +13,20 @@ const nodeMailer = require('nodemailer');
|
|||
exports.sendMail = sendMail;
|
||||
|
||||
function sendMail(message, cb) {
|
||||
const config = Config();
|
||||
if(!_.has(config, 'email.transport')) {
|
||||
return cb(Errors.MissingConfig('Email "email::transport" configuration missing'));
|
||||
}
|
||||
const config = Config();
|
||||
if(!_.has(config, 'email.transport')) {
|
||||
return cb(Errors.MissingConfig('Email "email::transport" configuration missing'));
|
||||
}
|
||||
|
||||
message.from = message.from || config.email.defaultFrom;
|
||||
message.from = message.from || config.email.defaultFrom;
|
||||
|
||||
const transportOptions = Object.assign( {}, config.email.transport, {
|
||||
logger : Log,
|
||||
});
|
||||
const transportOptions = Object.assign( {}, config.email.transport, {
|
||||
logger : Log,
|
||||
});
|
||||
|
||||
const transport = nodeMailer.createTransport(transportOptions);
|
||||
const transport = nodeMailer.createTransport(transportOptions);
|
||||
|
||||
transport.sendMail(message, (err, info) => {
|
||||
return cb(err, info);
|
||||
});
|
||||
transport.sendMail(message, (err, info) => {
|
||||
return cb(err, info);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,45 +2,45 @@
|
|||
'use strict';
|
||||
|
||||
class EnigError extends Error {
|
||||
constructor(message, code, reason, reasonCode) {
|
||||
super(message);
|
||||
constructor(message, code, reason, reasonCode) {
|
||||
super(message);
|
||||
|
||||
this.name = this.constructor.name;
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
this.reason = reason;
|
||||
this.reasonCode = reasonCode;
|
||||
this.name = this.constructor.name;
|
||||
this.message = message;
|
||||
this.code = code;
|
||||
this.reason = reason;
|
||||
this.reasonCode = reasonCode;
|
||||
|
||||
if(this.reason) {
|
||||
this.message += `: ${this.reason}`;
|
||||
}
|
||||
if(this.reason) {
|
||||
this.message += `: ${this.reason}`;
|
||||
}
|
||||
|
||||
if(typeof Error.captureStackTrace === 'function') {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
} else {
|
||||
this.stack = (new Error(message)).stack;
|
||||
}
|
||||
}
|
||||
if(typeof Error.captureStackTrace === 'function') {
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
} else {
|
||||
this.stack = (new Error(message)).stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.EnigError = EnigError;
|
||||
|
||||
exports.Errors = {
|
||||
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
|
||||
MenuStack : (reason, reasonCode) => new EnigError('Menu stack error', -33001, reason, reasonCode),
|
||||
DoesNotExist : (reason, reasonCode) => new EnigError('Object does not exist', -33002, reason, reasonCode),
|
||||
AccessDenied : (reason, reasonCode) => new EnigError('Access denied', -32003, reason, reasonCode),
|
||||
Invalid : (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode),
|
||||
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
||||
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
||||
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode),
|
||||
MissingParam : (reason, reasonCode) => new EnigError('Missing paramater(s)', -32008, reason, reasonCode),
|
||||
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
|
||||
MenuStack : (reason, reasonCode) => new EnigError('Menu stack error', -33001, reason, reasonCode),
|
||||
DoesNotExist : (reason, reasonCode) => new EnigError('Object does not exist', -33002, reason, reasonCode),
|
||||
AccessDenied : (reason, reasonCode) => new EnigError('Access denied', -32003, reason, reasonCode),
|
||||
Invalid : (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode),
|
||||
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
||||
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
||||
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode),
|
||||
MissingParam : (reason, reasonCode) => new EnigError('Missing paramater(s)', -32008, reason, reasonCode),
|
||||
};
|
||||
|
||||
exports.ErrorReasons = {
|
||||
AlreadyThere : 'ALREADYTHERE',
|
||||
InvalidNextMenu : 'BADNEXT',
|
||||
NoPreviousMenu : 'NOPREV',
|
||||
NoConditionMatch : 'NOCONDMATCH',
|
||||
NotEnabled : 'NOTENABLED',
|
||||
AlreadyThere : 'ALREADYTHERE',
|
||||
InvalidNextMenu : 'BADNEXT',
|
||||
NoPreviousMenu : 'NOPREV',
|
||||
NoConditionMatch : 'NOCONDMATCH',
|
||||
NotEnabled : 'NOTENABLED',
|
||||
};
|
|
@ -9,10 +9,10 @@ const Log = require('./logger.js').log;
|
|||
const assert = require('assert');
|
||||
|
||||
module.exports = function(condition, message) {
|
||||
if(Config().debug.assertsEnabled) {
|
||||
assert.apply(this, arguments);
|
||||
} else if(!(condition)) {
|
||||
const stack = new Error().stack;
|
||||
Log.error( { condition : condition, stack : stack }, message || 'Assertion failed' );
|
||||
}
|
||||
if(Config().debug.assertsEnabled) {
|
||||
assert.apply(this, arguments);
|
||||
} else if(!(condition)) {
|
||||
const stack = new Error().stack;
|
||||
Log.error( { condition : condition, stack : stack }, message || 'Assertion failed' );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -23,157 +23,157 @@ const net = require('net');
|
|||
exports.getModule = ErcClientModule;
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'ENiGMA Relay Chat Client',
|
||||
desc : 'Chat with other ENiGMA BBSes',
|
||||
author : 'Andrew Pamment',
|
||||
name : 'ENiGMA Relay Chat Client',
|
||||
desc : 'Chat with other ENiGMA BBSes',
|
||||
author : 'Andrew Pamment',
|
||||
};
|
||||
|
||||
var MciViewIds = {
|
||||
ChatDisplay : 1,
|
||||
InputArea : 3,
|
||||
ChatDisplay : 1,
|
||||
InputArea : 3,
|
||||
};
|
||||
|
||||
// :TODO: needs converted to ES6 MenuModule subclass
|
||||
function ErcClientModule(options) {
|
||||
MenuModule.prototype.ctorShim.call(this, options);
|
||||
MenuModule.prototype.ctorShim.call(this, options);
|
||||
|
||||
const self = this;
|
||||
this.config = options.menuConfig.config;
|
||||
const self = this;
|
||||
this.config = options.menuConfig.config;
|
||||
|
||||
this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}';
|
||||
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
|
||||
this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}';
|
||||
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
|
||||
|
||||
this.finishedLoading = function() {
|
||||
async.waterfall(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(_.isString(self.config.host) &&
|
||||
this.finishedLoading = function() {
|
||||
async.waterfall(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(_.isString(self.config.host) &&
|
||||
_.isNumber(self.config.port) &&
|
||||
_.isString(self.config.bbsTag))
|
||||
{
|
||||
return callback(null);
|
||||
} else {
|
||||
return callback(new Error('Configuration is missing required option(s)'));
|
||||
}
|
||||
},
|
||||
function connectToServer(callback) {
|
||||
const connectOpts = {
|
||||
port : self.config.port,
|
||||
host : self.config.host,
|
||||
};
|
||||
{
|
||||
return callback(null);
|
||||
} else {
|
||||
return callback(new Error('Configuration is missing required option(s)'));
|
||||
}
|
||||
},
|
||||
function connectToServer(callback) {
|
||||
const connectOpts = {
|
||||
port : self.config.port,
|
||||
host : self.config.host,
|
||||
};
|
||||
|
||||
const chatMessageView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
||||
const chatMessageView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
||||
|
||||
chatMessageView.setText('Connecting to server...');
|
||||
chatMessageView.redraw();
|
||||
chatMessageView.setText('Connecting to server...');
|
||||
chatMessageView.redraw();
|
||||
|
||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||
|
||||
// :TODO: Track actual client->enig connection for optional prevMenu @ final CB
|
||||
self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host);
|
||||
// :TODO: Track actual client->enig connection for optional prevMenu @ final CB
|
||||
self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host);
|
||||
|
||||
self.chatConnection.on('data', data => {
|
||||
data = data.toString();
|
||||
self.chatConnection.on('data', data => {
|
||||
data = data.toString();
|
||||
|
||||
if(data.startsWith('ERCHANDSHAKE')) {
|
||||
self.chatConnection.write(`ERCMAGIC|${self.config.bbsTag}|${self.client.user.username}\r\n`);
|
||||
} else if(data.startsWith('{')) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch(e) {
|
||||
return self.client.log.warn( { error : e.message }, 'ERC: Error parsing ERC data from server');
|
||||
}
|
||||
if(data.startsWith('ERCHANDSHAKE')) {
|
||||
self.chatConnection.write(`ERCMAGIC|${self.config.bbsTag}|${self.client.user.username}\r\n`);
|
||||
} else if(data.startsWith('{')) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch(e) {
|
||||
return self.client.log.warn( { error : e.message }, 'ERC: Error parsing ERC data from server');
|
||||
}
|
||||
|
||||
let text;
|
||||
try {
|
||||
if(data.userName) {
|
||||
// user message
|
||||
text = stringFormat(self.chatEntryFormat, data);
|
||||
} else {
|
||||
// system message
|
||||
text = stringFormat(self.systemEntryFormat, data);
|
||||
}
|
||||
} catch(e) {
|
||||
return self.client.log.warn( { error : e.message }, 'ERC: chatEntryFormat error');
|
||||
}
|
||||
let text;
|
||||
try {
|
||||
if(data.userName) {
|
||||
// user message
|
||||
text = stringFormat(self.chatEntryFormat, data);
|
||||
} else {
|
||||
// system message
|
||||
text = stringFormat(self.systemEntryFormat, data);
|
||||
}
|
||||
} catch(e) {
|
||||
return self.client.log.warn( { error : e.message }, 'ERC: chatEntryFormat error');
|
||||
}
|
||||
|
||||
chatMessageView.addText(text);
|
||||
chatMessageView.addText(text);
|
||||
|
||||
if(chatMessageView.getLineCount() > 30) { // :TODO: should probably be ChatDisplay.height?
|
||||
chatMessageView.deleteLine(0);
|
||||
chatMessageView.scrollDown();
|
||||
}
|
||||
if(chatMessageView.getLineCount() > 30) { // :TODO: should probably be ChatDisplay.height?
|
||||
chatMessageView.deleteLine(0);
|
||||
chatMessageView.scrollDown();
|
||||
}
|
||||
|
||||
chatMessageView.redraw();
|
||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||
}
|
||||
});
|
||||
chatMessageView.redraw();
|
||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||
}
|
||||
});
|
||||
|
||||
self.chatConnection.once('end', () => {
|
||||
return callback(null);
|
||||
});
|
||||
self.chatConnection.once('end', () => {
|
||||
return callback(null);
|
||||
});
|
||||
|
||||
self.chatConnection.once('error', err => {
|
||||
self.client.log.info(`ERC connection error: ${err.message}`);
|
||||
return callback(new Error('Failed connecting to ERC server!'));
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'ERC error');
|
||||
}
|
||||
self.chatConnection.once('error', err => {
|
||||
self.client.log.info(`ERC connection error: ${err.message}`);
|
||||
return callback(new Error('Failed connecting to ERC server!'));
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'ERC error');
|
||||
}
|
||||
|
||||
self.prevMenu();
|
||||
}
|
||||
);
|
||||
};
|
||||
self.prevMenu();
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
this.scrollHandler = function(keyName) {
|
||||
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
||||
const chatDisplayView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
||||
this.scrollHandler = function(keyName) {
|
||||
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
||||
const chatDisplayView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
||||
|
||||
if('up arrow' === keyName) {
|
||||
chatDisplayView.scrollUp();
|
||||
} else {
|
||||
chatDisplayView.scrollDown();
|
||||
}
|
||||
if('up arrow' === keyName) {
|
||||
chatDisplayView.scrollUp();
|
||||
} else {
|
||||
chatDisplayView.scrollDown();
|
||||
}
|
||||
|
||||
chatDisplayView.redraw();
|
||||
inputAreaView.setFocus(true);
|
||||
};
|
||||
chatDisplayView.redraw();
|
||||
inputAreaView.setFocus(true);
|
||||
};
|
||||
|
||||
|
||||
this.menuMethods = {
|
||||
inputAreaSubmit : function(formData, extraArgs, cb) {
|
||||
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
||||
const inputData = inputAreaView.getData();
|
||||
this.menuMethods = {
|
||||
inputAreaSubmit : function(formData, extraArgs, cb) {
|
||||
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
||||
const inputData = inputAreaView.getData();
|
||||
|
||||
if('/quit' === inputData.toLowerCase()) {
|
||||
self.chatConnection.end();
|
||||
} else {
|
||||
try {
|
||||
self.chatConnection.write(`${inputData}\r\n`);
|
||||
} catch(e) {
|
||||
self.client.log.warn( { error : e.message }, 'ERC error');
|
||||
}
|
||||
inputAreaView.clearText();
|
||||
}
|
||||
return cb(null);
|
||||
},
|
||||
scrollUp : function(formData, extraArgs, cb) {
|
||||
self.scrollHandler(formData.key.name);
|
||||
return cb(null);
|
||||
},
|
||||
scrollDown : function(formData, extraArgs, cb) {
|
||||
self.scrollHandler(formData.key.name);
|
||||
return cb(null);
|
||||
}
|
||||
};
|
||||
if('/quit' === inputData.toLowerCase()) {
|
||||
self.chatConnection.end();
|
||||
} else {
|
||||
try {
|
||||
self.chatConnection.write(`${inputData}\r\n`);
|
||||
} catch(e) {
|
||||
self.client.log.warn( { error : e.message }, 'ERC error');
|
||||
}
|
||||
inputAreaView.clearText();
|
||||
}
|
||||
return cb(null);
|
||||
},
|
||||
scrollUp : function(formData, extraArgs, cb) {
|
||||
self.scrollHandler(formData.key.name);
|
||||
return cb(null);
|
||||
},
|
||||
scrollDown : function(formData, extraArgs, cb) {
|
||||
self.scrollHandler(formData.key.name);
|
||||
return cb(null);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
require('util').inherits(ErcClientModule, MenuModule);
|
||||
|
||||
ErcClientModule.prototype.mciReady = function(mciData, cb) {
|
||||
this.standardMCIReadyHandler(mciData, cb);
|
||||
this.standardMCIReadyHandler(mciData, cb);
|
||||
};
|
||||
|
|
|
@ -19,251 +19,251 @@ exports.getModule = EventSchedulerModule;
|
|||
exports.EventSchedulerModule = EventSchedulerModule; // allow for loadAndStart
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Event Scheduler',
|
||||
desc : 'Support for scheduling arbritary events',
|
||||
author : 'NuSkooler',
|
||||
name : 'Event Scheduler',
|
||||
desc : 'Support for scheduling arbritary events',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const SCHEDULE_REGEXP = /(?:^|or )?(@watch:)([^\0]+)?$/;
|
||||
const ACTION_REGEXP = /@(method|execute):([^\0]+)?$/;
|
||||
|
||||
class ScheduledEvent {
|
||||
constructor(events, name) {
|
||||
this.name = name;
|
||||
this.schedule = this.parseScheduleString(events[name].schedule);
|
||||
this.action = this.parseActionSpec(events[name].action);
|
||||
if(this.action) {
|
||||
this.action.args = events[name].args || [];
|
||||
}
|
||||
}
|
||||
constructor(events, name) {
|
||||
this.name = name;
|
||||
this.schedule = this.parseScheduleString(events[name].schedule);
|
||||
this.action = this.parseActionSpec(events[name].action);
|
||||
if(this.action) {
|
||||
this.action.args = events[name].args || [];
|
||||
}
|
||||
}
|
||||
|
||||
get isValid() {
|
||||
if((!this.schedule || (!this.schedule.sched && !this.schedule.watchFile)) || !this.action) {
|
||||
return false;
|
||||
}
|
||||
get isValid() {
|
||||
if((!this.schedule || (!this.schedule.sched && !this.schedule.watchFile)) || !this.action) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if('method' === this.action.type && !this.action.location) {
|
||||
return false;
|
||||
}
|
||||
if('method' === this.action.type && !this.action.location) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
parseScheduleString(schedStr) {
|
||||
if(!schedStr) {
|
||||
return false;
|
||||
}
|
||||
parseScheduleString(schedStr) {
|
||||
if(!schedStr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let schedule = {};
|
||||
let schedule = {};
|
||||
|
||||
const m = SCHEDULE_REGEXP.exec(schedStr);
|
||||
if(m) {
|
||||
schedStr = schedStr.substr(0, m.index).trim();
|
||||
const m = SCHEDULE_REGEXP.exec(schedStr);
|
||||
if(m) {
|
||||
schedStr = schedStr.substr(0, m.index).trim();
|
||||
|
||||
if('@watch:' === m[1]) {
|
||||
schedule.watchFile = m[2];
|
||||
}
|
||||
}
|
||||
if('@watch:' === m[1]) {
|
||||
schedule.watchFile = m[2];
|
||||
}
|
||||
}
|
||||
|
||||
if(schedStr.length > 0) {
|
||||
const sched = later.parse.text(schedStr);
|
||||
if(-1 === sched.error) {
|
||||
schedule.sched = sched;
|
||||
}
|
||||
}
|
||||
if(schedStr.length > 0) {
|
||||
const sched = later.parse.text(schedStr);
|
||||
if(-1 === sched.error) {
|
||||
schedule.sched = sched;
|
||||
}
|
||||
}
|
||||
|
||||
// return undefined if we couldn't parse out anything useful
|
||||
if(!_.isEmpty(schedule)) {
|
||||
return schedule;
|
||||
}
|
||||
}
|
||||
// return undefined if we couldn't parse out anything useful
|
||||
if(!_.isEmpty(schedule)) {
|
||||
return schedule;
|
||||
}
|
||||
}
|
||||
|
||||
parseActionSpec(actionSpec) {
|
||||
if(actionSpec) {
|
||||
if('@' === actionSpec[0]) {
|
||||
const m = ACTION_REGEXP.exec(actionSpec);
|
||||
if(m) {
|
||||
if(m[2].indexOf(':') > -1) {
|
||||
const parts = m[2].split(':');
|
||||
return {
|
||||
type : m[1],
|
||||
location : parts[0],
|
||||
what : parts[1],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type : m[1],
|
||||
what : m[2],
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type : 'execute',
|
||||
what : actionSpec,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
parseActionSpec(actionSpec) {
|
||||
if(actionSpec) {
|
||||
if('@' === actionSpec[0]) {
|
||||
const m = ACTION_REGEXP.exec(actionSpec);
|
||||
if(m) {
|
||||
if(m[2].indexOf(':') > -1) {
|
||||
const parts = m[2].split(':');
|
||||
return {
|
||||
type : m[1],
|
||||
location : parts[0],
|
||||
what : parts[1],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type : m[1],
|
||||
what : m[2],
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type : 'execute',
|
||||
what : actionSpec,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
executeAction(reason, cb) {
|
||||
Log.info( { eventName : this.name, action : this.action, reason : reason }, 'Executing scheduled event 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')
|
||||
try {
|
||||
const methodModule = require(modulePath);
|
||||
methodModule[this.action.what](this.action.args, err => {
|
||||
if(err) {
|
||||
Log.debug(
|
||||
{ error : err.toString(), eventName : this.name, action : this.action },
|
||||
'Error performing scheduled event action');
|
||||
}
|
||||
if('method' === this.action.type) {
|
||||
const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js')
|
||||
try {
|
||||
const methodModule = require(modulePath);
|
||||
methodModule[this.action.what](this.action.args, err => {
|
||||
if(err) {
|
||||
Log.debug(
|
||||
{ error : err.toString(), eventName : this.name, action : this.action },
|
||||
'Error performing scheduled event action');
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
});
|
||||
} catch(e) {
|
||||
Log.warn(
|
||||
{ error : e.toString(), eventName : this.name, action : this.action },
|
||||
'Failed to perform scheduled event action');
|
||||
return cb(err);
|
||||
});
|
||||
} catch(e) {
|
||||
Log.warn(
|
||||
{ error : e.toString(), eventName : this.name, action : this.action },
|
||||
'Failed to perform scheduled event action');
|
||||
|
||||
return cb(e);
|
||||
}
|
||||
} else if('execute' === this.action.type) {
|
||||
const opts = {
|
||||
// :TODO: cwd
|
||||
name : this.name,
|
||||
cols : 80,
|
||||
rows : 24,
|
||||
env : process.env,
|
||||
};
|
||||
return cb(e);
|
||||
}
|
||||
} else if('execute' === this.action.type) {
|
||||
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);
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function EventSchedulerModule(options) {
|
||||
PluginModule.call(this, options);
|
||||
PluginModule.call(this, options);
|
||||
|
||||
const config = Config();
|
||||
if(_.has(config, 'eventScheduler')) {
|
||||
this.moduleConfig = config.eventScheduler;
|
||||
}
|
||||
const config = Config();
|
||||
if(_.has(config, 'eventScheduler')) {
|
||||
this.moduleConfig = config.eventScheduler;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
this.runningActions = new Set();
|
||||
const self = this;
|
||||
this.runningActions = new Set();
|
||||
|
||||
this.performAction = function(schedEvent, reason) {
|
||||
if(self.runningActions.has(schedEvent.name)) {
|
||||
return; // already running
|
||||
}
|
||||
this.performAction = function(schedEvent, reason) {
|
||||
if(self.runningActions.has(schedEvent.name)) {
|
||||
return; // already running
|
||||
}
|
||||
|
||||
self.runningActions.add(schedEvent.name);
|
||||
self.runningActions.add(schedEvent.name);
|
||||
|
||||
schedEvent.executeAction(reason, () => {
|
||||
self.runningActions.delete(schedEvent.name);
|
||||
});
|
||||
};
|
||||
schedEvent.executeAction(reason, () => {
|
||||
self.runningActions.delete(schedEvent.name);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// convienence static method for direct load + start
|
||||
EventSchedulerModule.loadAndStart = function(cb) {
|
||||
const loadModuleEx = require('./module_util.js').loadModuleEx;
|
||||
const loadModuleEx = require('./module_util.js').loadModuleEx;
|
||||
|
||||
const loadOpts = {
|
||||
name : path.basename(__filename, '.js'),
|
||||
path : __dirname,
|
||||
};
|
||||
const loadOpts = {
|
||||
name : path.basename(__filename, '.js'),
|
||||
path : __dirname,
|
||||
};
|
||||
|
||||
loadModuleEx(loadOpts, (err, mod) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
loadModuleEx(loadOpts, (err, mod) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const modInst = new mod.getModule();
|
||||
modInst.startup( err => {
|
||||
return cb(err, modInst);
|
||||
});
|
||||
});
|
||||
const modInst = new mod.getModule();
|
||||
modInst.startup( err => {
|
||||
return cb(err, modInst);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
EventSchedulerModule.prototype.startup = function(cb) {
|
||||
|
||||
this.eventTimers = [];
|
||||
const self = this;
|
||||
this.eventTimers = [];
|
||||
const self = this;
|
||||
|
||||
if(this.moduleConfig && _.has(this.moduleConfig, 'events')) {
|
||||
const events = Object.keys(this.moduleConfig.events).map( name => {
|
||||
return new ScheduledEvent(this.moduleConfig.events, name);
|
||||
});
|
||||
if(this.moduleConfig && _.has(this.moduleConfig, 'events')) {
|
||||
const events = Object.keys(this.moduleConfig.events).map( name => {
|
||||
return new ScheduledEvent(this.moduleConfig.events, name);
|
||||
});
|
||||
|
||||
events.forEach( schedEvent => {
|
||||
if(!schedEvent.isValid) {
|
||||
Log.warn( { eventName : schedEvent.name }, 'Invalid scheduled event entry');
|
||||
return;
|
||||
}
|
||||
events.forEach( schedEvent => {
|
||||
if(!schedEvent.isValid) {
|
||||
Log.warn( { eventName : schedEvent.name }, 'Invalid scheduled event entry');
|
||||
return;
|
||||
}
|
||||
|
||||
Log.debug(
|
||||
{
|
||||
eventName : schedEvent.name,
|
||||
schedule : this.moduleConfig.events[schedEvent.name].schedule,
|
||||
action : schedEvent.action,
|
||||
next : schedEvent.schedule.sched ? moment(later.schedule(schedEvent.schedule.sched).next(1)).format('ddd, MMM Do, YYYY @ h:m:ss a') : 'N/A',
|
||||
},
|
||||
'Scheduled event loaded'
|
||||
);
|
||||
Log.debug(
|
||||
{
|
||||
eventName : schedEvent.name,
|
||||
schedule : this.moduleConfig.events[schedEvent.name].schedule,
|
||||
action : schedEvent.action,
|
||||
next : schedEvent.schedule.sched ? moment(later.schedule(schedEvent.schedule.sched).next(1)).format('ddd, MMM Do, YYYY @ h:m:ss a') : 'N/A',
|
||||
},
|
||||
'Scheduled event loaded'
|
||||
);
|
||||
|
||||
if(schedEvent.schedule.sched) {
|
||||
this.eventTimers.push(later.setInterval( () => {
|
||||
self.performAction(schedEvent, 'Schedule');
|
||||
}, schedEvent.schedule.sched));
|
||||
}
|
||||
if(schedEvent.schedule.sched) {
|
||||
this.eventTimers.push(later.setInterval( () => {
|
||||
self.performAction(schedEvent, 'Schedule');
|
||||
}, schedEvent.schedule.sched));
|
||||
}
|
||||
|
||||
if(schedEvent.schedule.watchFile) {
|
||||
const watcher = sane(
|
||||
paths.dirname(schedEvent.schedule.watchFile),
|
||||
{
|
||||
glob : `**/${paths.basename(schedEvent.schedule.watchFile)}`
|
||||
}
|
||||
);
|
||||
if(schedEvent.schedule.watchFile) {
|
||||
const watcher = sane(
|
||||
paths.dirname(schedEvent.schedule.watchFile),
|
||||
{
|
||||
glob : `**/${paths.basename(schedEvent.schedule.watchFile)}`
|
||||
}
|
||||
);
|
||||
|
||||
// :TODO: should track watched files & stop watching @ shutdown?
|
||||
// :TODO: should track watched files & stop watching @ shutdown?
|
||||
|
||||
[ 'change', 'add', 'delete' ].forEach(event => {
|
||||
watcher.on(event, (fileName, fileRoot) => {
|
||||
const eventPath = paths.join(fileRoot, fileName);
|
||||
if(schedEvent.schedule.watchFile === eventPath) {
|
||||
self.performAction(schedEvent, `Watch file: ${eventPath}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
[ 'change', 'add', 'delete' ].forEach(event => {
|
||||
watcher.on(event, (fileName, fileRoot) => {
|
||||
const eventPath = paths.join(fileRoot, fileName);
|
||||
if(schedEvent.schedule.watchFile === eventPath) {
|
||||
self.performAction(schedEvent, `Watch file: ${eventPath}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fse.exists(schedEvent.schedule.watchFile, exists => {
|
||||
if(exists) {
|
||||
self.performAction(schedEvent, `Watch file: ${schedEvent.schedule.watchFile}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
fse.exists(schedEvent.schedule.watchFile, exists => {
|
||||
if(exists) {
|
||||
self.performAction(schedEvent, `Watch file: ${schedEvent.schedule.watchFile}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cb(null);
|
||||
cb(null);
|
||||
};
|
||||
|
||||
EventSchedulerModule.prototype.shutdown = function(cb) {
|
||||
if(this.eventTimers) {
|
||||
this.eventTimers.forEach( et => et.clear() );
|
||||
}
|
||||
if(this.eventTimers) {
|
||||
this.eventTimers.forEach( et => et.clear() );
|
||||
}
|
||||
|
||||
cb(null);
|
||||
cb(null);
|
||||
};
|
||||
|
|
106
core/events.js
106
core/events.js
|
@ -12,68 +12,68 @@ const async = require('async');
|
|||
const glob = require('glob');
|
||||
|
||||
module.exports = new class Events extends events.EventEmitter {
|
||||
constructor() {
|
||||
super();
|
||||
this.setMaxListeners(32); // :TODO: play with this...
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.setMaxListeners(32); // :TODO: play with this...
|
||||
}
|
||||
|
||||
getSystemEvents() {
|
||||
return SystemEvents;
|
||||
}
|
||||
getSystemEvents() {
|
||||
return SystemEvents;
|
||||
}
|
||||
|
||||
addListener(event, listener) {
|
||||
Log.trace( { event : event }, 'Registering event listener');
|
||||
return super.addListener(event, listener);
|
||||
}
|
||||
addListener(event, listener) {
|
||||
Log.trace( { event : event }, 'Registering event listener');
|
||||
return super.addListener(event, listener);
|
||||
}
|
||||
|
||||
emit(event, ...args) {
|
||||
Log.trace( { event : event }, 'Emitting event');
|
||||
return super.emit(event, ...args);
|
||||
}
|
||||
emit(event, ...args) {
|
||||
Log.trace( { event : event }, 'Emitting event');
|
||||
return super.emit(event, ...args);
|
||||
}
|
||||
|
||||
on(event, listener) {
|
||||
Log.trace( { event : event }, 'Registering event listener');
|
||||
return super.on(event, listener);
|
||||
}
|
||||
on(event, listener) {
|
||||
Log.trace( { event : event }, 'Registering event listener');
|
||||
return super.on(event, listener);
|
||||
}
|
||||
|
||||
once(event, listener) {
|
||||
Log.trace( { event : event }, 'Registering single use event listener');
|
||||
return super.once(event, listener);
|
||||
}
|
||||
once(event, listener) {
|
||||
Log.trace( { event : event }, 'Registering single use event listener');
|
||||
return super.once(event, listener);
|
||||
}
|
||||
|
||||
removeListener(event, listener) {
|
||||
Log.trace( { event : event }, 'Removing listener');
|
||||
return super.removeListener(event, listener);
|
||||
}
|
||||
removeListener(event, listener) {
|
||||
Log.trace( { event : event }, 'Removing listener');
|
||||
return super.removeListener(event, listener);
|
||||
}
|
||||
|
||||
startup(cb) {
|
||||
async.each(require('./module_util.js').getModulePaths(), (modulePath, nextPath) => {
|
||||
glob('*{.js,/*.js}', { cwd : modulePath }, (err, files) => {
|
||||
if(err) {
|
||||
return nextPath(err);
|
||||
}
|
||||
startup(cb) {
|
||||
async.each(require('./module_util.js').getModulePaths(), (modulePath, nextPath) => {
|
||||
glob('*{.js,/*.js}', { cwd : modulePath }, (err, files) => {
|
||||
if(err) {
|
||||
return nextPath(err);
|
||||
}
|
||||
|
||||
async.each(files, (moduleName, nextModule) => {
|
||||
const fullModulePath = paths.join(modulePath, moduleName);
|
||||
async.each(files, (moduleName, nextModule) => {
|
||||
const fullModulePath = paths.join(modulePath, moduleName);
|
||||
|
||||
try {
|
||||
const mod = require(fullModulePath);
|
||||
try {
|
||||
const mod = require(fullModulePath);
|
||||
|
||||
if(_.isFunction(mod.registerEvents)) {
|
||||
// :TODO: ... or just systemInit() / systemShutdown() & mods could call Events.on() / Events.removeListener() ?
|
||||
mod.registerEvents(this);
|
||||
}
|
||||
} catch(e) {
|
||||
Log.warn( { error : e }, 'Exception during module "registerEvents"');
|
||||
}
|
||||
if(_.isFunction(mod.registerEvents)) {
|
||||
// :TODO: ... or just systemInit() / systemShutdown() & mods could call Events.on() / Events.removeListener() ?
|
||||
mod.registerEvents(this);
|
||||
}
|
||||
} catch(e) {
|
||||
Log.warn( { error : e }, 'Exception during module "registerEvents"');
|
||||
}
|
||||
|
||||
return nextModule(null);
|
||||
}, err => {
|
||||
return nextPath(err);
|
||||
});
|
||||
});
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
return nextModule(null);
|
||||
}, err => {
|
||||
return nextPath(err);
|
||||
});
|
||||
});
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
298
core/exodus.js
298
core/exodus.js
|
@ -49,183 +49,183 @@ const SSHClient = require('ssh2').Client;
|
|||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Exodus',
|
||||
desc : 'Exodus Door Server Access Module - https://oddnetwork.org/exodus/',
|
||||
author : 'NuSkooler',
|
||||
name : 'Exodus',
|
||||
desc : 'Exodus Door Server Access Module - https://oddnetwork.org/exodus/',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class ExodusModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.config = options.menuConfig.config || {};
|
||||
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
|
||||
this.config.ticketPort = this.config.ticketPort || 1984,
|
||||
this.config.ticketPath = this.config.ticketPath || '/exodus';
|
||||
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
|
||||
this.config.sshHost = this.config.sshHost || this.config.ticketHost;
|
||||
this.config.sshPort = this.config.sshPort || 22;
|
||||
this.config.sshUser = this.config.sshUser || 'exodus_server';
|
||||
this.config.sshKeyPem = this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa');
|
||||
}
|
||||
this.config = options.menuConfig.config || {};
|
||||
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
|
||||
this.config.ticketPort = this.config.ticketPort || 1984,
|
||||
this.config.ticketPath = this.config.ticketPath || '/exodus';
|
||||
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
|
||||
this.config.sshHost = this.config.sshHost || this.config.ticketHost;
|
||||
this.config.sshPort = this.config.sshPort || 22;
|
||||
this.config.sshUser = this.config.sshUser || 'exodus_server';
|
||||
this.config.sshKeyPem = this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa');
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
initSequence() {
|
||||
|
||||
const self = this;
|
||||
let clientTerminated = false;
|
||||
const self = this;
|
||||
let clientTerminated = false;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
// very basic validation on optionals
|
||||
async.each( [ 'board', 'key', 'door' ], (key, next) => {
|
||||
return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`));
|
||||
}, callback);
|
||||
},
|
||||
function loadCertAuthorities(callback) {
|
||||
if(!_.isString(self.config.caPem)) {
|
||||
return callback(null, null);
|
||||
}
|
||||
async.waterfall(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
// very basic validation on optionals
|
||||
async.each( [ 'board', 'key', 'door' ], (key, next) => {
|
||||
return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`));
|
||||
}, callback);
|
||||
},
|
||||
function loadCertAuthorities(callback) {
|
||||
if(!_.isString(self.config.caPem)) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
fs.readFile(self.config.caPem, (err, certAuthorities) => {
|
||||
return callback(err, certAuthorities);
|
||||
});
|
||||
},
|
||||
function getTicket(certAuthorities, callback) {
|
||||
const now = moment.utc().unix();
|
||||
const sha256 = crypto.createHash('sha256').update(`${self.config.key}${now}`).digest('hex');
|
||||
const token = `${sha256}|${now}`;
|
||||
fs.readFile(self.config.caPem, (err, certAuthorities) => {
|
||||
return callback(err, certAuthorities);
|
||||
});
|
||||
},
|
||||
function getTicket(certAuthorities, callback) {
|
||||
const now = moment.utc().unix();
|
||||
const sha256 = crypto.createHash('sha256').update(`${self.config.key}${now}`).digest('hex');
|
||||
const token = `${sha256}|${now}`;
|
||||
|
||||
const postData = querystring.stringify({
|
||||
token : token,
|
||||
board : self.config.board,
|
||||
user : self.client.user.username,
|
||||
door : self.config.door,
|
||||
});
|
||||
const postData = querystring.stringify({
|
||||
token : token,
|
||||
board : self.config.board,
|
||||
user : self.client.user.username,
|
||||
door : self.config.door,
|
||||
});
|
||||
|
||||
const reqOptions = {
|
||||
hostname : self.config.ticketHost,
|
||||
port : self.config.ticketPort,
|
||||
path : self.config.ticketPath,
|
||||
rejectUnauthorized : self.config.rejectUnauthorized,
|
||||
method : 'POST',
|
||||
headers : {
|
||||
'Content-Type' : 'application/x-www-form-urlencoded',
|
||||
'Content-Length' : postData.length,
|
||||
'User-Agent' : getEnigmaUserAgent(),
|
||||
}
|
||||
};
|
||||
const reqOptions = {
|
||||
hostname : self.config.ticketHost,
|
||||
port : self.config.ticketPort,
|
||||
path : self.config.ticketPath,
|
||||
rejectUnauthorized : self.config.rejectUnauthorized,
|
||||
method : 'POST',
|
||||
headers : {
|
||||
'Content-Type' : 'application/x-www-form-urlencoded',
|
||||
'Content-Length' : postData.length,
|
||||
'User-Agent' : getEnigmaUserAgent(),
|
||||
}
|
||||
};
|
||||
|
||||
if(certAuthorities) {
|
||||
reqOptions.ca = certAuthorities;
|
||||
}
|
||||
if(certAuthorities) {
|
||||
reqOptions.ca = certAuthorities;
|
||||
}
|
||||
|
||||
let ticket = '';
|
||||
const req = https.request(reqOptions, res => {
|
||||
res.on('data', data => {
|
||||
ticket += data;
|
||||
});
|
||||
let ticket = '';
|
||||
const req = https.request(reqOptions, res => {
|
||||
res.on('data', data => {
|
||||
ticket += data;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
if(ticket.length !== 36) {
|
||||
return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`));
|
||||
}
|
||||
res.on('end', () => {
|
||||
if(ticket.length !== 36) {
|
||||
return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`));
|
||||
}
|
||||
|
||||
return callback(null, ticket);
|
||||
});
|
||||
});
|
||||
return callback(null, ticket);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', err => {
|
||||
return callback(Errors.General(`Exodus error: ${err.message}`));
|
||||
});
|
||||
req.on('error', err => {
|
||||
return callback(Errors.General(`Exodus error: ${err.message}`));
|
||||
});
|
||||
|
||||
req.write(postData);
|
||||
req.end();
|
||||
},
|
||||
function loadPrivateKey(ticket, callback) {
|
||||
fs.readFile(self.config.sshKeyPem, (err, privateKey) => {
|
||||
return callback(err, ticket, privateKey);
|
||||
});
|
||||
},
|
||||
function establishSecureConnection(ticket, privateKey, callback) {
|
||||
req.write(postData);
|
||||
req.end();
|
||||
},
|
||||
function loadPrivateKey(ticket, callback) {
|
||||
fs.readFile(self.config.sshKeyPem, (err, privateKey) => {
|
||||
return callback(err, ticket, privateKey);
|
||||
});
|
||||
},
|
||||
function establishSecureConnection(ticket, privateKey, callback) {
|
||||
|
||||
let pipeRestored = false;
|
||||
let pipedStream;
|
||||
let pipeRestored = false;
|
||||
let pipedStream;
|
||||
|
||||
function restorePipe() {
|
||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
self.client.term.output.resume();
|
||||
}
|
||||
}
|
||||
function restorePipe() {
|
||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||
self.client.term.output.unpipe(pipedStream);
|
||||
self.client.term.output.resume();
|
||||
}
|
||||
}
|
||||
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to Exodus server, please wait...\n');
|
||||
self.client.term.write(resetScreen());
|
||||
self.client.term.write('Connecting to Exodus server, please wait...\n');
|
||||
|
||||
const sshClient = new SSHClient();
|
||||
const sshClient = new SSHClient();
|
||||
|
||||
const window = {
|
||||
rows : self.client.term.termHeight,
|
||||
cols : self.client.term.termWidth,
|
||||
width : 0,
|
||||
height : 0,
|
||||
term : 'vt100', // Want to pass |self.client.term.termClient| here, but we end up getting hung up on :(
|
||||
};
|
||||
const window = {
|
||||
rows : self.client.term.termHeight,
|
||||
cols : self.client.term.termWidth,
|
||||
width : 0,
|
||||
height : 0,
|
||||
term : 'vt100', // Want to pass |self.client.term.termClient| here, but we end up getting hung up on :(
|
||||
};
|
||||
|
||||
const options = {
|
||||
env : {
|
||||
exodus : ticket,
|
||||
},
|
||||
};
|
||||
const options = {
|
||||
env : {
|
||||
exodus : ticket,
|
||||
},
|
||||
};
|
||||
|
||||
sshClient.on('ready', () => {
|
||||
self.client.once('end', () => {
|
||||
self.client.log.info('Connection ended. Terminating Exodus connection');
|
||||
clientTerminated = true;
|
||||
return sshClient.end();
|
||||
});
|
||||
sshClient.on('ready', () => {
|
||||
self.client.once('end', () => {
|
||||
self.client.log.info('Connection ended. Terminating Exodus connection');
|
||||
clientTerminated = true;
|
||||
return sshClient.end();
|
||||
});
|
||||
|
||||
sshClient.shell(window, options, (err, stream) => {
|
||||
pipedStream = stream; // :TODO: ewwwwwwwww hack
|
||||
self.client.term.output.pipe(stream);
|
||||
sshClient.shell(window, options, (err, stream) => {
|
||||
pipedStream = stream; // :TODO: ewwwwwwwww hack
|
||||
self.client.term.output.pipe(stream);
|
||||
|
||||
stream.on('data', d => {
|
||||
return self.client.term.rawWrite(d);
|
||||
});
|
||||
stream.on('data', d => {
|
||||
return self.client.term.rawWrite(d);
|
||||
});
|
||||
|
||||
stream.on('close', () => {
|
||||
restorePipe();
|
||||
return sshClient.end();
|
||||
});
|
||||
stream.on('close', () => {
|
||||
restorePipe();
|
||||
return sshClient.end();
|
||||
});
|
||||
|
||||
stream.on('error', err => {
|
||||
Log.warn( { error : err.message }, 'Exodus SSH client stream error');
|
||||
});
|
||||
});
|
||||
});
|
||||
stream.on('error', err => {
|
||||
Log.warn( { error : err.message }, 'Exodus SSH client stream error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
sshClient.on('close', () => {
|
||||
restorePipe();
|
||||
return callback(null);
|
||||
});
|
||||
sshClient.on('close', () => {
|
||||
restorePipe();
|
||||
return callback(null);
|
||||
});
|
||||
|
||||
sshClient.connect({
|
||||
host : self.config.sshHost,
|
||||
port : self.config.sshPort,
|
||||
username : self.config.sshUser,
|
||||
privateKey : privateKey,
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'Exodus error');
|
||||
}
|
||||
sshClient.connect({
|
||||
host : self.config.sshHost,
|
||||
port : self.config.sshPort,
|
||||
username : self.config.sshUser,
|
||||
privateKey : privateKey,
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'Exodus error');
|
||||
}
|
||||
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
if(!clientTerminated) {
|
||||
self.prevMenu();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,328 +12,328 @@ const stringFormat = require('./string_format.js');
|
|||
const async = require('async');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Area Filter Editor',
|
||||
desc : 'Module for adding, deleting, and modifying file base filters',
|
||||
author : 'NuSkooler',
|
||||
name : 'File Area Filter Editor',
|
||||
desc : 'Module for adding, deleting, and modifying file base filters',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
editor : {
|
||||
searchTerms : 1,
|
||||
tags : 2,
|
||||
area : 3,
|
||||
sort : 4,
|
||||
order : 5,
|
||||
filterName : 6,
|
||||
navMenu : 7,
|
||||
editor : {
|
||||
searchTerms : 1,
|
||||
tags : 2,
|
||||
area : 3,
|
||||
sort : 4,
|
||||
order : 5,
|
||||
filterName : 6,
|
||||
navMenu : 7,
|
||||
|
||||
// :TODO: use the customs new standard thing - filter obj can have active/selected, etc.
|
||||
selectedFilterInfo : 10, // { ...filter object ... }
|
||||
activeFilterInfo : 11, // { ...filter object ... }
|
||||
error : 12, // validation errors
|
||||
}
|
||||
// :TODO: use the customs new standard thing - filter obj can have active/selected, etc.
|
||||
selectedFilterInfo : 10, // { ...filter object ... }
|
||||
activeFilterInfo : 11, // { ...filter object ... }
|
||||
error : 12, // validation errors
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.filtersArray = new FileBaseFilters(this.client).toArray(); // ordered, such that we can index into them
|
||||
this.currentFilterIndex = 0; // into |filtersArray|
|
||||
this.filtersArray = new FileBaseFilters(this.client).toArray(); // ordered, such that we can index into them
|
||||
this.currentFilterIndex = 0; // into |filtersArray|
|
||||
|
||||
//
|
||||
// Lexical sort + keep currently active filter (if any) as the first item in |filtersArray|
|
||||
//
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||
this.filtersArray.sort( (filterA, filterB) => {
|
||||
if(activeFilter) {
|
||||
if(filterA.uuid === activeFilter.uuid) {
|
||||
return -1;
|
||||
}
|
||||
if(filterB.uuid === activeFilter.uuid) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
//
|
||||
// Lexical sort + keep currently active filter (if any) as the first item in |filtersArray|
|
||||
//
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||
this.filtersArray.sort( (filterA, filterB) => {
|
||||
if(activeFilter) {
|
||||
if(filterA.uuid === activeFilter.uuid) {
|
||||
return -1;
|
||||
}
|
||||
if(filterB.uuid === activeFilter.uuid) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return filterA.name.localeCompare(filterB.name, { sensitivity : false, numeric : true } );
|
||||
});
|
||||
return filterA.name.localeCompare(filterB.name, { sensitivity : false, numeric : true } );
|
||||
});
|
||||
|
||||
this.menuMethods = {
|
||||
saveFilter : (formData, extraArgs, cb) => {
|
||||
return this.saveCurrentFilter(formData, cb);
|
||||
},
|
||||
prevFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex -= 1;
|
||||
if(this.currentFilterIndex < 0) {
|
||||
this.currentFilterIndex = this.filtersArray.length - 1;
|
||||
}
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
return cb(null);
|
||||
},
|
||||
nextFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex += 1;
|
||||
if(this.currentFilterIndex >= this.filtersArray.length) {
|
||||
this.currentFilterIndex = 0;
|
||||
}
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
return cb(null);
|
||||
},
|
||||
makeFilterActive : (formData, extraArgs, cb) => {
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
filters.setActive(this.filtersArray[this.currentFilterIndex].uuid);
|
||||
this.menuMethods = {
|
||||
saveFilter : (formData, extraArgs, cb) => {
|
||||
return this.saveCurrentFilter(formData, cb);
|
||||
},
|
||||
prevFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex -= 1;
|
||||
if(this.currentFilterIndex < 0) {
|
||||
this.currentFilterIndex = this.filtersArray.length - 1;
|
||||
}
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
return cb(null);
|
||||
},
|
||||
nextFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex += 1;
|
||||
if(this.currentFilterIndex >= this.filtersArray.length) {
|
||||
this.currentFilterIndex = 0;
|
||||
}
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
return cb(null);
|
||||
},
|
||||
makeFilterActive : (formData, extraArgs, cb) => {
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
filters.setActive(this.filtersArray[this.currentFilterIndex].uuid);
|
||||
|
||||
this.updateActiveLabel();
|
||||
this.updateActiveLabel();
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
newFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex = this.filtersArray.length; // next avail slot
|
||||
this.clearForm(MciViewIds.editor.searchTerms);
|
||||
return cb(null);
|
||||
},
|
||||
deleteFilter : (formData, extraArgs, cb) => {
|
||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||
const filterUuid = selectedFilter.uuid;
|
||||
return cb(null);
|
||||
},
|
||||
newFilter : (formData, extraArgs, cb) => {
|
||||
this.currentFilterIndex = this.filtersArray.length; // next avail slot
|
||||
this.clearForm(MciViewIds.editor.searchTerms);
|
||||
return cb(null);
|
||||
},
|
||||
deleteFilter : (formData, extraArgs, cb) => {
|
||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||
const filterUuid = selectedFilter.uuid;
|
||||
|
||||
// cannot delete built-in/system filters
|
||||
if(true === selectedFilter.system) {
|
||||
this.showError('Cannot delete built in filters!');
|
||||
return cb(null);
|
||||
}
|
||||
// cannot delete built-in/system filters
|
||||
if(true === selectedFilter.system) {
|
||||
this.showError('Cannot delete built in filters!');
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
this.filtersArray.splice(this.currentFilterIndex, 1); // remove selected entry
|
||||
this.filtersArray.splice(this.currentFilterIndex, 1); // remove selected entry
|
||||
|
||||
// remove from stored properties
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
filters.remove(filterUuid);
|
||||
filters.persist( () => {
|
||||
// remove from stored properties
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
filters.remove(filterUuid);
|
||||
filters.persist( () => {
|
||||
|
||||
//
|
||||
// If the item was also the active filter, we need to make a new one active
|
||||
//
|
||||
if(filterUuid === this.client.user.properties.file_base_filter_active_uuid) {
|
||||
const newActive = this.filtersArray[this.currentFilterIndex];
|
||||
if(newActive) {
|
||||
filters.setActive(newActive.uuid);
|
||||
} else {
|
||||
// nothing to set active to
|
||||
this.client.user.removeProperty('file_base_filter_active_uuid');
|
||||
}
|
||||
}
|
||||
//
|
||||
// If the item was also the active filter, we need to make a new one active
|
||||
//
|
||||
if(filterUuid === this.client.user.properties.file_base_filter_active_uuid) {
|
||||
const newActive = this.filtersArray[this.currentFilterIndex];
|
||||
if(newActive) {
|
||||
filters.setActive(newActive.uuid);
|
||||
} else {
|
||||
// nothing to set active to
|
||||
this.client.user.removeProperty('file_base_filter_active_uuid');
|
||||
}
|
||||
}
|
||||
|
||||
// update UI
|
||||
this.updateActiveLabel();
|
||||
// update UI
|
||||
this.updateActiveLabel();
|
||||
|
||||
if(this.filtersArray.length > 0) {
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
} else {
|
||||
this.clearForm();
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
},
|
||||
if(this.filtersArray.length > 0) {
|
||||
this.loadDataForFilter(this.currentFilterIndex);
|
||||
} else {
|
||||
this.clearForm();
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
},
|
||||
|
||||
viewValidationListener : (err, cb) => {
|
||||
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
||||
let newFocusId;
|
||||
viewValidationListener : (err, cb) => {
|
||||
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
||||
let newFocusId;
|
||||
|
||||
if(errorView) {
|
||||
if(err) {
|
||||
errorView.setText(err.message);
|
||||
err.view.clearText(); // clear out the invalid data
|
||||
} else {
|
||||
errorView.clearText();
|
||||
}
|
||||
}
|
||||
if(errorView) {
|
||||
if(err) {
|
||||
errorView.setText(err.message);
|
||||
err.view.clearText(); // clear out the invalid data
|
||||
} else {
|
||||
errorView.clearText();
|
||||
}
|
||||
}
|
||||
|
||||
return cb(newFocusId);
|
||||
},
|
||||
};
|
||||
}
|
||||
return cb(newFocusId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
showError(errMsg) {
|
||||
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
||||
if(errorView) {
|
||||
if(errMsg) {
|
||||
errorView.setText(errMsg);
|
||||
} else {
|
||||
errorView.clearText();
|
||||
}
|
||||
}
|
||||
}
|
||||
showError(errMsg) {
|
||||
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
||||
if(errorView) {
|
||||
if(errMsg) {
|
||||
errorView.setText(errMsg);
|
||||
} else {
|
||||
errorView.clearText();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.addViewController( 'editor', new ViewController( { client : this.client } ) );
|
||||
const self = this;
|
||||
const vc = self.addViewController( 'editor', new ViewController( { client : this.client } ) );
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||
},
|
||||
function populateAreas(callback) {
|
||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||
},
|
||||
function populateAreas(callback) {
|
||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||
|
||||
const areasView = vc.getView(MciViewIds.editor.area);
|
||||
if(areasView) {
|
||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||
}
|
||||
const areasView = vc.getView(MciViewIds.editor.area);
|
||||
if(areasView) {
|
||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||
}
|
||||
|
||||
self.updateActiveLabel();
|
||||
self.loadDataForFilter(self.currentFilterIndex);
|
||||
self.viewControllers.editor.resetInitialFocus();
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
self.updateActiveLabel();
|
||||
self.loadDataForFilter(self.currentFilterIndex);
|
||||
self.viewControllers.editor.resetInitialFocus();
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getCurrentFilter() {
|
||||
return this.filtersArray[this.currentFilterIndex];
|
||||
}
|
||||
getCurrentFilter() {
|
||||
return this.filtersArray[this.currentFilterIndex];
|
||||
}
|
||||
|
||||
setText(mciId, text) {
|
||||
const view = this.viewControllers.editor.getView(mciId);
|
||||
if(view) {
|
||||
view.setText(text);
|
||||
}
|
||||
}
|
||||
setText(mciId, text) {
|
||||
const view = this.viewControllers.editor.getView(mciId);
|
||||
if(view) {
|
||||
view.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
updateActiveLabel() {
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||
if(activeFilter) {
|
||||
const activeFormat = this.menuConfig.config.activeFormat || '{name}';
|
||||
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
|
||||
}
|
||||
}
|
||||
updateActiveLabel() {
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||
if(activeFilter) {
|
||||
const activeFormat = this.menuConfig.config.activeFormat || '{name}';
|
||||
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
|
||||
}
|
||||
}
|
||||
|
||||
setFocusItemIndex(mciId, index) {
|
||||
const view = this.viewControllers.editor.getView(mciId);
|
||||
if(view) {
|
||||
view.setFocusItemIndex(index);
|
||||
}
|
||||
}
|
||||
setFocusItemIndex(mciId, index) {
|
||||
const view = this.viewControllers.editor.getView(mciId);
|
||||
if(view) {
|
||||
view.setFocusItemIndex(index);
|
||||
}
|
||||
}
|
||||
|
||||
clearForm(newFocusId) {
|
||||
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => {
|
||||
this.setText(mciId, '');
|
||||
});
|
||||
clearForm(newFocusId) {
|
||||
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => {
|
||||
this.setText(mciId, '');
|
||||
});
|
||||
|
||||
[ MciViewIds.editor.area, MciViewIds.editor.order, MciViewIds.editor.sort ].forEach(mciId => {
|
||||
this.setFocusItemIndex(mciId, 0);
|
||||
});
|
||||
[ MciViewIds.editor.area, MciViewIds.editor.order, MciViewIds.editor.sort ].forEach(mciId => {
|
||||
this.setFocusItemIndex(mciId, 0);
|
||||
});
|
||||
|
||||
if(newFocusId) {
|
||||
this.viewControllers.editor.switchFocus(newFocusId);
|
||||
} else {
|
||||
this.viewControllers.editor.resetInitialFocus();
|
||||
}
|
||||
}
|
||||
if(newFocusId) {
|
||||
this.viewControllers.editor.switchFocus(newFocusId);
|
||||
} else {
|
||||
this.viewControllers.editor.resetInitialFocus();
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedAreaTag(index) {
|
||||
if(0 === index) {
|
||||
return ''; // -ALL-
|
||||
}
|
||||
const area = this.availAreas[index];
|
||||
if(!area) {
|
||||
return '';
|
||||
}
|
||||
return area.areaTag;
|
||||
}
|
||||
getSelectedAreaTag(index) {
|
||||
if(0 === index) {
|
||||
return ''; // -ALL-
|
||||
}
|
||||
const area = this.availAreas[index];
|
||||
if(!area) {
|
||||
return '';
|
||||
}
|
||||
return area.areaTag;
|
||||
}
|
||||
|
||||
getOrderBy(index) {
|
||||
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||
}
|
||||
getOrderBy(index) {
|
||||
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||
}
|
||||
|
||||
setAreaIndexFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
// special treatment: areaTag saved as blank ("") if -ALL-
|
||||
index = (filter.areaTag && this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) || 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.setFocusItemIndex(MciViewIds.editor.area, index);
|
||||
}
|
||||
setAreaIndexFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
// special treatment: areaTag saved as blank ("") if -ALL-
|
||||
index = (filter.areaTag && this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) || 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.setFocusItemIndex(MciViewIds.editor.area, index);
|
||||
}
|
||||
|
||||
setOrderByFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.setFocusItemIndex(MciViewIds.editor.order, index);
|
||||
}
|
||||
setOrderByFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.setFocusItemIndex(MciViewIds.editor.order, index);
|
||||
}
|
||||
|
||||
setSortByFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
index = FileBaseFilters.SortByValues.findIndex( sb => filter.sort === sb ) || 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.setFocusItemIndex(MciViewIds.editor.sort, index);
|
||||
}
|
||||
setSortByFromCurrentFilter() {
|
||||
let index;
|
||||
const filter = this.getCurrentFilter();
|
||||
if(filter) {
|
||||
index = FileBaseFilters.SortByValues.findIndex( sb => filter.sort === sb ) || 0;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.setFocusItemIndex(MciViewIds.editor.sort, index);
|
||||
}
|
||||
|
||||
getSortBy(index) {
|
||||
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||
}
|
||||
getSortBy(index) {
|
||||
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||
}
|
||||
|
||||
setFilterValuesFromFormData(filter, formData) {
|
||||
filter.name = formData.value.name;
|
||||
filter.areaTag = this.getSelectedAreaTag(formData.value.areaIndex);
|
||||
filter.terms = formData.value.searchTerms;
|
||||
filter.tags = formData.value.tags;
|
||||
filter.order = this.getOrderBy(formData.value.orderByIndex);
|
||||
filter.sort = this.getSortBy(formData.value.sortByIndex);
|
||||
}
|
||||
setFilterValuesFromFormData(filter, formData) {
|
||||
filter.name = formData.value.name;
|
||||
filter.areaTag = this.getSelectedAreaTag(formData.value.areaIndex);
|
||||
filter.terms = formData.value.searchTerms;
|
||||
filter.tags = formData.value.tags;
|
||||
filter.order = this.getOrderBy(formData.value.orderByIndex);
|
||||
filter.sort = this.getSortBy(formData.value.sortByIndex);
|
||||
}
|
||||
|
||||
saveCurrentFilter(formData, cb) {
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||
saveCurrentFilter(formData, cb) {
|
||||
const filters = new FileBaseFilters(this.client);
|
||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||
|
||||
if(selectedFilter) {
|
||||
// *update* currently selected filter
|
||||
this.setFilterValuesFromFormData(selectedFilter, formData);
|
||||
filters.replace(selectedFilter.uuid, selectedFilter);
|
||||
} else {
|
||||
// add a new entry; note that UUID will be generated
|
||||
const newFilter = {};
|
||||
this.setFilterValuesFromFormData(newFilter, formData);
|
||||
if(selectedFilter) {
|
||||
// *update* currently selected filter
|
||||
this.setFilterValuesFromFormData(selectedFilter, formData);
|
||||
filters.replace(selectedFilter.uuid, selectedFilter);
|
||||
} else {
|
||||
// add a new entry; note that UUID will be generated
|
||||
const newFilter = {};
|
||||
this.setFilterValuesFromFormData(newFilter, formData);
|
||||
|
||||
// set current to what we just saved
|
||||
newFilter.uuid = filters.add(newFilter);
|
||||
// set current to what we just saved
|
||||
newFilter.uuid = filters.add(newFilter);
|
||||
|
||||
// add to our array (at current index position)
|
||||
this.filtersArray[this.currentFilterIndex] = newFilter;
|
||||
}
|
||||
// add to our array (at current index position)
|
||||
this.filtersArray[this.currentFilterIndex] = newFilter;
|
||||
}
|
||||
|
||||
return filters.persist(cb);
|
||||
}
|
||||
return filters.persist(cb);
|
||||
}
|
||||
|
||||
loadDataForFilter(filterIndex) {
|
||||
const filter = this.filtersArray[filterIndex];
|
||||
if(filter) {
|
||||
this.setText(MciViewIds.editor.searchTerms, filter.terms);
|
||||
this.setText(MciViewIds.editor.tags, filter.tags);
|
||||
this.setText(MciViewIds.editor.filterName, filter.name);
|
||||
loadDataForFilter(filterIndex) {
|
||||
const filter = this.filtersArray[filterIndex];
|
||||
if(filter) {
|
||||
this.setText(MciViewIds.editor.searchTerms, filter.terms);
|
||||
this.setText(MciViewIds.editor.tags, filter.tags);
|
||||
this.setText(MciViewIds.editor.filterName, filter.name);
|
||||
|
||||
this.setAreaIndexFromCurrentFilter();
|
||||
this.setSortByFromCurrentFilter();
|
||||
this.setOrderByFromCurrentFilter();
|
||||
}
|
||||
}
|
||||
this.setAreaIndexFromCurrentFilter();
|
||||
this.setSortByFromCurrentFilter();
|
||||
this.setOrderByFromCurrentFilter();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -26,470 +26,470 @@ const mimeTypes = require('mime-types');
|
|||
const yazl = require('yazl');
|
||||
|
||||
function notEnabledError() {
|
||||
return Errors.General('Web server is not enabled', ErrNotEnabled);
|
||||
return Errors.General('Web server is not enabled', ErrNotEnabled);
|
||||
}
|
||||
|
||||
class FileAreaWebAccess {
|
||||
constructor() {
|
||||
this.hashids = new hashids(Config().general.boardName);
|
||||
this.expireTimers = {}; // hashId->timer
|
||||
}
|
||||
constructor() {
|
||||
this.hashids = new hashids(Config().general.boardName);
|
||||
this.expireTimers = {}; // hashId->timer
|
||||
}
|
||||
|
||||
startup(cb) {
|
||||
const self = this;
|
||||
startup(cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function initFromDb(callback) {
|
||||
return self.load(callback);
|
||||
},
|
||||
function addWebRoute(callback) {
|
||||
self.webServer = getServer(webServerPackageName);
|
||||
if(!self.webServer) {
|
||||
return callback(Errors.DoesNotExist(`Server with package name "${webServerPackageName}" does not exist`));
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function initFromDb(callback) {
|
||||
return self.load(callback);
|
||||
},
|
||||
function addWebRoute(callback) {
|
||||
self.webServer = getServer(webServerPackageName);
|
||||
if(!self.webServer) {
|
||||
return callback(Errors.DoesNotExist(`Server with package name "${webServerPackageName}" does not exist`));
|
||||
}
|
||||
|
||||
if(self.isEnabled()) {
|
||||
const routeAdded = self.webServer.instance.addRoute({
|
||||
method : 'GET',
|
||||
path : Config().fileBase.web.routePath,
|
||||
handler : self.routeWebRequest.bind(self),
|
||||
});
|
||||
return callback(routeAdded ? null : Errors.General('Failed adding route'));
|
||||
} else {
|
||||
return callback(null); // not enabled, but no error
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
if(self.isEnabled()) {
|
||||
const routeAdded = self.webServer.instance.addRoute({
|
||||
method : 'GET',
|
||||
path : Config().fileBase.web.routePath,
|
||||
handler : self.routeWebRequest.bind(self),
|
||||
});
|
||||
return callback(routeAdded ? null : Errors.General('Failed adding route'));
|
||||
} else {
|
||||
return callback(null); // not enabled, but no error
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
shutdown(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
shutdown(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.webServer.instance.isEnabled();
|
||||
}
|
||||
isEnabled() {
|
||||
return this.webServer.instance.isEnabled();
|
||||
}
|
||||
|
||||
static getHashIdTypes() {
|
||||
return {
|
||||
SingleFile : 0,
|
||||
BatchArchive : 1,
|
||||
};
|
||||
}
|
||||
static getHashIdTypes() {
|
||||
return {
|
||||
SingleFile : 0,
|
||||
BatchArchive : 1,
|
||||
};
|
||||
}
|
||||
|
||||
load(cb) {
|
||||
//
|
||||
// Load entries, register expiration timers
|
||||
//
|
||||
FileDb.each(
|
||||
`SELECT hash_id, expire_timestamp
|
||||
load(cb) {
|
||||
//
|
||||
// Load entries, register expiration timers
|
||||
//
|
||||
FileDb.each(
|
||||
`SELECT hash_id, expire_timestamp
|
||||
FROM file_web_serve;`,
|
||||
(err, row) => {
|
||||
if(row) {
|
||||
this.scheduleExpire(row.hash_id, moment(row.expire_timestamp));
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
(err, row) => {
|
||||
if(row) {
|
||||
this.scheduleExpire(row.hash_id, moment(row.expire_timestamp));
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
removeEntry(hashId) {
|
||||
//
|
||||
// Delete record from DB, and our timer
|
||||
//
|
||||
FileDb.run(
|
||||
`DELETE FROM file_web_serve
|
||||
removeEntry(hashId) {
|
||||
//
|
||||
// Delete record from DB, and our timer
|
||||
//
|
||||
FileDb.run(
|
||||
`DELETE FROM file_web_serve
|
||||
WHERE hash_id = ?;`,
|
||||
[ hashId ]
|
||||
);
|
||||
[ hashId ]
|
||||
);
|
||||
|
||||
delete this.expireTimers[hashId];
|
||||
}
|
||||
delete this.expireTimers[hashId];
|
||||
}
|
||||
|
||||
scheduleExpire(hashId, expireTime) {
|
||||
scheduleExpire(hashId, expireTime) {
|
||||
|
||||
// remove any previous entry for this hashId
|
||||
const previous = this.expireTimers[hashId];
|
||||
if(previous) {
|
||||
clearTimeout(previous);
|
||||
delete this.expireTimers[hashId];
|
||||
}
|
||||
// remove any previous entry for this hashId
|
||||
const previous = this.expireTimers[hashId];
|
||||
if(previous) {
|
||||
clearTimeout(previous);
|
||||
delete this.expireTimers[hashId];
|
||||
}
|
||||
|
||||
const timeoutMs = expireTime.diff(moment());
|
||||
const timeoutMs = expireTime.diff(moment());
|
||||
|
||||
if(timeoutMs <= 0) {
|
||||
setImmediate( () => {
|
||||
this.removeEntry(hashId);
|
||||
});
|
||||
} else {
|
||||
this.expireTimers[hashId] = setTimeout( () => {
|
||||
this.removeEntry(hashId);
|
||||
}, timeoutMs);
|
||||
}
|
||||
}
|
||||
if(timeoutMs <= 0) {
|
||||
setImmediate( () => {
|
||||
this.removeEntry(hashId);
|
||||
});
|
||||
} else {
|
||||
this.expireTimers[hashId] = setTimeout( () => {
|
||||
this.removeEntry(hashId);
|
||||
}, timeoutMs);
|
||||
}
|
||||
}
|
||||
|
||||
loadServedHashId(hashId, cb) {
|
||||
FileDb.get(
|
||||
`SELECT expire_timestamp FROM
|
||||
loadServedHashId(hashId, cb) {
|
||||
FileDb.get(
|
||||
`SELECT expire_timestamp FROM
|
||||
file_web_serve
|
||||
WHERE hash_id = ?`,
|
||||
[ hashId ],
|
||||
(err, result) => {
|
||||
if(err || !result) {
|
||||
return cb(err ? err : Errors.DoesNotExist('Invalid or missing hash ID'));
|
||||
}
|
||||
[ hashId ],
|
||||
(err, result) => {
|
||||
if(err || !result) {
|
||||
return cb(err ? err : Errors.DoesNotExist('Invalid or missing hash ID'));
|
||||
}
|
||||
|
||||
const decoded = this.hashids.decode(hashId);
|
||||
const decoded = this.hashids.decode(hashId);
|
||||
|
||||
// decode() should provide an array of [ userId, hashIdType, id, ... ]
|
||||
if(!Array.isArray(decoded) || decoded.length < 3) {
|
||||
return cb(Errors.Invalid('Invalid or unknown hash ID'));
|
||||
}
|
||||
// decode() should provide an array of [ userId, hashIdType, id, ... ]
|
||||
if(!Array.isArray(decoded) || decoded.length < 3) {
|
||||
return cb(Errors.Invalid('Invalid or unknown hash ID'));
|
||||
}
|
||||
|
||||
const servedItem = {
|
||||
hashId : hashId,
|
||||
userId : decoded[0],
|
||||
hashIdType : decoded[1],
|
||||
expireTimestamp : moment(result.expire_timestamp),
|
||||
};
|
||||
const servedItem = {
|
||||
hashId : hashId,
|
||||
userId : decoded[0],
|
||||
hashIdType : decoded[1],
|
||||
expireTimestamp : moment(result.expire_timestamp),
|
||||
};
|
||||
|
||||
if(FileAreaWebAccess.getHashIdTypes().SingleFile === servedItem.hashIdType) {
|
||||
servedItem.fileIds = decoded.slice(2);
|
||||
}
|
||||
if(FileAreaWebAccess.getHashIdTypes().SingleFile === servedItem.hashIdType) {
|
||||
servedItem.fileIds = decoded.slice(2);
|
||||
}
|
||||
|
||||
return cb(null, servedItem);
|
||||
}
|
||||
);
|
||||
}
|
||||
return cb(null, servedItem);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSingleFileHashId(client, fileEntry) {
|
||||
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [ fileEntry.fileId ] );
|
||||
}
|
||||
getSingleFileHashId(client, fileEntry) {
|
||||
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [ fileEntry.fileId ] );
|
||||
}
|
||||
|
||||
getBatchArchiveHashId(client, batchId) {
|
||||
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().BatchArchive, batchId);
|
||||
}
|
||||
getBatchArchiveHashId(client, batchId) {
|
||||
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().BatchArchive, batchId);
|
||||
}
|
||||
|
||||
getHashId(client, hashIdType, identifier) {
|
||||
return this.hashids.encode(client.user.userId, hashIdType, identifier);
|
||||
}
|
||||
getHashId(client, hashIdType, identifier) {
|
||||
return this.hashids.encode(client.user.userId, hashIdType, identifier);
|
||||
}
|
||||
|
||||
buildSingleFileTempDownloadLink(client, fileEntry, hashId) {
|
||||
hashId = hashId || this.getSingleFileHashId(client, fileEntry);
|
||||
buildSingleFileTempDownloadLink(client, fileEntry, hashId) {
|
||||
hashId = hashId || this.getSingleFileHashId(client, fileEntry);
|
||||
|
||||
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
||||
}
|
||||
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
||||
}
|
||||
|
||||
buildBatchArchiveTempDownloadLink(client, hashId) {
|
||||
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
||||
}
|
||||
buildBatchArchiveTempDownloadLink(client, hashId) {
|
||||
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
||||
}
|
||||
|
||||
getExistingTempDownloadServeItem(client, fileEntry, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
getExistingTempDownloadServeItem(client, fileEntry, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
|
||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
servedItem.url = this.buildSingleFileTempDownloadLink(client, fileEntry);
|
||||
servedItem.url = this.buildSingleFileTempDownloadLink(client, fileEntry);
|
||||
|
||||
return cb(null, servedItem);
|
||||
});
|
||||
}
|
||||
return cb(null, servedItem);
|
||||
});
|
||||
}
|
||||
|
||||
_addOrUpdateHashIdRecord(dbOrTrans, hashId, expireTime, cb) {
|
||||
// add/update rec with hash id and (latest) timestamp
|
||||
dbOrTrans.run(
|
||||
`REPLACE INTO file_web_serve (hash_id, expire_timestamp)
|
||||
_addOrUpdateHashIdRecord(dbOrTrans, hashId, expireTime, cb) {
|
||||
// add/update rec with hash id and (latest) timestamp
|
||||
dbOrTrans.run(
|
||||
`REPLACE INTO file_web_serve (hash_id, expire_timestamp)
|
||||
VALUES (?, ?);`,
|
||||
[ hashId, getISOTimestampString(expireTime) ],
|
||||
err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
[ hashId, getISOTimestampString(expireTime) ],
|
||||
err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.scheduleExpire(hashId, expireTime);
|
||||
this.scheduleExpire(hashId, expireTime);
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
createAndServeTempDownload(client, fileEntry, options, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
createAndServeTempDownload(client, fileEntry, options, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
|
||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||
const url = this.buildSingleFileTempDownloadLink(client, fileEntry, hashId);
|
||||
options.expireTime = options.expireTime || moment().add(2, 'days');
|
||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||
const url = this.buildSingleFileTempDownloadLink(client, fileEntry, hashId);
|
||||
options.expireTime = options.expireTime || moment().add(2, 'days');
|
||||
|
||||
this._addOrUpdateHashIdRecord(FileDb, hashId, options.expireTime, err => {
|
||||
return cb(err, url);
|
||||
});
|
||||
}
|
||||
this._addOrUpdateHashIdRecord(FileDb, hashId, options.expireTime, err => {
|
||||
return cb(err, url);
|
||||
});
|
||||
}
|
||||
|
||||
createAndServeTempBatchDownload(client, fileEntries, options, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
createAndServeTempBatchDownload(client, fileEntries, options, cb) {
|
||||
if(!this.isEnabled()) {
|
||||
return cb(notEnabledError());
|
||||
}
|
||||
|
||||
const batchId = moment().utc().unix();
|
||||
const hashId = this.getBatchArchiveHashId(client, batchId);
|
||||
const url = this.buildBatchArchiveTempDownloadLink(client, hashId);
|
||||
options.expireTime = options.expireTime || moment().add(2, 'days');
|
||||
const batchId = moment().utc().unix();
|
||||
const hashId = this.getBatchArchiveHashId(client, batchId);
|
||||
const url = this.buildBatchArchiveTempDownloadLink(client, hashId);
|
||||
options.expireTime = options.expireTime || moment().add(2, 'days');
|
||||
|
||||
FileDb.beginTransaction( (err, trans) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
FileDb.beginTransaction( (err, trans) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this._addOrUpdateHashIdRecord(trans, hashId, options.expireTime, err => {
|
||||
if(err) {
|
||||
return trans.rollback( () => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
this._addOrUpdateHashIdRecord(trans, hashId, options.expireTime, err => {
|
||||
if(err) {
|
||||
return trans.rollback( () => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
async.eachSeries(fileEntries, (entry, nextEntry) => {
|
||||
trans.run(
|
||||
`INSERT INTO file_web_serve_batch (hash_id, file_id)
|
||||
async.eachSeries(fileEntries, (entry, nextEntry) => {
|
||||
trans.run(
|
||||
`INSERT INTO file_web_serve_batch (hash_id, file_id)
|
||||
VALUES (?, ?);`,
|
||||
[ hashId, entry.fileId ],
|
||||
err => {
|
||||
return nextEntry(err);
|
||||
}
|
||||
);
|
||||
}, err => {
|
||||
trans[err ? 'rollback' : 'commit']( () => {
|
||||
return cb(err, url);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
[ hashId, entry.fileId ],
|
||||
err => {
|
||||
return nextEntry(err);
|
||||
}
|
||||
);
|
||||
}, err => {
|
||||
trans[err ? 'rollback' : 'commit']( () => {
|
||||
return cb(err, url);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fileNotFound(resp) {
|
||||
return this.webServer.instance.fileNotFound(resp);
|
||||
}
|
||||
fileNotFound(resp) {
|
||||
return this.webServer.instance.fileNotFound(resp);
|
||||
}
|
||||
|
||||
routeWebRequest(req, resp) {
|
||||
const hashId = paths.basename(req.url);
|
||||
routeWebRequest(req, resp) {
|
||||
const hashId = paths.basename(req.url);
|
||||
|
||||
Log.debug( { hashId : hashId, url : req.url }, 'File area web request');
|
||||
Log.debug( { hashId : hashId, url : req.url }, 'File area web request');
|
||||
|
||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||
|
||||
if(err) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
if(err) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
|
||||
const hashIdTypes = FileAreaWebAccess.getHashIdTypes();
|
||||
switch(servedItem.hashIdType) {
|
||||
case hashIdTypes.SingleFile :
|
||||
return this.routeWebRequestForSingleFile(servedItem, req, resp);
|
||||
const hashIdTypes = FileAreaWebAccess.getHashIdTypes();
|
||||
switch(servedItem.hashIdType) {
|
||||
case hashIdTypes.SingleFile :
|
||||
return this.routeWebRequestForSingleFile(servedItem, req, resp);
|
||||
|
||||
case hashIdTypes.BatchArchive :
|
||||
return this.routeWebRequestForBatchArchive(servedItem, req, resp);
|
||||
case hashIdTypes.BatchArchive :
|
||||
return this.routeWebRequestForBatchArchive(servedItem, req, resp);
|
||||
|
||||
default :
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
});
|
||||
}
|
||||
default :
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
routeWebRequestForSingleFile(servedItem, req, resp) {
|
||||
Log.debug( { servedItem : servedItem }, 'Single file web request');
|
||||
routeWebRequestForSingleFile(servedItem, req, resp) {
|
||||
Log.debug( { servedItem : servedItem }, 'Single file web request');
|
||||
|
||||
const fileEntry = new FileEntry();
|
||||
const fileEntry = new FileEntry();
|
||||
|
||||
servedItem.fileId = servedItem.fileIds[0];
|
||||
servedItem.fileId = servedItem.fileIds[0];
|
||||
|
||||
fileEntry.load(servedItem.fileId, err => {
|
||||
if(err) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
fileEntry.load(servedItem.fileId, err => {
|
||||
if(err) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
|
||||
const filePath = fileEntry.filePath;
|
||||
if(!filePath) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
const filePath = fileEntry.filePath;
|
||||
if(!filePath) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err) {
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
|
||||
resp.on('close', () => {
|
||||
// connection closed *before* the response was fully sent
|
||||
// :TODO: Log and such
|
||||
});
|
||||
resp.on('close', () => {
|
||||
// connection closed *before* the response was fully sent
|
||||
// :TODO: Log and such
|
||||
});
|
||||
|
||||
resp.on('finish', () => {
|
||||
// transfer completed fully
|
||||
this.updateDownloadStatsForUserIdAndSystem(servedItem.userId, stats.size, [ fileEntry ]);
|
||||
});
|
||||
resp.on('finish', () => {
|
||||
// transfer completed fully
|
||||
this.updateDownloadStatsForUserIdAndSystem(servedItem.userId, stats.size, [ fileEntry ]);
|
||||
});
|
||||
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : stats.size,
|
||||
'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`,
|
||||
};
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : stats.size,
|
||||
'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`,
|
||||
};
|
||||
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
resp.writeHead(200, headers);
|
||||
return readStream.pipe(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
const readStream = fs.createReadStream(filePath);
|
||||
resp.writeHead(200, headers);
|
||||
return readStream.pipe(resp);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
routeWebRequestForBatchArchive(servedItem, req, resp) {
|
||||
Log.debug( { servedItem : servedItem }, 'Batch file web request');
|
||||
routeWebRequestForBatchArchive(servedItem, req, resp) {
|
||||
Log.debug( { servedItem : servedItem }, 'Batch file web request');
|
||||
|
||||
//
|
||||
// We are going to build an on-the-fly zip file stream of 1:n
|
||||
// files in the batch.
|
||||
//
|
||||
// First, collect all file IDs
|
||||
//
|
||||
const self = this;
|
||||
//
|
||||
// We are going to build an on-the-fly zip file stream of 1:n
|
||||
// files in the batch.
|
||||
//
|
||||
// First, collect all file IDs
|
||||
//
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function fetchFileIds(callback) {
|
||||
FileDb.all(
|
||||
`SELECT file_id
|
||||
async.waterfall(
|
||||
[
|
||||
function fetchFileIds(callback) {
|
||||
FileDb.all(
|
||||
`SELECT file_id
|
||||
FROM file_web_serve_batch
|
||||
WHERE hash_id = ?;`,
|
||||
[ servedItem.hashId ],
|
||||
(err, fileIdRows) => {
|
||||
if(err || !Array.isArray(fileIdRows) || 0 === fileIdRows.length) {
|
||||
return callback(Errors.DoesNotExist('Could not get file IDs for batch'));
|
||||
}
|
||||
[ servedItem.hashId ],
|
||||
(err, fileIdRows) => {
|
||||
if(err || !Array.isArray(fileIdRows) || 0 === fileIdRows.length) {
|
||||
return callback(Errors.DoesNotExist('Could not get file IDs for batch'));
|
||||
}
|
||||
|
||||
return callback(null, fileIdRows.map(r => r.file_id));
|
||||
}
|
||||
);
|
||||
},
|
||||
function loadFileEntries(fileIds, callback) {
|
||||
async.map(fileIds, (fileId, nextFileId) => {
|
||||
const fileEntry = new FileEntry();
|
||||
fileEntry.load(fileId, err => {
|
||||
return nextFileId(err, fileEntry);
|
||||
});
|
||||
}, (err, fileEntries) => {
|
||||
if(err) {
|
||||
return callback(Errors.DoesNotExist('Could not load file IDs for batch'));
|
||||
}
|
||||
return callback(null, fileIdRows.map(r => r.file_id));
|
||||
}
|
||||
);
|
||||
},
|
||||
function loadFileEntries(fileIds, callback) {
|
||||
async.map(fileIds, (fileId, nextFileId) => {
|
||||
const fileEntry = new FileEntry();
|
||||
fileEntry.load(fileId, err => {
|
||||
return nextFileId(err, fileEntry);
|
||||
});
|
||||
}, (err, fileEntries) => {
|
||||
if(err) {
|
||||
return callback(Errors.DoesNotExist('Could not load file IDs for batch'));
|
||||
}
|
||||
|
||||
return callback(null, fileEntries);
|
||||
});
|
||||
},
|
||||
function createAndServeStream(fileEntries, callback) {
|
||||
const filePaths = fileEntries.map(fe => fe.filePath);
|
||||
Log.trace( { filePaths : filePaths }, 'Creating zip archive for batch web request');
|
||||
return callback(null, fileEntries);
|
||||
});
|
||||
},
|
||||
function createAndServeStream(fileEntries, callback) {
|
||||
const filePaths = fileEntries.map(fe => fe.filePath);
|
||||
Log.trace( { filePaths : filePaths }, 'Creating zip archive for batch web request');
|
||||
|
||||
const zipFile = new yazl.ZipFile();
|
||||
const zipFile = new yazl.ZipFile();
|
||||
|
||||
zipFile.on('error', err => {
|
||||
Log.warn( { error : err.message }, 'Error adding file to batch web request archive');
|
||||
});
|
||||
zipFile.on('error', err => {
|
||||
Log.warn( { error : err.message }, 'Error adding file to batch web request archive');
|
||||
});
|
||||
|
||||
filePaths.forEach(fp => {
|
||||
zipFile.addFile(
|
||||
fp, // path to physical file
|
||||
paths.basename(fp), // filename/path *stored in archive*
|
||||
{
|
||||
compress : false, // :TODO: do this smartly - if ext is in set = false, else true via isArchive() or such... mimeDB has this for us.
|
||||
}
|
||||
);
|
||||
});
|
||||
filePaths.forEach(fp => {
|
||||
zipFile.addFile(
|
||||
fp, // path to physical file
|
||||
paths.basename(fp), // filename/path *stored in archive*
|
||||
{
|
||||
compress : false, // :TODO: do this smartly - if ext is in set = false, else true via isArchive() or such... mimeDB has this for us.
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
zipFile.end( finalZipSize => {
|
||||
if(-1 === finalZipSize) {
|
||||
return callback(Errors.UnexpectedState('Unable to acquire final zip size'));
|
||||
}
|
||||
zipFile.end( finalZipSize => {
|
||||
if(-1 === finalZipSize) {
|
||||
return callback(Errors.UnexpectedState('Unable to acquire final zip size'));
|
||||
}
|
||||
|
||||
resp.on('close', () => {
|
||||
// connection closed *before* the response was fully sent
|
||||
// :TODO: Log and such
|
||||
});
|
||||
resp.on('close', () => {
|
||||
// connection closed *before* the response was fully sent
|
||||
// :TODO: Log and such
|
||||
});
|
||||
|
||||
resp.on('finish', () => {
|
||||
// transfer completed fully
|
||||
self.updateDownloadStatsForUserIdAndSystem(servedItem.userId, finalZipSize, fileEntries);
|
||||
});
|
||||
resp.on('finish', () => {
|
||||
// transfer completed fully
|
||||
self.updateDownloadStatsForUserIdAndSystem(servedItem.userId, finalZipSize, fileEntries);
|
||||
});
|
||||
|
||||
const batchFileName = `batch_${servedItem.hashId}.zip`;
|
||||
const batchFileName = `batch_${servedItem.hashId}.zip`;
|
||||
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(batchFileName) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : finalZipSize,
|
||||
'Content-Disposition' : `attachment; filename="${batchFileName}"`,
|
||||
};
|
||||
const headers = {
|
||||
'Content-Type' : mimeTypes.contentType(batchFileName) || mimeTypes.contentType('.bin'),
|
||||
'Content-Length' : finalZipSize,
|
||||
'Content-Disposition' : `attachment; filename="${batchFileName}"`,
|
||||
};
|
||||
|
||||
resp.writeHead(200, headers);
|
||||
return zipFile.outputStream.pipe(resp);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Log me!
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
resp.writeHead(200, headers);
|
||||
return zipFile.outputStream.pipe(resp);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Log me!
|
||||
return this.fileNotFound(resp);
|
||||
}
|
||||
|
||||
// ...otherwise, we would have called resp() already.
|
||||
}
|
||||
);
|
||||
}
|
||||
// ...otherwise, we would have called resp() already.
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) {
|
||||
async.waterfall(
|
||||
[
|
||||
function fetchActiveUser(callback) {
|
||||
const clientForUserId = getConnectionByUserId(userId);
|
||||
if(clientForUserId) {
|
||||
return callback(null, clientForUserId.user);
|
||||
}
|
||||
updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) {
|
||||
async.waterfall(
|
||||
[
|
||||
function fetchActiveUser(callback) {
|
||||
const clientForUserId = getConnectionByUserId(userId);
|
||||
if(clientForUserId) {
|
||||
return callback(null, clientForUserId.user);
|
||||
}
|
||||
|
||||
// not online now - look 'em up
|
||||
User.getUser(userId, (err, assocUser) => {
|
||||
return callback(err, assocUser);
|
||||
});
|
||||
},
|
||||
function updateStats(user, callback) {
|
||||
StatLog.incrementUserStat(user, 'dl_total_count', 1);
|
||||
StatLog.incrementUserStat(user, 'dl_total_bytes', dlBytes);
|
||||
StatLog.incrementSystemStat('dl_total_count', 1);
|
||||
StatLog.incrementSystemStat('dl_total_bytes', dlBytes);
|
||||
// not online now - look 'em up
|
||||
User.getUser(userId, (err, assocUser) => {
|
||||
return callback(err, assocUser);
|
||||
});
|
||||
},
|
||||
function updateStats(user, callback) {
|
||||
StatLog.incrementUserStat(user, 'dl_total_count', 1);
|
||||
StatLog.incrementUserStat(user, 'dl_total_bytes', dlBytes);
|
||||
StatLog.incrementSystemStat('dl_total_count', 1);
|
||||
StatLog.incrementSystemStat('dl_total_bytes', dlBytes);
|
||||
|
||||
return callback(null, user);
|
||||
},
|
||||
function sendEvent(user, callback) {
|
||||
Events.emit(
|
||||
Events.getSystemEvents().UserDownload,
|
||||
{
|
||||
user : user,
|
||||
files : fileEntries,
|
||||
}
|
||||
);
|
||||
return callback(null);
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
return callback(null, user);
|
||||
},
|
||||
function sendEvent(user, callback) {
|
||||
Events.emit(
|
||||
Events.getSystemEvents().UserDownload,
|
||||
{
|
||||
user : user,
|
||||
files : fileEntries,
|
||||
}
|
||||
);
|
||||
return callback(null);
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FileAreaWebAccess();
|
File diff suppressed because it is too large
Load Diff
|
@ -10,78 +10,78 @@ const StatLog = require('./stat_log.js');
|
|||
const async = require('async');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Area Selector',
|
||||
desc : 'Select from available file areas',
|
||||
author : 'NuSkooler',
|
||||
name : 'File Area Selector',
|
||||
desc : 'Select from available file areas',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
areaList : 1,
|
||||
areaList : 1,
|
||||
};
|
||||
|
||||
exports.getModule = class FileAreaSelectModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.menuMethods = {
|
||||
selectArea : (formData, extraArgs, cb) => {
|
||||
const filterCriteria = {
|
||||
areaTag : formData.value.areaTag,
|
||||
};
|
||||
this.menuMethods = {
|
||||
selectArea : (formData, extraArgs, cb) => {
|
||||
const filterCriteria = {
|
||||
areaTag : formData.value.areaTag,
|
||||
};
|
||||
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
filterCriteria : filterCriteria,
|
||||
},
|
||||
menuFlags : [ 'popParent', 'mergeFlags' ],
|
||||
};
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
filterCriteria : filterCriteria,
|
||||
},
|
||||
menuFlags : [ 'popParent', 'mergeFlags' ],
|
||||
};
|
||||
|
||||
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function mergeAreaStats(callback) {
|
||||
const areaStats = StatLog.getSystemStat('file_base_area_stats') || { areas : {} };
|
||||
async.waterfall(
|
||||
[
|
||||
function mergeAreaStats(callback) {
|
||||
const areaStats = StatLog.getSystemStat('file_base_area_stats') || { areas : {} };
|
||||
|
||||
// we could use 'sort' alone, but area/conf sorting has some special properties; user can still override
|
||||
const availAreas = getSortedAvailableFileAreas(self.client);
|
||||
availAreas.forEach(area => {
|
||||
const stats = areaStats.areas[area.areaTag];
|
||||
area.totalFiles = stats ? stats.files : 0;
|
||||
area.totalBytes = stats ? stats.bytes : 0;
|
||||
});
|
||||
// we could use 'sort' alone, but area/conf sorting has some special properties; user can still override
|
||||
const availAreas = getSortedAvailableFileAreas(self.client);
|
||||
availAreas.forEach(area => {
|
||||
const stats = areaStats.areas[area.areaTag];
|
||||
area.totalFiles = stats ? stats.files : 0;
|
||||
area.totalBytes = stats ? stats.bytes : 0;
|
||||
});
|
||||
|
||||
return callback(null, availAreas);
|
||||
},
|
||||
function prepView(availAreas, callback) {
|
||||
self.prepViewController('allViews', 0, mciData.menu, (err, vc) => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, availAreas);
|
||||
},
|
||||
function prepView(availAreas, callback) {
|
||||
self.prepViewController('allViews', 0, mciData.menu, (err, vc) => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const areaListView = vc.getView(MciViewIds.areaList);
|
||||
areaListView.setItems(availAreas.map(area => Object.assign(area, { text : area.name, data : area.areaTag } )));
|
||||
areaListView.redraw();
|
||||
const areaListView = vc.getView(MciViewIds.areaList);
|
||||
areaListView.setItems(availAreas.map(area => Object.assign(area, { text : area.name, data : area.areaTag } )));
|
||||
areaListView.redraw();
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
return callback(null);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,226 +17,226 @@ const _ = require('lodash');
|
|||
const moment = require('moment');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Base Download Queue Manager',
|
||||
desc : 'Module for interacting with download queue/batch',
|
||||
author : 'NuSkooler',
|
||||
name : 'File Base Download Queue Manager',
|
||||
desc : 'Module for interacting with download queue/batch',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
queueManager : 0,
|
||||
queueManager : 0,
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
queueManager : {
|
||||
queue : 1,
|
||||
navMenu : 2,
|
||||
queueManager : {
|
||||
queue : 1,
|
||||
navMenu : 2,
|
||||
|
||||
customRangeStart : 10,
|
||||
},
|
||||
customRangeStart : 10,
|
||||
},
|
||||
};
|
||||
|
||||
exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.dlQueue = new DownloadQueue(this.client);
|
||||
this.dlQueue = new DownloadQueue(this.client);
|
||||
|
||||
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
||||
}
|
||||
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
||||
}
|
||||
|
||||
this.fallbackOnly = options.lastMenuResult ? true : false;
|
||||
this.fallbackOnly = options.lastMenuResult ? true : false;
|
||||
|
||||
this.menuMethods = {
|
||||
downloadAll : (formData, extraArgs, cb) => {
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
sendQueue : this.dlQueue.items,
|
||||
direction : 'send',
|
||||
}
|
||||
};
|
||||
this.menuMethods = {
|
||||
downloadAll : (formData, extraArgs, cb) => {
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
sendQueue : this.dlQueue.items,
|
||||
direction : 'send',
|
||||
}
|
||||
};
|
||||
|
||||
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
|
||||
},
|
||||
removeItem : (formData, extraArgs, cb) => {
|
||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||
if(!selectedItem) {
|
||||
return cb(null);
|
||||
}
|
||||
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
|
||||
},
|
||||
removeItem : (formData, extraArgs, cb) => {
|
||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||
if(!selectedItem) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
this.dlQueue.removeItems(selectedItem.fileId);
|
||||
this.dlQueue.removeItems(selectedItem.fileId);
|
||||
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
||||
},
|
||||
clearQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.clear();
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
||||
},
|
||||
clearQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.clear();
|
||||
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
if(0 === this.dlQueue.items.length) {
|
||||
if(this.sendFileIds) {
|
||||
// we've finished everything up - just fall back
|
||||
return this.prevMenu();
|
||||
}
|
||||
initSequence() {
|
||||
if(0 === this.dlQueue.items.length) {
|
||||
if(this.sendFileIds) {
|
||||
// we've finished everything up - just fall back
|
||||
return this.prevMenu();
|
||||
}
|
||||
|
||||
// Simply an empty D/L queue: Present a specialized "empty queue" page
|
||||
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
||||
}
|
||||
// Simply an empty D/L queue: Present a specialized "empty queue" page
|
||||
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function beforeArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayQueueManagerPage(false, callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
return self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function beforeArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayQueueManagerPage(false, callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
return self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
|
||||
if('all' === itemIndex) {
|
||||
queueView.setItems([]);
|
||||
queueView.setFocusItems([]);
|
||||
} else {
|
||||
queueView.removeItem(itemIndex);
|
||||
}
|
||||
if('all' === itemIndex) {
|
||||
queueView.setItems([]);
|
||||
queueView.setFocusItems([]);
|
||||
} else {
|
||||
queueView.removeItem(itemIndex);
|
||||
}
|
||||
|
||||
queueView.redraw();
|
||||
return cb(null);
|
||||
}
|
||||
queueView.redraw();
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
displayWebDownloadLinkForFileEntry(fileEntry) {
|
||||
FileAreaWeb.getExistingTempDownloadServeItem(this.client, fileEntry, (err, serveItem) => {
|
||||
if(serveItem && serveItem.url) {
|
||||
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
displayWebDownloadLinkForFileEntry(fileEntry) {
|
||||
FileAreaWeb.getExistingTempDownloadServeItem(this.client, fileEntry, (err, serveItem) => {
|
||||
if(serveItem && serveItem.url) {
|
||||
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
|
||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||
} else {
|
||||
fileEntry.webDlLink = '';
|
||||
fileEntry.webDlExpire = '';
|
||||
}
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
|
||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||
} else {
|
||||
fileEntry.webDlLink = '';
|
||||
fileEntry.webDlExpire = '';
|
||||
}
|
||||
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||
{ filter : [ '{webDlLink}', '{webDlExpire}' ] }
|
||||
);
|
||||
});
|
||||
}
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||
{ filter : [ '{webDlLink}', '{webDlExpire}' ] }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
updateDownloadQueueView(cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
updateDownloadQueueView(cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
|
||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{fileName} {byteSize}';
|
||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{fileName} {byteSize}';
|
||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||
|
||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||
|
||||
queueView.on('index update', idx => {
|
||||
const fileEntry = this.dlQueue.items[idx];
|
||||
this.displayWebDownloadLinkForFileEntry(fileEntry);
|
||||
});
|
||||
queueView.on('index update', idx => {
|
||||
const fileEntry = this.dlQueue.items[idx];
|
||||
this.displayWebDownloadLinkForFileEntry(fileEntry);
|
||||
});
|
||||
|
||||
queueView.redraw();
|
||||
this.displayWebDownloadLinkForFileEntry(this.dlQueue.items[0]);
|
||||
queueView.redraw();
|
||||
this.displayWebDownloadLinkForFileEntry(this.dlQueue.items[0]);
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
displayQueueManagerPage(clearScreen, cb) {
|
||||
const self = this;
|
||||
displayQueueManagerPage(clearScreen, cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||
},
|
||||
function populateViews(callback) {
|
||||
return self.updateDownloadQueueView(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||
},
|
||||
function populateViews(callback) {
|
||||
return self.updateDownloadQueueView(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayArtAndPrepViewController(name, options, cb) {
|
||||
const self = this;
|
||||
const config = this.menuConfig.config;
|
||||
displayArtAndPrepViewController(name, options, cb) {
|
||||
const self = this;
|
||||
const config = this.menuConfig.config;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function readyAndDisplayArt(callback) {
|
||||
if(options.clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
async.waterfall(
|
||||
[
|
||||
function readyAndDisplayArt(callback) {
|
||||
if(options.clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
theme.displayThemedAsset(
|
||||
config.art[name],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function prepeareViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers[name])) {
|
||||
const vcOpts = {
|
||||
client : self.client,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
theme.displayThemedAsset(
|
||||
config.art[name],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function prepeareViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers[name])) {
|
||||
const vcOpts = {
|
||||
client : self.client,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
|
||||
if(!_.isUndefined(options.noInput)) {
|
||||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
if(!_.isUndefined(options.noInput)) {
|
||||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6,150 +6,150 @@ const _ = require('lodash');
|
|||
const uuidV4 = require('uuid/v4');
|
||||
|
||||
module.exports = class FileBaseFilters {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
|
||||
this.load();
|
||||
}
|
||||
this.load();
|
||||
}
|
||||
|
||||
static get OrderByValues() {
|
||||
return [ 'descending', 'ascending' ];
|
||||
}
|
||||
static get OrderByValues() {
|
||||
return [ 'descending', 'ascending' ];
|
||||
}
|
||||
|
||||
static get SortByValues() {
|
||||
return [
|
||||
'upload_timestamp',
|
||||
'upload_by_username',
|
||||
'dl_count',
|
||||
'user_rating',
|
||||
'est_release_year',
|
||||
'byte_size',
|
||||
'file_name',
|
||||
];
|
||||
}
|
||||
static get SortByValues() {
|
||||
return [
|
||||
'upload_timestamp',
|
||||
'upload_by_username',
|
||||
'dl_count',
|
||||
'user_rating',
|
||||
'est_release_year',
|
||||
'byte_size',
|
||||
'file_name',
|
||||
];
|
||||
}
|
||||
|
||||
toArray() {
|
||||
return _.map(this.filters, (filter, uuid) => {
|
||||
return Object.assign( { uuid : uuid }, filter );
|
||||
});
|
||||
}
|
||||
toArray() {
|
||||
return _.map(this.filters, (filter, uuid) => {
|
||||
return Object.assign( { uuid : uuid }, filter );
|
||||
});
|
||||
}
|
||||
|
||||
get(filterUuid) {
|
||||
return this.filters[filterUuid];
|
||||
}
|
||||
get(filterUuid) {
|
||||
return this.filters[filterUuid];
|
||||
}
|
||||
|
||||
add(filterInfo) {
|
||||
const filterUuid = uuidV4();
|
||||
add(filterInfo) {
|
||||
const filterUuid = uuidV4();
|
||||
|
||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||
|
||||
this.filters[filterUuid] = filterInfo;
|
||||
this.filters[filterUuid] = filterInfo;
|
||||
|
||||
return filterUuid;
|
||||
}
|
||||
return filterUuid;
|
||||
}
|
||||
|
||||
replace(filterUuid, filterInfo) {
|
||||
const filter = this.get(filterUuid);
|
||||
if(!filter) {
|
||||
return false;
|
||||
}
|
||||
replace(filterUuid, filterInfo) {
|
||||
const filter = this.get(filterUuid);
|
||||
if(!filter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||
this.filters[filterUuid] = filterInfo;
|
||||
return true;
|
||||
}
|
||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||
this.filters[filterUuid] = filterInfo;
|
||||
return true;
|
||||
}
|
||||
|
||||
remove(filterUuid) {
|
||||
delete this.filters[filterUuid];
|
||||
}
|
||||
remove(filterUuid) {
|
||||
delete this.filters[filterUuid];
|
||||
}
|
||||
|
||||
load() {
|
||||
let filtersProperty = this.client.user.properties.file_base_filters;
|
||||
let defaulted;
|
||||
if(!filtersProperty) {
|
||||
filtersProperty = JSON.stringify(FileBaseFilters.getBuiltInSystemFilters());
|
||||
defaulted = true;
|
||||
}
|
||||
load() {
|
||||
let filtersProperty = this.client.user.properties.file_base_filters;
|
||||
let defaulted;
|
||||
if(!filtersProperty) {
|
||||
filtersProperty = JSON.stringify(FileBaseFilters.getBuiltInSystemFilters());
|
||||
defaulted = true;
|
||||
}
|
||||
|
||||
try {
|
||||
this.filters = JSON.parse(filtersProperty);
|
||||
} catch(e) {
|
||||
this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :(
|
||||
defaulted = true;
|
||||
this.client.log.error( { error : e.message, property : filtersProperty }, 'Failed parsing file base filters property' );
|
||||
}
|
||||
try {
|
||||
this.filters = JSON.parse(filtersProperty);
|
||||
} catch(e) {
|
||||
this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :(
|
||||
defaulted = true;
|
||||
this.client.log.error( { error : e.message, property : filtersProperty }, 'Failed parsing file base filters property' );
|
||||
}
|
||||
|
||||
if(defaulted) {
|
||||
this.persist( err => {
|
||||
if(!err) {
|
||||
const defaultActiveUuid = this.toArray()[0].uuid;
|
||||
this.setActive(defaultActiveUuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if(defaulted) {
|
||||
this.persist( err => {
|
||||
if(!err) {
|
||||
const defaultActiveUuid = this.toArray()[0].uuid;
|
||||
this.setActive(defaultActiveUuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
persist(cb) {
|
||||
return this.client.user.persistProperty('file_base_filters', JSON.stringify(this.filters), cb);
|
||||
}
|
||||
persist(cb) {
|
||||
return this.client.user.persistProperty('file_base_filters', JSON.stringify(this.filters), cb);
|
||||
}
|
||||
|
||||
cleanTags(tags) {
|
||||
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim();
|
||||
}
|
||||
cleanTags(tags) {
|
||||
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim();
|
||||
}
|
||||
|
||||
setActive(filterUuid) {
|
||||
const activeFilter = this.get(filterUuid);
|
||||
setActive(filterUuid) {
|
||||
const activeFilter = this.get(filterUuid);
|
||||
|
||||
if(activeFilter) {
|
||||
this.activeFilter = activeFilter;
|
||||
this.client.user.persistProperty('file_base_filter_active_uuid', filterUuid);
|
||||
return true;
|
||||
}
|
||||
if(activeFilter) {
|
||||
this.activeFilter = activeFilter;
|
||||
this.client.user.persistProperty('file_base_filter_active_uuid', filterUuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static getBuiltInSystemFilters() {
|
||||
const U_LATEST = '7458b09d-40ab-4f9b-a0d7-0cf866646329';
|
||||
static getBuiltInSystemFilters() {
|
||||
const U_LATEST = '7458b09d-40ab-4f9b-a0d7-0cf866646329';
|
||||
|
||||
const filters = {
|
||||
[ U_LATEST ] : {
|
||||
name : 'By Date Added',
|
||||
areaTag : '', // all
|
||||
terms : '', // *
|
||||
tags : '', // *
|
||||
order : 'descending',
|
||||
sort : 'upload_timestamp',
|
||||
uuid : U_LATEST,
|
||||
system : true,
|
||||
}
|
||||
};
|
||||
const filters = {
|
||||
[ U_LATEST ] : {
|
||||
name : 'By Date Added',
|
||||
areaTag : '', // all
|
||||
terms : '', // *
|
||||
tags : '', // *
|
||||
order : 'descending',
|
||||
sort : 'upload_timestamp',
|
||||
uuid : U_LATEST,
|
||||
system : true,
|
||||
}
|
||||
};
|
||||
|
||||
return filters;
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
static getActiveFilter(client) {
|
||||
return new FileBaseFilters(client).get(client.user.properties.file_base_filter_active_uuid);
|
||||
}
|
||||
static getActiveFilter(client) {
|
||||
return new FileBaseFilters(client).get(client.user.properties.file_base_filter_active_uuid);
|
||||
}
|
||||
|
||||
static getFileBaseLastViewedFileIdByUser(user) {
|
||||
return parseInt((user.properties.user_file_base_last_viewed || 0));
|
||||
}
|
||||
static getFileBaseLastViewedFileIdByUser(user) {
|
||||
return parseInt((user.properties.user_file_base_last_viewed || 0));
|
||||
}
|
||||
|
||||
static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) {
|
||||
if(!cb && _.isFunction(allowOlder)) {
|
||||
cb = allowOlder;
|
||||
allowOlder = false;
|
||||
}
|
||||
static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) {
|
||||
if(!cb && _.isFunction(allowOlder)) {
|
||||
cb = allowOlder;
|
||||
allowOlder = false;
|
||||
}
|
||||
|
||||
const current = FileBaseFilters.getFileBaseLastViewedFileIdByUser(user);
|
||||
if(!allowOlder && fileId < current) {
|
||||
if(cb) {
|
||||
cb(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const current = FileBaseFilters.getFileBaseLastViewedFileIdByUser(user);
|
||||
if(!allowOlder && fileId < current) {
|
||||
if(cb) {
|
||||
cb(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return user.persistProperty('user_file_base_last_viewed', fileId, cb);
|
||||
}
|
||||
return user.persistProperty('user_file_base_last_viewed', fileId, cb);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,8 +8,8 @@ const FileArea = require('./file_base_area.js');
|
|||
const Config = require('./config.js').get;
|
||||
const { Errors } = require('./enig_error.js');
|
||||
const {
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
splitTextAtTerms,
|
||||
isAnsi,
|
||||
} = require('./string_util.js');
|
||||
const AnsiPrep = require('./ansi_prep.js');
|
||||
const Log = require('./logger.js').log;
|
||||
|
@ -26,276 +26,276 @@ exports.exportFileList = exportFileList;
|
|||
exports.updateFileBaseDescFilesScheduledEvent = updateFileBaseDescFilesScheduledEvent;
|
||||
|
||||
function exportFileList(filterCriteria, options, cb) {
|
||||
options.templateEncoding = options.templateEncoding || 'utf8';
|
||||
options.entryTemplate = options.entryTemplate || 'descript_ion_export_entry_template.asc';
|
||||
options.tsFormat = options.tsFormat || 'YYYY-MM-DD';
|
||||
options.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec
|
||||
options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc?
|
||||
options.templateEncoding = options.templateEncoding || 'utf8';
|
||||
options.entryTemplate = options.entryTemplate || 'descript_ion_export_entry_template.asc';
|
||||
options.tsFormat = options.tsFormat || 'YYYY-MM-DD';
|
||||
options.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec
|
||||
options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc?
|
||||
|
||||
if(true === options.escapeDesc) {
|
||||
options.escapeDesc = '\\n';
|
||||
}
|
||||
if(true === options.escapeDesc) {
|
||||
options.escapeDesc = '\\n';
|
||||
}
|
||||
|
||||
const state = {
|
||||
total : 0,
|
||||
current : 0,
|
||||
step : 'preparing',
|
||||
status : 'Preparing',
|
||||
};
|
||||
const state = {
|
||||
total : 0,
|
||||
current : 0,
|
||||
step : 'preparing',
|
||||
status : 'Preparing',
|
||||
};
|
||||
|
||||
const updateProgress = _.isFunction(options.progress) ?
|
||||
progCb => {
|
||||
return options.progress(state, progCb);
|
||||
} :
|
||||
progCb => {
|
||||
return progCb(null);
|
||||
}
|
||||
const updateProgress = _.isFunction(options.progress) ?
|
||||
progCb => {
|
||||
return options.progress(state, progCb);
|
||||
} :
|
||||
progCb => {
|
||||
return progCb(null);
|
||||
}
|
||||
;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function readTemplateFiles(callback) {
|
||||
updateProgress(err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
async.waterfall(
|
||||
[
|
||||
function readTemplateFiles(callback) {
|
||||
updateProgress(err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const templateFiles = [
|
||||
{ name : options.headerTemplate, req : false },
|
||||
{ name : options.entryTemplate, req : true }
|
||||
];
|
||||
const templateFiles = [
|
||||
{ name : options.headerTemplate, req : false },
|
||||
{ name : options.entryTemplate, req : true }
|
||||
];
|
||||
|
||||
const config = Config();
|
||||
async.map(templateFiles, (template, nextTemplate) => {
|
||||
if(!template.name && !template.req) {
|
||||
return nextTemplate(null, Buffer.from([]));
|
||||
}
|
||||
const config = Config();
|
||||
async.map(templateFiles, (template, nextTemplate) => {
|
||||
if(!template.name && !template.req) {
|
||||
return nextTemplate(null, Buffer.from([]));
|
||||
}
|
||||
|
||||
template.name = paths.isAbsolute(template.name) ? template.name : paths.join(config.paths.misc, template.name);
|
||||
fs.readFile(template.name, (err, data) => {
|
||||
return nextTemplate(err, data);
|
||||
});
|
||||
}, (err, templates) => {
|
||||
if(err) {
|
||||
return callback(Errors.General(err.message));
|
||||
}
|
||||
template.name = paths.isAbsolute(template.name) ? template.name : paths.join(config.paths.misc, template.name);
|
||||
fs.readFile(template.name, (err, data) => {
|
||||
return nextTemplate(err, data);
|
||||
});
|
||||
}, (err, templates) => {
|
||||
if(err) {
|
||||
return callback(Errors.General(err.message));
|
||||
}
|
||||
|
||||
// decode + ensure DOS style CRLF
|
||||
templates = templates.map(tmp => iconv.decode(tmp, options.templateEncoding).replace(/\r?\n/g, '\r\n') );
|
||||
// decode + ensure DOS style CRLF
|
||||
templates = templates.map(tmp => iconv.decode(tmp, options.templateEncoding).replace(/\r?\n/g, '\r\n') );
|
||||
|
||||
// Look for the first {fileDesc} (if any) in 'entry' template & find indentation requirements
|
||||
let descIndent = 0;
|
||||
if(!options.escapeDesc) {
|
||||
splitTextAtTerms(templates[1]).some(line => {
|
||||
const pos = line.indexOf('{fileDesc}');
|
||||
if(pos > -1) {
|
||||
descIndent = pos;
|
||||
return true; // found it!
|
||||
}
|
||||
return false; // keep looking
|
||||
});
|
||||
}
|
||||
// Look for the first {fileDesc} (if any) in 'entry' template & find indentation requirements
|
||||
let descIndent = 0;
|
||||
if(!options.escapeDesc) {
|
||||
splitTextAtTerms(templates[1]).some(line => {
|
||||
const pos = line.indexOf('{fileDesc}');
|
||||
if(pos > -1) {
|
||||
descIndent = pos;
|
||||
return true; // found it!
|
||||
}
|
||||
return false; // keep looking
|
||||
});
|
||||
}
|
||||
|
||||
return callback(null, templates[0], templates[1], descIndent);
|
||||
});
|
||||
});
|
||||
},
|
||||
function findFiles(headerTemplate, entryTemplate, descIndent, callback) {
|
||||
state.step = 'gathering';
|
||||
state.status = 'Gathering files for supplied criteria';
|
||||
updateProgress(err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, templates[0], templates[1], descIndent);
|
||||
});
|
||||
});
|
||||
},
|
||||
function findFiles(headerTemplate, entryTemplate, descIndent, callback) {
|
||||
state.step = 'gathering';
|
||||
state.status = 'Gathering files for supplied criteria';
|
||||
updateProgress(err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
FileEntry.findFiles(filterCriteria, (err, fileIds) => {
|
||||
if(0 === fileIds.length) {
|
||||
return callback(Errors.General('No results for criteria', 'NORESULTS'));
|
||||
}
|
||||
FileEntry.findFiles(filterCriteria, (err, fileIds) => {
|
||||
if(0 === fileIds.length) {
|
||||
return callback(Errors.General('No results for criteria', 'NORESULTS'));
|
||||
}
|
||||
|
||||
return callback(err, headerTemplate, entryTemplate, descIndent, fileIds);
|
||||
});
|
||||
});
|
||||
},
|
||||
function buildListEntries(headerTemplate, entryTemplate, descIndent, fileIds, callback) {
|
||||
const formatObj = {
|
||||
totalFileCount : fileIds.length,
|
||||
};
|
||||
return callback(err, headerTemplate, entryTemplate, descIndent, fileIds);
|
||||
});
|
||||
});
|
||||
},
|
||||
function buildListEntries(headerTemplate, entryTemplate, descIndent, fileIds, callback) {
|
||||
const formatObj = {
|
||||
totalFileCount : fileIds.length,
|
||||
};
|
||||
|
||||
let current = 0;
|
||||
let listBody = '';
|
||||
const totals = { fileCount : fileIds.length, bytes : 0 };
|
||||
state.total = fileIds.length;
|
||||
let current = 0;
|
||||
let listBody = '';
|
||||
const totals = { fileCount : fileIds.length, bytes : 0 };
|
||||
state.total = fileIds.length;
|
||||
|
||||
state.step = 'file';
|
||||
state.step = 'file';
|
||||
|
||||
async.eachSeries(fileIds, (fileId, nextFileId) => {
|
||||
const fileInfo = new FileEntry();
|
||||
current += 1;
|
||||
async.eachSeries(fileIds, (fileId, nextFileId) => {
|
||||
const fileInfo = new FileEntry();
|
||||
current += 1;
|
||||
|
||||
fileInfo.load(fileId, err => {
|
||||
if(err) {
|
||||
return nextFileId(null); // failed, but try the next
|
||||
}
|
||||
fileInfo.load(fileId, err => {
|
||||
if(err) {
|
||||
return nextFileId(null); // failed, but try the next
|
||||
}
|
||||
|
||||
totals.bytes += fileInfo.meta.byte_size;
|
||||
totals.bytes += fileInfo.meta.byte_size;
|
||||
|
||||
const appendFileInfo = () => {
|
||||
if(options.escapeDesc) {
|
||||
formatObj.fileDesc = formatObj.fileDesc.replace(/\r?\n/g, options.escapeDesc);
|
||||
}
|
||||
const appendFileInfo = () => {
|
||||
if(options.escapeDesc) {
|
||||
formatObj.fileDesc = formatObj.fileDesc.replace(/\r?\n/g, options.escapeDesc);
|
||||
}
|
||||
|
||||
if(options.maxDescLen) {
|
||||
formatObj.fileDesc = formatObj.fileDesc.slice(0, options.maxDescLen);
|
||||
}
|
||||
if(options.maxDescLen) {
|
||||
formatObj.fileDesc = formatObj.fileDesc.slice(0, options.maxDescLen);
|
||||
}
|
||||
|
||||
listBody += stringFormat(entryTemplate, formatObj);
|
||||
listBody += stringFormat(entryTemplate, formatObj);
|
||||
|
||||
state.current = current;
|
||||
state.status = `Processing ${fileInfo.fileName}`;
|
||||
state.fileInfo = formatObj;
|
||||
state.current = current;
|
||||
state.status = `Processing ${fileInfo.fileName}`;
|
||||
state.fileInfo = formatObj;
|
||||
|
||||
updateProgress(err => {
|
||||
return nextFileId(err);
|
||||
});
|
||||
};
|
||||
updateProgress(err => {
|
||||
return nextFileId(err);
|
||||
});
|
||||
};
|
||||
|
||||
const area = FileArea.getFileAreaByTag(fileInfo.areaTag);
|
||||
const area = FileArea.getFileAreaByTag(fileInfo.areaTag);
|
||||
|
||||
formatObj.fileId = fileId;
|
||||
formatObj.areaName = _.get(area, 'name') || 'N/A';
|
||||
formatObj.areaDesc = _.get(area, 'desc') || 'N/A';
|
||||
formatObj.userRating = fileInfo.userRating || 0;
|
||||
formatObj.fileName = fileInfo.fileName;
|
||||
formatObj.fileSize = fileInfo.meta.byte_size;
|
||||
formatObj.fileDesc = fileInfo.desc || '';
|
||||
formatObj.fileDescShort = formatObj.fileDesc.slice(0, options.descWidth);
|
||||
formatObj.fileSha256 = fileInfo.fileSha256;
|
||||
formatObj.fileCrc32 = fileInfo.meta.file_crc32;
|
||||
formatObj.fileMd5 = fileInfo.meta.file_md5;
|
||||
formatObj.fileSha1 = fileInfo.meta.file_sha1;
|
||||
formatObj.uploadBy = fileInfo.meta.upload_by_username || 'N/A';
|
||||
formatObj.fileUploadTs = moment(fileInfo.uploadTimestamp).format(options.tsFormat);
|
||||
formatObj.fileHashTags = fileInfo.hashTags.size > 0 ? Array.from(fileInfo.hashTags).join(', ') : 'N/A';
|
||||
formatObj.currentFile = current;
|
||||
formatObj.progress = Math.floor( (current / fileIds.length) * 100 );
|
||||
formatObj.fileId = fileId;
|
||||
formatObj.areaName = _.get(area, 'name') || 'N/A';
|
||||
formatObj.areaDesc = _.get(area, 'desc') || 'N/A';
|
||||
formatObj.userRating = fileInfo.userRating || 0;
|
||||
formatObj.fileName = fileInfo.fileName;
|
||||
formatObj.fileSize = fileInfo.meta.byte_size;
|
||||
formatObj.fileDesc = fileInfo.desc || '';
|
||||
formatObj.fileDescShort = formatObj.fileDesc.slice(0, options.descWidth);
|
||||
formatObj.fileSha256 = fileInfo.fileSha256;
|
||||
formatObj.fileCrc32 = fileInfo.meta.file_crc32;
|
||||
formatObj.fileMd5 = fileInfo.meta.file_md5;
|
||||
formatObj.fileSha1 = fileInfo.meta.file_sha1;
|
||||
formatObj.uploadBy = fileInfo.meta.upload_by_username || 'N/A';
|
||||
formatObj.fileUploadTs = moment(fileInfo.uploadTimestamp).format(options.tsFormat);
|
||||
formatObj.fileHashTags = fileInfo.hashTags.size > 0 ? Array.from(fileInfo.hashTags).join(', ') : 'N/A';
|
||||
formatObj.currentFile = current;
|
||||
formatObj.progress = Math.floor( (current / fileIds.length) * 100 );
|
||||
|
||||
if(isAnsi(fileInfo.desc)) {
|
||||
AnsiPrep(
|
||||
fileInfo.desc,
|
||||
{
|
||||
cols : Math.min(options.descWidth, 79 - descIndent),
|
||||
forceLineTerm : true, // ensure each line is term'd
|
||||
asciiMode : true, // export to ASCII
|
||||
fillLines : false, // don't fill up to |cols|
|
||||
indent : descIndent,
|
||||
},
|
||||
(err, desc) => {
|
||||
if(desc) {
|
||||
formatObj.fileDesc = desc;
|
||||
}
|
||||
return appendFileInfo();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const indentSpc = descIndent > 0 ? ' '.repeat(descIndent) : '';
|
||||
formatObj.fileDesc = splitTextAtTerms(formatObj.fileDesc).join(`\r\n${indentSpc}`) + '\r\n';
|
||||
return appendFileInfo();
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
return callback(err, listBody, headerTemplate, totals);
|
||||
});
|
||||
},
|
||||
function buildHeader(listBody, headerTemplate, totals, callback) {
|
||||
// header is built last such that we can have totals/etc.
|
||||
if(isAnsi(fileInfo.desc)) {
|
||||
AnsiPrep(
|
||||
fileInfo.desc,
|
||||
{
|
||||
cols : Math.min(options.descWidth, 79 - descIndent),
|
||||
forceLineTerm : true, // ensure each line is term'd
|
||||
asciiMode : true, // export to ASCII
|
||||
fillLines : false, // don't fill up to |cols|
|
||||
indent : descIndent,
|
||||
},
|
||||
(err, desc) => {
|
||||
if(desc) {
|
||||
formatObj.fileDesc = desc;
|
||||
}
|
||||
return appendFileInfo();
|
||||
}
|
||||
);
|
||||
} else {
|
||||
const indentSpc = descIndent > 0 ? ' '.repeat(descIndent) : '';
|
||||
formatObj.fileDesc = splitTextAtTerms(formatObj.fileDesc).join(`\r\n${indentSpc}`) + '\r\n';
|
||||
return appendFileInfo();
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
return callback(err, listBody, headerTemplate, totals);
|
||||
});
|
||||
},
|
||||
function buildHeader(listBody, headerTemplate, totals, callback) {
|
||||
// header is built last such that we can have totals/etc.
|
||||
|
||||
let filterAreaName;
|
||||
let filterAreaDesc;
|
||||
if(filterCriteria.areaTag) {
|
||||
const area = FileArea.getFileAreaByTag(filterCriteria.areaTag);
|
||||
filterAreaName = _.get(area, 'name') || 'N/A';
|
||||
filterAreaDesc = _.get(area, 'desc') || 'N/A';
|
||||
} else {
|
||||
filterAreaName = '-ALL-';
|
||||
filterAreaDesc = 'All areas';
|
||||
}
|
||||
let filterAreaName;
|
||||
let filterAreaDesc;
|
||||
if(filterCriteria.areaTag) {
|
||||
const area = FileArea.getFileAreaByTag(filterCriteria.areaTag);
|
||||
filterAreaName = _.get(area, 'name') || 'N/A';
|
||||
filterAreaDesc = _.get(area, 'desc') || 'N/A';
|
||||
} else {
|
||||
filterAreaName = '-ALL-';
|
||||
filterAreaDesc = 'All areas';
|
||||
}
|
||||
|
||||
const headerFormatObj = {
|
||||
nowTs : moment().format(options.tsFormat),
|
||||
boardName : Config().general.boardName,
|
||||
totalFileCount : totals.fileCount,
|
||||
totalFileSize : totals.bytes,
|
||||
filterAreaTag : filterCriteria.areaTag || '-ALL-',
|
||||
filterAreaName : filterAreaName,
|
||||
filterAreaDesc : filterAreaDesc,
|
||||
filterTerms : filterCriteria.terms || '(none)',
|
||||
filterHashTags : filterCriteria.tags || '(none)',
|
||||
};
|
||||
const headerFormatObj = {
|
||||
nowTs : moment().format(options.tsFormat),
|
||||
boardName : Config().general.boardName,
|
||||
totalFileCount : totals.fileCount,
|
||||
totalFileSize : totals.bytes,
|
||||
filterAreaTag : filterCriteria.areaTag || '-ALL-',
|
||||
filterAreaName : filterAreaName,
|
||||
filterAreaDesc : filterAreaDesc,
|
||||
filterTerms : filterCriteria.terms || '(none)',
|
||||
filterHashTags : filterCriteria.tags || '(none)',
|
||||
};
|
||||
|
||||
listBody = stringFormat(headerTemplate, headerFormatObj) + listBody;
|
||||
return callback(null, listBody);
|
||||
},
|
||||
function done(listBody, callback) {
|
||||
delete state.fileInfo;
|
||||
state.step = 'finished';
|
||||
state.status = 'Finished processing';
|
||||
updateProgress( () => {
|
||||
return callback(null, listBody);
|
||||
});
|
||||
}
|
||||
], (err, listBody) => {
|
||||
return cb(err, listBody);
|
||||
}
|
||||
);
|
||||
listBody = stringFormat(headerTemplate, headerFormatObj) + listBody;
|
||||
return callback(null, listBody);
|
||||
},
|
||||
function done(listBody, callback) {
|
||||
delete state.fileInfo;
|
||||
state.step = 'finished';
|
||||
state.status = 'Finished processing';
|
||||
updateProgress( () => {
|
||||
return callback(null, listBody);
|
||||
});
|
||||
}
|
||||
], (err, listBody) => {
|
||||
return cb(err, listBody);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function updateFileBaseDescFilesScheduledEvent(args, cb) {
|
||||
//
|
||||
// For each area, loop over storage locations and build
|
||||
// DESCRIPT.ION file to store in the same directory.
|
||||
//
|
||||
// Standard-ish 4DOS spec is as such:
|
||||
// * Entry: <QUOTED_LFN> <DESC>[0x04<AppData>]\r\n
|
||||
// * Multi line descriptions are stored with *escaped* \r\n pairs
|
||||
// * Default template uses 0x2c for <AppData> as per https://stackoverflow.com/questions/1810398/descript-ion-file-spec
|
||||
//
|
||||
const entryTemplate = args[0];
|
||||
const headerTemplate = args[1];
|
||||
//
|
||||
// For each area, loop over storage locations and build
|
||||
// DESCRIPT.ION file to store in the same directory.
|
||||
//
|
||||
// Standard-ish 4DOS spec is as such:
|
||||
// * Entry: <QUOTED_LFN> <DESC>[0x04<AppData>]\r\n
|
||||
// * Multi line descriptions are stored with *escaped* \r\n pairs
|
||||
// * Default template uses 0x2c for <AppData> as per https://stackoverflow.com/questions/1810398/descript-ion-file-spec
|
||||
//
|
||||
const entryTemplate = args[0];
|
||||
const headerTemplate = args[1];
|
||||
|
||||
const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck : true });
|
||||
async.each(areas, (area, nextArea) => {
|
||||
const storageLocations = FileArea.getAreaStorageLocations(area);
|
||||
const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck : true });
|
||||
async.each(areas, (area, nextArea) => {
|
||||
const storageLocations = FileArea.getAreaStorageLocations(area);
|
||||
|
||||
async.each(storageLocations, (storageLoc, nextStorageLoc) => {
|
||||
const filterCriteria = {
|
||||
areaTag : area.areaTag,
|
||||
storageTag : storageLoc.storageTag,
|
||||
};
|
||||
async.each(storageLocations, (storageLoc, nextStorageLoc) => {
|
||||
const filterCriteria = {
|
||||
areaTag : area.areaTag,
|
||||
storageTag : storageLoc.storageTag,
|
||||
};
|
||||
|
||||
const exportOpts = {
|
||||
headerTemplate : headerTemplate,
|
||||
entryTemplate : entryTemplate,
|
||||
escapeDesc : true, // escape CRLF's
|
||||
maxDescLen : 4096, // DESCRIPT.ION: "The line length limit is 4096 bytes"
|
||||
};
|
||||
const exportOpts = {
|
||||
headerTemplate : headerTemplate,
|
||||
entryTemplate : entryTemplate,
|
||||
escapeDesc : true, // escape CRLF's
|
||||
maxDescLen : 4096, // DESCRIPT.ION: "The line length limit is 4096 bytes"
|
||||
};
|
||||
|
||||
exportFileList(filterCriteria, exportOpts, (err, listBody) => {
|
||||
exportFileList(filterCriteria, exportOpts, (err, listBody) => {
|
||||
|
||||
const descIonPath = paths.join(storageLoc.dir, 'DESCRIPT.ION');
|
||||
fs.writeFile(descIonPath, iconv.encode(listBody, 'cp437'), err => {
|
||||
if(err) {
|
||||
Log.warn( { error : err.message, path : descIonPath }, 'Failed (re)creating DESCRIPT.ION');
|
||||
} else {
|
||||
Log.debug( { path : descIonPath }, '(Re)generated DESCRIPT.ION');
|
||||
}
|
||||
return nextStorageLoc(null);
|
||||
});
|
||||
});
|
||||
}, () => {
|
||||
return nextArea(null);
|
||||
});
|
||||
}, () => {
|
||||
return cb(null);
|
||||
});
|
||||
const descIonPath = paths.join(storageLoc.dir, 'DESCRIPT.ION');
|
||||
fs.writeFile(descIonPath, iconv.encode(listBody, 'cp437'), err => {
|
||||
if(err) {
|
||||
Log.warn( { error : err.message, path : descIonPath }, 'Failed (re)creating DESCRIPT.ION');
|
||||
} else {
|
||||
Log.debug( { path : descIonPath }, '(Re)generated DESCRIPT.ION');
|
||||
}
|
||||
return nextStorageLoc(null);
|
||||
});
|
||||
});
|
||||
}, () => {
|
||||
return nextArea(null);
|
||||
});
|
||||
}, () => {
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,110 +11,110 @@ const FileBaseFilters = require('./file_base_filter.js');
|
|||
const async = require('async');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Base Search',
|
||||
desc : 'Module for quickly searching the file base',
|
||||
author : 'NuSkooler',
|
||||
name : 'File Base Search',
|
||||
desc : 'Module for quickly searching the file base',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
search : {
|
||||
searchTerms : 1,
|
||||
search : 2,
|
||||
tags : 3,
|
||||
area : 4,
|
||||
orderBy : 5,
|
||||
sort : 6,
|
||||
advSearch : 7,
|
||||
}
|
||||
search : {
|
||||
searchTerms : 1,
|
||||
search : 2,
|
||||
tags : 3,
|
||||
area : 4,
|
||||
orderBy : 5,
|
||||
sort : 6,
|
||||
advSearch : 7,
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class FileBaseSearch extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.menuMethods = {
|
||||
search : (formData, extraArgs, cb) => {
|
||||
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||
return this.searchNow(formData, isAdvanced, cb);
|
||||
},
|
||||
};
|
||||
}
|
||||
this.menuMethods = {
|
||||
search : (formData, extraArgs, cb) => {
|
||||
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||
return this.searchNow(formData, isAdvanced, cb);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) );
|
||||
const self = this;
|
||||
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) );
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||
},
|
||||
function populateAreas(callback) {
|
||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||
},
|
||||
function populateAreas(callback) {
|
||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||
|
||||
const areasView = vc.getView(MciViewIds.search.area);
|
||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||
areasView.redraw();
|
||||
vc.switchFocus(MciViewIds.search.searchTerms);
|
||||
const areasView = vc.getView(MciViewIds.search.area);
|
||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||
areasView.redraw();
|
||||
vc.switchFocus(MciViewIds.search.searchTerms);
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getSelectedAreaTag(index) {
|
||||
if(0 === index) {
|
||||
return ''; // -ALL-
|
||||
}
|
||||
const area = this.availAreas[index];
|
||||
if(!area) {
|
||||
return '';
|
||||
}
|
||||
return area.areaTag;
|
||||
}
|
||||
getSelectedAreaTag(index) {
|
||||
if(0 === index) {
|
||||
return ''; // -ALL-
|
||||
}
|
||||
const area = this.availAreas[index];
|
||||
if(!area) {
|
||||
return '';
|
||||
}
|
||||
return area.areaTag;
|
||||
}
|
||||
|
||||
getOrderBy(index) {
|
||||
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||
}
|
||||
getOrderBy(index) {
|
||||
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||
}
|
||||
|
||||
getSortBy(index) {
|
||||
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||
}
|
||||
getSortBy(index) {
|
||||
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||
}
|
||||
|
||||
getFilterValuesFromFormData(formData, isAdvanced) {
|
||||
const areaIndex = isAdvanced ? formData.value.areaIndex : 0;
|
||||
const orderByIndex = isAdvanced ? formData.value.orderByIndex : 0;
|
||||
const sortByIndex = isAdvanced ? formData.value.sortByIndex : 0;
|
||||
getFilterValuesFromFormData(formData, isAdvanced) {
|
||||
const areaIndex = isAdvanced ? formData.value.areaIndex : 0;
|
||||
const orderByIndex = isAdvanced ? formData.value.orderByIndex : 0;
|
||||
const sortByIndex = isAdvanced ? formData.value.sortByIndex : 0;
|
||||
|
||||
return {
|
||||
areaTag : this.getSelectedAreaTag(areaIndex),
|
||||
terms : formData.value.searchTerms,
|
||||
tags : isAdvanced ? formData.value.tags : '',
|
||||
order : this.getOrderBy(orderByIndex),
|
||||
sort : this.getSortBy(sortByIndex),
|
||||
};
|
||||
}
|
||||
return {
|
||||
areaTag : this.getSelectedAreaTag(areaIndex),
|
||||
terms : formData.value.searchTerms,
|
||||
tags : isAdvanced ? formData.value.tags : '',
|
||||
order : this.getOrderBy(orderByIndex),
|
||||
sort : this.getSortBy(sortByIndex),
|
||||
};
|
||||
}
|
||||
|
||||
searchNow(formData, isAdvanced, cb) {
|
||||
const filterCriteria = this.getFilterValuesFromFormData(formData, isAdvanced);
|
||||
searchNow(formData, isAdvanced, cb) {
|
||||
const filterCriteria = this.getFilterValuesFromFormData(formData, isAdvanced);
|
||||
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
filterCriteria : filterCriteria,
|
||||
},
|
||||
menuFlags : [ 'popParent' ],
|
||||
};
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
filterCriteria : filterCriteria,
|
||||
},
|
||||
menuFlags : [ 'popParent' ],
|
||||
};
|
||||
|
||||
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
|
||||
}
|
||||
return this.gotoMenu(this.menuConfig.config.fileBaseListEntriesMenu || 'fileBaseListEntries', menuOpts, cb);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -46,249 +46,249 @@ const yazl = require('yazl');
|
|||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Base List Export',
|
||||
desc : 'Exports file base listings for download',
|
||||
author : 'NuSkooler',
|
||||
name : 'File Base List Export',
|
||||
desc : 'Exports file base listings for download',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
main : 0,
|
||||
main : 0,
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
main : {
|
||||
status : 1,
|
||||
progressBar : 2,
|
||||
main : {
|
||||
status : 1,
|
||||
progressBar : 2,
|
||||
|
||||
customRangeStart : 10,
|
||||
}
|
||||
customRangeStart : 10,
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class FileBaseListExport extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
||||
|
||||
this.config.templateEncoding = this.config.templateEncoding || 'utf8';
|
||||
this.config.tsFormat = this.config.tsFormat || this.client.currentTheme.helpers.getDateTimeFormat('short');
|
||||
this.config.descWidth = this.config.descWidth || 45; // ie FILE_ID.DIZ
|
||||
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
|
||||
this.config.compressThreshold = this.config.compressThreshold || (1440000); // >= 1.44M by default :)
|
||||
}
|
||||
this.config.templateEncoding = this.config.templateEncoding || 'utf8';
|
||||
this.config.tsFormat = this.config.tsFormat || this.client.currentTheme.helpers.getDateTimeFormat('short');
|
||||
this.config.descWidth = this.config.descWidth || 45; // ie FILE_ID.DIZ
|
||||
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
|
||||
this.config.compressThreshold = this.config.compressThreshold || (1440000); // >= 1.44M by default :)
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
async.series(
|
||||
[
|
||||
(callback) => this.prepViewController('main', FormIds.main, mciData.menu, callback),
|
||||
(callback) => this.prepareList(callback),
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
if('NORESULTS' === err.reasonCode) {
|
||||
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'fileBaseExportListNoResults');
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
(callback) => this.prepViewController('main', FormIds.main, mciData.menu, callback),
|
||||
(callback) => this.prepareList(callback),
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
if('NORESULTS' === err.reasonCode) {
|
||||
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'fileBaseExportListNoResults');
|
||||
}
|
||||
|
||||
return this.prevMenu();
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
return this.prevMenu();
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
finishedLoading() {
|
||||
this.prevMenu();
|
||||
}
|
||||
finishedLoading() {
|
||||
this.prevMenu();
|
||||
}
|
||||
|
||||
prepareList(cb) {
|
||||
const self = this;
|
||||
prepareList(cb) {
|
||||
const self = this;
|
||||
|
||||
const statusView = self.viewControllers.main.getView(MciViewIds.main.status);
|
||||
const updateStatus = (status) => {
|
||||
if(statusView) {
|
||||
statusView.setText(status);
|
||||
}
|
||||
};
|
||||
const statusView = self.viewControllers.main.getView(MciViewIds.main.status);
|
||||
const updateStatus = (status) => {
|
||||
if(statusView) {
|
||||
statusView.setText(status);
|
||||
}
|
||||
};
|
||||
|
||||
const progBarView = self.viewControllers.main.getView(MciViewIds.main.progressBar);
|
||||
const updateProgressBar = (curr, total) => {
|
||||
if(progBarView) {
|
||||
const prog = Math.floor( (curr / total) * progBarView.dimens.width );
|
||||
progBarView.setText(self.config.progBarChar.repeat(prog));
|
||||
}
|
||||
};
|
||||
const progBarView = self.viewControllers.main.getView(MciViewIds.main.progressBar);
|
||||
const updateProgressBar = (curr, total) => {
|
||||
if(progBarView) {
|
||||
const prog = Math.floor( (curr / total) * progBarView.dimens.width );
|
||||
progBarView.setText(self.config.progBarChar.repeat(prog));
|
||||
}
|
||||
};
|
||||
|
||||
let cancel = false;
|
||||
let cancel = false;
|
||||
|
||||
const exportListProgress = (state, progNext) => {
|
||||
switch(state.step) {
|
||||
case 'preparing' :
|
||||
case 'gathering' :
|
||||
updateStatus(state.status);
|
||||
break;
|
||||
case 'file' :
|
||||
updateStatus(state.status);
|
||||
updateProgressBar(state.current, state.total);
|
||||
self.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state.fileInfo);
|
||||
break;
|
||||
default :
|
||||
break;
|
||||
}
|
||||
const exportListProgress = (state, progNext) => {
|
||||
switch(state.step) {
|
||||
case 'preparing' :
|
||||
case 'gathering' :
|
||||
updateStatus(state.status);
|
||||
break;
|
||||
case 'file' :
|
||||
updateStatus(state.status);
|
||||
updateProgressBar(state.current, state.total);
|
||||
self.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state.fileInfo);
|
||||
break;
|
||||
default :
|
||||
break;
|
||||
}
|
||||
|
||||
return progNext(cancel ? Errors.General('User canceled') : null);
|
||||
};
|
||||
return progNext(cancel ? Errors.General('User canceled') : null);
|
||||
};
|
||||
|
||||
const keyPressHandler = (ch, key) => {
|
||||
if('escape' === key.name) {
|
||||
cancel = true;
|
||||
self.client.removeListener('key press', keyPressHandler);
|
||||
}
|
||||
};
|
||||
const keyPressHandler = (ch, key) => {
|
||||
if('escape' === key.name) {
|
||||
cancel = true;
|
||||
self.client.removeListener('key press', keyPressHandler);
|
||||
}
|
||||
};
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function buildList(callback) {
|
||||
// this may take quite a while; temp disable of idle monitor
|
||||
self.client.stopIdleMonitor();
|
||||
async.waterfall(
|
||||
[
|
||||
function buildList(callback) {
|
||||
// this may take quite a while; temp disable of idle monitor
|
||||
self.client.stopIdleMonitor();
|
||||
|
||||
self.client.on('key press', keyPressHandler);
|
||||
self.client.on('key press', keyPressHandler);
|
||||
|
||||
const filterCriteria = Object.assign({}, self.config.filterCriteria);
|
||||
if(!filterCriteria.areaTag) {
|
||||
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(self.client);
|
||||
}
|
||||
const filterCriteria = Object.assign({}, self.config.filterCriteria);
|
||||
if(!filterCriteria.areaTag) {
|
||||
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(self.client);
|
||||
}
|
||||
|
||||
const opts = {
|
||||
templateEncoding : self.config.templateEncoding,
|
||||
headerTemplate : _.get(self.config, 'templates.header', 'file_list_header.asc'),
|
||||
entryTemplate : _.get(self.config, 'templates.entry', 'file_list_entry.asc'),
|
||||
tsFormat : self.config.tsFormat,
|
||||
descWidth : self.config.descWidth,
|
||||
progress : exportListProgress,
|
||||
};
|
||||
const opts = {
|
||||
templateEncoding : self.config.templateEncoding,
|
||||
headerTemplate : _.get(self.config, 'templates.header', 'file_list_header.asc'),
|
||||
entryTemplate : _.get(self.config, 'templates.entry', 'file_list_entry.asc'),
|
||||
tsFormat : self.config.tsFormat,
|
||||
descWidth : self.config.descWidth,
|
||||
progress : exportListProgress,
|
||||
};
|
||||
|
||||
exportFileList(filterCriteria, opts, (err, listBody) => {
|
||||
return callback(err, listBody);
|
||||
});
|
||||
},
|
||||
function persistList(listBody, callback) {
|
||||
updateStatus('Persisting list');
|
||||
exportFileList(filterCriteria, opts, (err, listBody) => {
|
||||
return callback(err, listBody);
|
||||
});
|
||||
},
|
||||
function persistList(listBody, callback) {
|
||||
updateStatus('Persisting list');
|
||||
|
||||
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
|
||||
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
|
||||
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
|
||||
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
|
||||
|
||||
fse.mkdirs(sysTempDownloadDir, err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
fse.mkdirs(sysTempDownloadDir, err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const outputFileName = paths.join(
|
||||
sysTempDownloadDir,
|
||||
`file_list_${uuidv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
|
||||
);
|
||||
const outputFileName = paths.join(
|
||||
sysTempDownloadDir,
|
||||
`file_list_${uuidv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
|
||||
);
|
||||
|
||||
fs.writeFile(outputFileName, listBody, 'utf8', err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
fs.writeFile(outputFileName, listBody, 'utf8', err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
self.getSizeAndCompressIfMeetsSizeThreshold(outputFileName, (err, finalOutputFileName, fileSize) => {
|
||||
return callback(err, finalOutputFileName, fileSize, sysTempDownloadArea);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function persistFileEntry(outputFileName, fileSize, sysTempDownloadArea, callback) {
|
||||
const newEntry = new FileEntry({
|
||||
areaTag : sysTempDownloadArea.areaTag,
|
||||
fileName : paths.basename(outputFileName),
|
||||
storageTag : sysTempDownloadArea.storageTags[0],
|
||||
meta : {
|
||||
upload_by_username : self.client.user.username,
|
||||
upload_by_user_id : self.client.user.userId,
|
||||
byte_size : fileSize,
|
||||
session_temp_dl : 1, // download is valid until session is over
|
||||
}
|
||||
});
|
||||
self.getSizeAndCompressIfMeetsSizeThreshold(outputFileName, (err, finalOutputFileName, fileSize) => {
|
||||
return callback(err, finalOutputFileName, fileSize, sysTempDownloadArea);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
function persistFileEntry(outputFileName, fileSize, sysTempDownloadArea, callback) {
|
||||
const newEntry = new FileEntry({
|
||||
areaTag : sysTempDownloadArea.areaTag,
|
||||
fileName : paths.basename(outputFileName),
|
||||
storageTag : sysTempDownloadArea.storageTags[0],
|
||||
meta : {
|
||||
upload_by_username : self.client.user.username,
|
||||
upload_by_user_id : self.client.user.userId,
|
||||
byte_size : fileSize,
|
||||
session_temp_dl : 1, // download is valid until session is over
|
||||
}
|
||||
});
|
||||
|
||||
newEntry.desc = 'File List Export';
|
||||
newEntry.desc = 'File List Export';
|
||||
|
||||
newEntry.persist(err => {
|
||||
if(!err) {
|
||||
// queue it!
|
||||
const dlQueue = new DownloadQueue(self.client);
|
||||
dlQueue.add(newEntry, true); // true=systemFile
|
||||
newEntry.persist(err => {
|
||||
if(!err) {
|
||||
// queue it!
|
||||
const dlQueue = new DownloadQueue(self.client);
|
||||
dlQueue.add(newEntry, true); // true=systemFile
|
||||
|
||||
// clean up after ourselves when the session ends
|
||||
const thisClientId = self.client.session.id;
|
||||
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
||||
if(thisClientId === _.get(evt, 'client.session.id')) {
|
||||
FileEntry.removeEntry(newEntry, { removePhysFile : true }, err => {
|
||||
if(err) {
|
||||
Log.warn( { fileId : newEntry.fileId, path : outputFileName }, 'Failed removing temporary session download' );
|
||||
} else {
|
||||
Log.debug( { fileId : newEntry.fileId, path : outputFileName }, 'Removed temporary session download item' );
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function done(callback) {
|
||||
// re-enable idle monitor
|
||||
self.client.startIdleMonitor();
|
||||
// clean up after ourselves when the session ends
|
||||
const thisClientId = self.client.session.id;
|
||||
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
||||
if(thisClientId === _.get(evt, 'client.session.id')) {
|
||||
FileEntry.removeEntry(newEntry, { removePhysFile : true }, err => {
|
||||
if(err) {
|
||||
Log.warn( { fileId : newEntry.fileId, path : outputFileName }, 'Failed removing temporary session download' );
|
||||
} else {
|
||||
Log.debug( { fileId : newEntry.fileId, path : outputFileName }, 'Removed temporary session download item' );
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function done(callback) {
|
||||
// re-enable idle monitor
|
||||
self.client.startIdleMonitor();
|
||||
|
||||
updateStatus('Exported list has been added to your download queue');
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
self.client.removeListener('key press', keyPressHandler);
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
updateStatus('Exported list has been added to your download queue');
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
self.client.removeListener('key press', keyPressHandler);
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSizeAndCompressIfMeetsSizeThreshold(filePath, cb) {
|
||||
fse.stat(filePath, (err, stats) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
getSizeAndCompressIfMeetsSizeThreshold(filePath, cb) {
|
||||
fse.stat(filePath, (err, stats) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(stats.size < this.config.compressThreshold) {
|
||||
// small enough, keep orig
|
||||
return cb(null, filePath, stats.size);
|
||||
}
|
||||
if(stats.size < this.config.compressThreshold) {
|
||||
// small enough, keep orig
|
||||
return cb(null, filePath, stats.size);
|
||||
}
|
||||
|
||||
const zipFilePath = `${filePath}.zip`;
|
||||
const zipFilePath = `${filePath}.zip`;
|
||||
|
||||
const zipFile = new yazl.ZipFile();
|
||||
zipFile.addFile(filePath, paths.basename(filePath));
|
||||
zipFile.end( () => {
|
||||
const outZipFile = fs.createWriteStream(zipFilePath);
|
||||
zipFile.outputStream.pipe(outZipFile);
|
||||
zipFile.outputStream.on('finish', () => {
|
||||
// delete the original
|
||||
fse.unlink(filePath, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
const zipFile = new yazl.ZipFile();
|
||||
zipFile.addFile(filePath, paths.basename(filePath));
|
||||
zipFile.end( () => {
|
||||
const outZipFile = fs.createWriteStream(zipFilePath);
|
||||
zipFile.outputStream.pipe(outZipFile);
|
||||
zipFile.outputStream.on('finish', () => {
|
||||
// delete the original
|
||||
fse.unlink(filePath, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
// finally stat the new output
|
||||
fse.stat(zipFilePath, (err, stats) => {
|
||||
return cb(err, zipFilePath, stats ? stats.size : 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// finally stat the new output
|
||||
fse.stat(zipFilePath, (err, stats) => {
|
||||
return cb(err, zipFilePath, stats ? stats.size : 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
|
@ -19,269 +19,269 @@ const _ = require('lodash');
|
|||
const moment = require('moment');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Base Download Web Queue Manager',
|
||||
desc : 'Module for interacting with web backed download queue/batch',
|
||||
author : 'NuSkooler',
|
||||
name : 'File Base Download Web Queue Manager',
|
||||
desc : 'Module for interacting with web backed download queue/batch',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
queueManager : 0
|
||||
queueManager : 0
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
queueManager : {
|
||||
queue : 1,
|
||||
navMenu : 2,
|
||||
queueManager : {
|
||||
queue : 1,
|
||||
navMenu : 2,
|
||||
|
||||
customRangeStart : 10,
|
||||
}
|
||||
customRangeStart : 10,
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.dlQueue = new DownloadQueue(this.client);
|
||||
this.dlQueue = new DownloadQueue(this.client);
|
||||
|
||||
this.menuMethods = {
|
||||
removeItem : (formData, extraArgs, cb) => {
|
||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||
if(!selectedItem) {
|
||||
return cb(null);
|
||||
}
|
||||
this.menuMethods = {
|
||||
removeItem : (formData, extraArgs, cb) => {
|
||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||
if(!selectedItem) {
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
this.dlQueue.removeItems(selectedItem.fileId);
|
||||
this.dlQueue.removeItems(selectedItem.fileId);
|
||||
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
||||
},
|
||||
clearQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.clear();
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
||||
},
|
||||
clearQueue : (formData, extraArgs, cb) => {
|
||||
this.dlQueue.clear();
|
||||
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||
},
|
||||
getBatchLink : (formData, extraArgs, cb) => {
|
||||
return this.generateAndDisplayBatchLink(cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||
},
|
||||
getBatchLink : (formData, extraArgs, cb) => {
|
||||
return this.generateAndDisplayBatchLink(cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
if(0 === this.dlQueue.items.length) {
|
||||
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
||||
}
|
||||
initSequence() {
|
||||
if(0 === this.dlQueue.items.length) {
|
||||
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function beforeArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayQueueManagerPage(false, callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
return self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function beforeArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayQueueManagerPage(false, callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
return self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
|
||||
if('all' === itemIndex) {
|
||||
queueView.setItems([]);
|
||||
queueView.setFocusItems([]);
|
||||
} else {
|
||||
queueView.removeItem(itemIndex);
|
||||
}
|
||||
if('all' === itemIndex) {
|
||||
queueView.setItems([]);
|
||||
queueView.setFocusItems([]);
|
||||
} else {
|
||||
queueView.removeItem(itemIndex);
|
||||
}
|
||||
|
||||
queueView.redraw();
|
||||
return cb(null);
|
||||
}
|
||||
queueView.redraw();
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
displayFileInfoForFileEntry(fileEntry) {
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||
{ filter : [ '{webDlLink}', '{webDlExpire}', '{fileName}' ] } // :TODO: Others....
|
||||
);
|
||||
}
|
||||
displayFileInfoForFileEntry(fileEntry) {
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||
{ filter : [ '{webDlLink}', '{webDlExpire}', '{fileName}' ] } // :TODO: Others....
|
||||
);
|
||||
}
|
||||
|
||||
updateDownloadQueueView(cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
updateDownloadQueueView(cb) {
|
||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||
if(!queueView) {
|
||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||
}
|
||||
|
||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{webDlLink}';
|
||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{webDlLink}';
|
||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||
|
||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||
|
||||
queueView.on('index update', idx => {
|
||||
const fileEntry = this.dlQueue.items[idx];
|
||||
this.displayFileInfoForFileEntry(fileEntry);
|
||||
});
|
||||
queueView.on('index update', idx => {
|
||||
const fileEntry = this.dlQueue.items[idx];
|
||||
this.displayFileInfoForFileEntry(fileEntry);
|
||||
});
|
||||
|
||||
queueView.redraw();
|
||||
this.displayFileInfoForFileEntry(this.dlQueue.items[0]);
|
||||
queueView.redraw();
|
||||
this.displayFileInfoForFileEntry(this.dlQueue.items[0]);
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
generateAndDisplayBatchLink(cb) {
|
||||
const expireTime = moment().add(Config().fileBase.web.expireMinutes, 'minutes');
|
||||
generateAndDisplayBatchLink(cb) {
|
||||
const expireTime = moment().add(Config().fileBase.web.expireMinutes, 'minutes');
|
||||
|
||||
FileAreaWeb.createAndServeTempBatchDownload(
|
||||
this.client,
|
||||
this.dlQueue.items,
|
||||
{
|
||||
expireTime : expireTime
|
||||
},
|
||||
(err, webBatchDlLink) => {
|
||||
// :TODO: handle not enabled -> display such
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
FileAreaWeb.createAndServeTempBatchDownload(
|
||||
this.client,
|
||||
this.dlQueue.items,
|
||||
{
|
||||
expireTime : expireTime
|
||||
},
|
||||
(err, webBatchDlLink) => {
|
||||
// :TODO: handle not enabled -> display such
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
|
||||
const formatObj = {
|
||||
webBatchDlLink : ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
|
||||
webBatchDlExpire : expireTime.format(webDlExpireTimeFormat),
|
||||
};
|
||||
const formatObj = {
|
||||
webBatchDlLink : ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
|
||||
webBatchDlExpire : expireTime.format(webDlExpireTimeFormat),
|
||||
};
|
||||
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart,
|
||||
formatObj,
|
||||
{ filter : Object.keys(formatObj).map(k => '{' + k + '}' ) }
|
||||
);
|
||||
this.updateCustomViewTextsWithFilter(
|
||||
'queueManager',
|
||||
MciViewIds.queueManager.customRangeStart,
|
||||
formatObj,
|
||||
{ filter : Object.keys(formatObj).map(k => '{' + k + '}' ) }
|
||||
);
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayQueueManagerPage(clearScreen, cb) {
|
||||
const self = this;
|
||||
displayQueueManagerPage(clearScreen, cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||
},
|
||||
function prepareQueueDownloadLinks(callback) {
|
||||
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
async.series(
|
||||
[
|
||||
function prepArtAndViewController(callback) {
|
||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||
},
|
||||
function prepareQueueDownloadLinks(callback) {
|
||||
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||
|
||||
const config = Config();
|
||||
async.each(self.dlQueue.items, (fileEntry, nextFileEntry) => {
|
||||
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => {
|
||||
if(err) {
|
||||
if(ErrNotEnabled === err.reasonCode) {
|
||||
return nextFileEntry(err); // we should have caught this prior
|
||||
}
|
||||
const config = Config();
|
||||
async.each(self.dlQueue.items, (fileEntry, nextFileEntry) => {
|
||||
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => {
|
||||
if(err) {
|
||||
if(ErrNotEnabled === err.reasonCode) {
|
||||
return nextFileEntry(err); // we should have caught this prior
|
||||
}
|
||||
|
||||
const expireTime = moment().add(config.fileBase.web.expireMinutes, 'minutes');
|
||||
const expireTime = moment().add(config.fileBase.web.expireMinutes, 'minutes');
|
||||
|
||||
FileAreaWeb.createAndServeTempDownload(
|
||||
self.client,
|
||||
fileEntry,
|
||||
{ expireTime : expireTime },
|
||||
(err, url) => {
|
||||
if(err) {
|
||||
return nextFileEntry(err);
|
||||
}
|
||||
FileAreaWeb.createAndServeTempDownload(
|
||||
self.client,
|
||||
fileEntry,
|
||||
{ expireTime : expireTime },
|
||||
(err, url) => {
|
||||
if(err) {
|
||||
return nextFileEntry(err);
|
||||
}
|
||||
|
||||
fileEntry.webDlLinkRaw = url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
|
||||
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat);
|
||||
fileEntry.webDlLinkRaw = url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
|
||||
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat);
|
||||
|
||||
return nextFileEntry(null);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
fileEntry.webDlLinkRaw = serveItem.url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url;
|
||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||
return nextFileEntry(null);
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function populateViews(callback) {
|
||||
return self.updateDownloadQueueView(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return nextFileEntry(null);
|
||||
}
|
||||
);
|
||||
} else {
|
||||
fileEntry.webDlLinkRaw = serveItem.url;
|
||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url;
|
||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||
return nextFileEntry(null);
|
||||
}
|
||||
});
|
||||
}, err => {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function populateViews(callback) {
|
||||
return self.updateDownloadQueueView(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayArtAndPrepViewController(name, options, cb) {
|
||||
const self = this;
|
||||
const config = this.menuConfig.config;
|
||||
displayArtAndPrepViewController(name, options, cb) {
|
||||
const self = this;
|
||||
const config = this.menuConfig.config;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function readyAndDisplayArt(callback) {
|
||||
if(options.clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
async.waterfall(
|
||||
[
|
||||
function readyAndDisplayArt(callback) {
|
||||
if(options.clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
theme.displayThemedAsset(
|
||||
config.art[name],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function prepeareViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers[name])) {
|
||||
const vcOpts = {
|
||||
client : self.client,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
theme.displayThemedAsset(
|
||||
config.art[name],
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function prepeareViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers[name])) {
|
||||
const vcOpts = {
|
||||
client : self.client,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
|
||||
if(!_.isUndefined(options.noInput)) {
|
||||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
if(!_.isUndefined(options.noInput)) {
|
||||
vcOpts.noInput = options.noInput;
|
||||
}
|
||||
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds[name],
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
self.viewControllers[name].setFocus(true);
|
||||
return callback(null);
|
||||
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
1000
core/file_entry.js
1000
core/file_entry.js
File diff suppressed because it is too large
Load Diff
|
@ -42,113 +42,113 @@ const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc.
|
|||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Transfer file',
|
||||
desc : 'Sends or receives a file(s)',
|
||||
author : 'NuSkooler',
|
||||
name : 'Transfer file',
|
||||
desc : 'Sends or receives a file(s)',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class TransferFileModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.config = this.menuConfig.config || {};
|
||||
this.config = this.menuConfig.config || {};
|
||||
|
||||
//
|
||||
// Most options can be set via extraArgs or config block
|
||||
//
|
||||
const config = Config();
|
||||
if(options.extraArgs) {
|
||||
if(options.extraArgs.protocol) {
|
||||
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol];
|
||||
}
|
||||
//
|
||||
// Most options can be set via extraArgs or config block
|
||||
//
|
||||
const config = Config();
|
||||
if(options.extraArgs) {
|
||||
if(options.extraArgs.protocol) {
|
||||
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol];
|
||||
}
|
||||
|
||||
if(options.extraArgs.direction) {
|
||||
this.direction = options.extraArgs.direction;
|
||||
}
|
||||
if(options.extraArgs.direction) {
|
||||
this.direction = options.extraArgs.direction;
|
||||
}
|
||||
|
||||
if(options.extraArgs.sendQueue) {
|
||||
this.sendQueue = options.extraArgs.sendQueue;
|
||||
}
|
||||
if(options.extraArgs.sendQueue) {
|
||||
this.sendQueue = options.extraArgs.sendQueue;
|
||||
}
|
||||
|
||||
if(options.extraArgs.recvFileName) {
|
||||
this.recvFileName = options.extraArgs.recvFileName;
|
||||
}
|
||||
if(options.extraArgs.recvFileName) {
|
||||
this.recvFileName = options.extraArgs.recvFileName;
|
||||
}
|
||||
|
||||
if(options.extraArgs.recvDirectory) {
|
||||
this.recvDirectory = options.extraArgs.recvDirectory;
|
||||
}
|
||||
} else {
|
||||
if(this.config.protocol) {
|
||||
this.protocolConfig = config.fileTransferProtocols[this.config.protocol];
|
||||
}
|
||||
if(options.extraArgs.recvDirectory) {
|
||||
this.recvDirectory = options.extraArgs.recvDirectory;
|
||||
}
|
||||
} else {
|
||||
if(this.config.protocol) {
|
||||
this.protocolConfig = config.fileTransferProtocols[this.config.protocol];
|
||||
}
|
||||
|
||||
if(this.config.direction) {
|
||||
this.direction = this.config.direction;
|
||||
}
|
||||
if(this.config.direction) {
|
||||
this.direction = this.config.direction;
|
||||
}
|
||||
|
||||
if(this.config.sendQueue) {
|
||||
this.sendQueue = this.config.sendQueue;
|
||||
}
|
||||
if(this.config.sendQueue) {
|
||||
this.sendQueue = this.config.sendQueue;
|
||||
}
|
||||
|
||||
if(this.config.recvFileName) {
|
||||
this.recvFileName = this.config.recvFileName;
|
||||
}
|
||||
if(this.config.recvFileName) {
|
||||
this.recvFileName = this.config.recvFileName;
|
||||
}
|
||||
|
||||
if(this.config.recvDirectory) {
|
||||
this.recvDirectory = this.config.recvDirectory;
|
||||
}
|
||||
}
|
||||
if(this.config.recvDirectory) {
|
||||
this.recvDirectory = this.config.recvDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
this.protocolConfig = this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
|
||||
this.direction = this.direction || 'send';
|
||||
this.sendQueue = this.sendQueue || [];
|
||||
this.protocolConfig = this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
|
||||
this.direction = this.direction || 'send';
|
||||
this.sendQueue = this.sendQueue || [];
|
||||
|
||||
// Ensure sendQueue is an array of objects that contain at least a 'path' member
|
||||
this.sendQueue = this.sendQueue.map(item => {
|
||||
if(_.isString(item)) {
|
||||
return { path : item };
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
// Ensure sendQueue is an array of objects that contain at least a 'path' member
|
||||
this.sendQueue = this.sendQueue.map(item => {
|
||||
if(_.isString(item)) {
|
||||
return { path : item };
|
||||
} else {
|
||||
return item;
|
||||
}
|
||||
});
|
||||
|
||||
this.sentFileIds = [];
|
||||
}
|
||||
this.sentFileIds = [];
|
||||
}
|
||||
|
||||
isSending() {
|
||||
return ('send' === this.direction);
|
||||
}
|
||||
isSending() {
|
||||
return ('send' === this.direction);
|
||||
}
|
||||
|
||||
restorePipeAfterExternalProc() {
|
||||
if(!this.pipeRestored) {
|
||||
this.pipeRestored = true;
|
||||
restorePipeAfterExternalProc() {
|
||||
if(!this.pipeRestored) {
|
||||
this.pipeRestored = true;
|
||||
|
||||
this.client.restoreDataHandler();
|
||||
}
|
||||
}
|
||||
this.client.restoreDataHandler();
|
||||
}
|
||||
}
|
||||
|
||||
sendFiles(cb) {
|
||||
// assume *sending* can always batch
|
||||
// :TODO: Look into this further
|
||||
const allFiles = this.sendQueue.map(f => f.path);
|
||||
this.executeExternalProtocolHandlerForSend(allFiles, err => {
|
||||
if(err) {
|
||||
this.client.log.warn( { files : allFiles, error : err.message }, 'Error sending file(s)' );
|
||||
} else {
|
||||
const sentFiles = [];
|
||||
this.sendQueue.forEach(f => {
|
||||
f.sent = true;
|
||||
sentFiles.push(f.path);
|
||||
sendFiles(cb) {
|
||||
// assume *sending* can always batch
|
||||
// :TODO: Look into this further
|
||||
const allFiles = this.sendQueue.map(f => f.path);
|
||||
this.executeExternalProtocolHandlerForSend(allFiles, err => {
|
||||
if(err) {
|
||||
this.client.log.warn( { files : allFiles, error : err.message }, 'Error sending file(s)' );
|
||||
} else {
|
||||
const sentFiles = [];
|
||||
this.sendQueue.forEach(f => {
|
||||
f.sent = true;
|
||||
sentFiles.push(f.path);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` );
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` );
|
||||
}
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
sendFiles(cb) {
|
||||
// :TODO: built in/native protocol support
|
||||
|
||||
|
@ -189,408 +189,408 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
|||
}
|
||||
*/
|
||||
|
||||
moveFileWithCollisionHandling(src, dst, cb) {
|
||||
//
|
||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||
// in the case of collisions.
|
||||
//
|
||||
const dstPath = paths.dirname(dst);
|
||||
const dstFileExt = paths.extname(dst);
|
||||
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
||||
moveFileWithCollisionHandling(src, dst, cb) {
|
||||
//
|
||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||
// in the case of collisions.
|
||||
//
|
||||
const dstPath = paths.dirname(dst);
|
||||
const dstFileExt = paths.extname(dst);
|
||||
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
||||
|
||||
let renameIndex = 0;
|
||||
let movedOk = false;
|
||||
let tryDstPath;
|
||||
let renameIndex = 0;
|
||||
let movedOk = false;
|
||||
let tryDstPath;
|
||||
|
||||
async.until(
|
||||
() => movedOk, // until moved OK
|
||||
(cb) => {
|
||||
if(0 === renameIndex) {
|
||||
// try originally supplied path first
|
||||
tryDstPath = dst;
|
||||
} else {
|
||||
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
||||
}
|
||||
async.until(
|
||||
() => movedOk, // until moved OK
|
||||
(cb) => {
|
||||
if(0 === renameIndex) {
|
||||
// try originally supplied path first
|
||||
tryDstPath = dst;
|
||||
} else {
|
||||
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
||||
}
|
||||
|
||||
fse.move(src, tryDstPath, err => {
|
||||
if(err) {
|
||||
if('EEXIST' === err.code) {
|
||||
renameIndex += 1;
|
||||
return cb(null); // keep trying
|
||||
}
|
||||
fse.move(src, tryDstPath, err => {
|
||||
if(err) {
|
||||
if('EEXIST' === err.code) {
|
||||
renameIndex += 1;
|
||||
return cb(null); // keep trying
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
movedOk = true;
|
||||
return cb(null, tryDstPath);
|
||||
});
|
||||
},
|
||||
(err, finalPath) => {
|
||||
return cb(err, finalPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
movedOk = true;
|
||||
return cb(null, tryDstPath);
|
||||
});
|
||||
},
|
||||
(err, finalPath) => {
|
||||
return cb(err, finalPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
recvFiles(cb) {
|
||||
this.executeExternalProtocolHandlerForRecv(err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
recvFiles(cb) {
|
||||
this.executeExternalProtocolHandlerForRecv(err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.recvFilePaths = [];
|
||||
this.recvFilePaths = [];
|
||||
|
||||
if(this.recvFileName) {
|
||||
//
|
||||
// file name specified - we expect a single file in |this.recvDirectory|
|
||||
// by the name of |this.recvFileName|
|
||||
//
|
||||
const recvFullPath = paths.join(this.recvDirectory, this.recvFileName);
|
||||
fs.stat(recvFullPath, (err, stats) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
if(this.recvFileName) {
|
||||
//
|
||||
// file name specified - we expect a single file in |this.recvDirectory|
|
||||
// by the name of |this.recvFileName|
|
||||
//
|
||||
const recvFullPath = paths.join(this.recvDirectory, this.recvFileName);
|
||||
fs.stat(recvFullPath, (err, stats) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(!stats.isFile()) {
|
||||
return cb(Errors.Invalid('Expected file entry in recv directory'));
|
||||
}
|
||||
if(!stats.isFile()) {
|
||||
return cb(Errors.Invalid('Expected file entry in recv directory'));
|
||||
}
|
||||
|
||||
this.recvFilePaths.push(recvFullPath);
|
||||
return cb(null);
|
||||
});
|
||||
} else {
|
||||
//
|
||||
// Blind Upload (recv): files in |this.recvDirectory| should be named appropriately already
|
||||
//
|
||||
fs.readdir(this.recvDirectory, (err, files) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
this.recvFilePaths.push(recvFullPath);
|
||||
return cb(null);
|
||||
});
|
||||
} else {
|
||||
//
|
||||
// Blind Upload (recv): files in |this.recvDirectory| should be named appropriately already
|
||||
//
|
||||
fs.readdir(this.recvDirectory, (err, files) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
// stat each to grab files only
|
||||
async.each(files, (fileName, nextFile) => {
|
||||
const recvFullPath = paths.join(this.recvDirectory, fileName);
|
||||
// stat each to grab files only
|
||||
async.each(files, (fileName, nextFile) => {
|
||||
const recvFullPath = paths.join(this.recvDirectory, fileName);
|
||||
|
||||
fs.stat(recvFullPath, (err, stats) => {
|
||||
if(err) {
|
||||
this.client.log.warn('Failed to stat file', { path : recvFullPath } );
|
||||
return nextFile(null); // just try the next one
|
||||
}
|
||||
fs.stat(recvFullPath, (err, stats) => {
|
||||
if(err) {
|
||||
this.client.log.warn('Failed to stat file', { path : recvFullPath } );
|
||||
return nextFile(null); // just try the next one
|
||||
}
|
||||
|
||||
if(stats.isFile()) {
|
||||
this.recvFilePaths.push(recvFullPath);
|
||||
}
|
||||
if(stats.isFile()) {
|
||||
this.recvFilePaths.push(recvFullPath);
|
||||
}
|
||||
|
||||
return nextFile(null);
|
||||
});
|
||||
}, () => {
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return nextFile(null);
|
||||
});
|
||||
}, () => {
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pathWithTerminatingSeparator(path) {
|
||||
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
||||
path = path + paths.sep;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
pathWithTerminatingSeparator(path) {
|
||||
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
||||
path = path + paths.sep;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
prepAndBuildSendArgs(filePaths, cb) {
|
||||
const externalArgs = this.protocolConfig.external['sendArgs'];
|
||||
prepAndBuildSendArgs(filePaths, cb) {
|
||||
const externalArgs = this.protocolConfig.external['sendArgs'];
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function getTempFileListPath(callback) {
|
||||
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) );
|
||||
if(!hasFileList) {
|
||||
return callback(null, null);
|
||||
}
|
||||
async.waterfall(
|
||||
[
|
||||
function getTempFileListPath(callback) {
|
||||
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) );
|
||||
if(!hasFileList) {
|
||||
return callback(null, null);
|
||||
}
|
||||
|
||||
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
|
||||
if(err) {
|
||||
return callback(err); // failed to create it
|
||||
}
|
||||
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
|
||||
if(err) {
|
||||
return callback(err); // failed to create it
|
||||
}
|
||||
|
||||
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL));
|
||||
fs.close(tempFileInfo.fd, err => {
|
||||
return callback(err, tempFileInfo.path);
|
||||
});
|
||||
});
|
||||
},
|
||||
function createArgs(tempFileListPath, callback) {
|
||||
// initial args: ignore {filePaths} as we must break that into it's own sep array items
|
||||
const args = externalArgs.map(arg => {
|
||||
return '{filePaths}' === arg ? arg : stringFormat(arg, {
|
||||
fileListPath : tempFileListPath || '',
|
||||
});
|
||||
});
|
||||
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL));
|
||||
fs.close(tempFileInfo.fd, err => {
|
||||
return callback(err, tempFileInfo.path);
|
||||
});
|
||||
});
|
||||
},
|
||||
function createArgs(tempFileListPath, callback) {
|
||||
// initial args: ignore {filePaths} as we must break that into it's own sep array items
|
||||
const args = externalArgs.map(arg => {
|
||||
return '{filePaths}' === arg ? arg : stringFormat(arg, {
|
||||
fileListPath : tempFileListPath || '',
|
||||
});
|
||||
});
|
||||
|
||||
const filePathsPos = args.indexOf('{filePaths}');
|
||||
if(filePathsPos > -1) {
|
||||
// replace {filePaths} with 0:n individual entries in |args|
|
||||
args.splice.apply( args, [ filePathsPos, 1 ].concat(filePaths) );
|
||||
}
|
||||
const filePathsPos = args.indexOf('{filePaths}');
|
||||
if(filePathsPos > -1) {
|
||||
// replace {filePaths} with 0:n individual entries in |args|
|
||||
args.splice.apply( args, [ filePathsPos, 1 ].concat(filePaths) );
|
||||
}
|
||||
|
||||
return callback(null, args);
|
||||
}
|
||||
],
|
||||
(err, args) => {
|
||||
return cb(err, args);
|
||||
}
|
||||
);
|
||||
}
|
||||
return callback(null, args);
|
||||
}
|
||||
],
|
||||
(err, args) => {
|
||||
return cb(err, args);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
prepAndBuildRecvArgs(cb) {
|
||||
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
|
||||
const externalArgs = this.protocolConfig.external[argsKey];
|
||||
const args = externalArgs.map(arg => stringFormat(arg, {
|
||||
uploadDir : this.recvDirectory,
|
||||
fileName : this.recvFileName || '',
|
||||
}));
|
||||
prepAndBuildRecvArgs(cb) {
|
||||
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
|
||||
const externalArgs = this.protocolConfig.external[argsKey];
|
||||
const args = externalArgs.map(arg => stringFormat(arg, {
|
||||
uploadDir : this.recvDirectory,
|
||||
fileName : this.recvFileName || '',
|
||||
}));
|
||||
|
||||
return cb(null, args);
|
||||
}
|
||||
return cb(null, args);
|
||||
}
|
||||
|
||||
executeExternalProtocolHandler(args, cb) {
|
||||
const external = this.protocolConfig.external;
|
||||
const cmd = external[`${this.direction}Cmd`];
|
||||
executeExternalProtocolHandler(args, cb) {
|
||||
const external = this.protocolConfig.external;
|
||||
const cmd = external[`${this.direction}Cmd`];
|
||||
|
||||
this.client.log.debug(
|
||||
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
|
||||
'Executing external protocol'
|
||||
);
|
||||
this.client.log.debug(
|
||||
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
|
||||
'Executing external protocol'
|
||||
);
|
||||
|
||||
const spawnOpts = {
|
||||
cols : this.client.term.termWidth,
|
||||
rows : this.client.term.termHeight,
|
||||
cwd : this.recvDirectory,
|
||||
encoding : null, // don't bork our data!
|
||||
};
|
||||
const spawnOpts = {
|
||||
cols : this.client.term.termWidth,
|
||||
rows : this.client.term.termHeight,
|
||||
cwd : this.recvDirectory,
|
||||
encoding : null, // don't bork our data!
|
||||
};
|
||||
|
||||
const externalProc = pty.spawn(cmd, args, spawnOpts);
|
||||
const externalProc = pty.spawn(cmd, args, spawnOpts);
|
||||
|
||||
this.client.setTemporaryDirectDataHandler(data => {
|
||||
// needed for things like sz/rz
|
||||
if(external.escapeTelnet) {
|
||||
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
|
||||
externalProc.write(Buffer.from(tmp, 'binary'));
|
||||
} else {
|
||||
externalProc.write(data);
|
||||
}
|
||||
});
|
||||
this.client.setTemporaryDirectDataHandler(data => {
|
||||
// needed for things like sz/rz
|
||||
if(external.escapeTelnet) {
|
||||
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
|
||||
externalProc.write(Buffer.from(tmp, 'binary'));
|
||||
} else {
|
||||
externalProc.write(data);
|
||||
}
|
||||
});
|
||||
|
||||
externalProc.on('data', data => {
|
||||
// needed for things like sz/rz
|
||||
if(external.escapeTelnet) {
|
||||
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape
|
||||
this.client.term.rawWrite(Buffer.from(tmp, 'binary'));
|
||||
} else {
|
||||
this.client.term.rawWrite(data);
|
||||
}
|
||||
});
|
||||
externalProc.on('data', data => {
|
||||
// needed for things like sz/rz
|
||||
if(external.escapeTelnet) {
|
||||
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape
|
||||
this.client.term.rawWrite(Buffer.from(tmp, 'binary'));
|
||||
} else {
|
||||
this.client.term.rawWrite(data);
|
||||
}
|
||||
});
|
||||
|
||||
externalProc.once('close', () => {
|
||||
return this.restorePipeAfterExternalProc();
|
||||
});
|
||||
externalProc.once('close', () => {
|
||||
return this.restorePipeAfterExternalProc();
|
||||
});
|
||||
|
||||
externalProc.once('exit', (exitCode) => {
|
||||
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
|
||||
externalProc.once('exit', (exitCode) => {
|
||||
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
|
||||
|
||||
this.restorePipeAfterExternalProc();
|
||||
externalProc.removeAllListeners();
|
||||
this.restorePipeAfterExternalProc();
|
||||
externalProc.removeAllListeners();
|
||||
|
||||
return cb(exitCode ? Errors.ExternalProcess(`Process exited with exit code ${exitCode}`, 'EBADEXIT') : null);
|
||||
});
|
||||
}
|
||||
return cb(exitCode ? Errors.ExternalProcess(`Process exited with exit code ${exitCode}`, 'EBADEXIT') : null);
|
||||
});
|
||||
}
|
||||
|
||||
executeExternalProtocolHandlerForSend(filePaths, cb) {
|
||||
if(!Array.isArray(filePaths)) {
|
||||
filePaths = [ filePaths ];
|
||||
}
|
||||
executeExternalProtocolHandlerForSend(filePaths, cb) {
|
||||
if(!Array.isArray(filePaths)) {
|
||||
filePaths = [ filePaths ];
|
||||
}
|
||||
|
||||
this.prepAndBuildSendArgs(filePaths, (err, args) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
this.prepAndBuildSendArgs(filePaths, (err, args) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.executeExternalProtocolHandler(args, err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.executeExternalProtocolHandler(args, err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
executeExternalProtocolHandlerForRecv(cb) {
|
||||
this.prepAndBuildRecvArgs( (err, args) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
executeExternalProtocolHandlerForRecv(cb) {
|
||||
this.prepAndBuildRecvArgs( (err, args) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.executeExternalProtocolHandler(args, err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.executeExternalProtocolHandler(args, err => {
|
||||
return cb(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
if(this.isSending()) {
|
||||
return { sentFileIds : this.sentFileIds };
|
||||
} else {
|
||||
return { recvFilePaths : this.recvFilePaths };
|
||||
}
|
||||
}
|
||||
getMenuResult() {
|
||||
if(this.isSending()) {
|
||||
return { sentFileIds : this.sentFileIds };
|
||||
} else {
|
||||
return { recvFilePaths : this.recvFilePaths };
|
||||
}
|
||||
}
|
||||
|
||||
updateSendStats(cb) {
|
||||
let downloadBytes = 0;
|
||||
let downloadCount = 0;
|
||||
let fileIds = [];
|
||||
updateSendStats(cb) {
|
||||
let downloadBytes = 0;
|
||||
let downloadCount = 0;
|
||||
let fileIds = [];
|
||||
|
||||
async.each(this.sendQueue, (queueItem, next) => {
|
||||
if(!queueItem.sent) {
|
||||
return next(null);
|
||||
}
|
||||
async.each(this.sendQueue, (queueItem, next) => {
|
||||
if(!queueItem.sent) {
|
||||
return next(null);
|
||||
}
|
||||
|
||||
if(queueItem.fileId) {
|
||||
fileIds.push(queueItem.fileId);
|
||||
}
|
||||
if(queueItem.fileId) {
|
||||
fileIds.push(queueItem.fileId);
|
||||
}
|
||||
|
||||
if(_.isNumber(queueItem.byteSize)) {
|
||||
downloadCount += 1;
|
||||
downloadBytes += queueItem.byteSize;
|
||||
return next(null);
|
||||
}
|
||||
if(_.isNumber(queueItem.byteSize)) {
|
||||
downloadCount += 1;
|
||||
downloadBytes += queueItem.byteSize;
|
||||
return next(null);
|
||||
}
|
||||
|
||||
// we just have a path - figure it out
|
||||
fs.stat(queueItem.path, (err, stats) => {
|
||||
if(err) {
|
||||
this.client.log.warn( { error : err.message, path : queueItem.path }, 'File stat failed' );
|
||||
} else {
|
||||
downloadCount += 1;
|
||||
downloadBytes += stats.size;
|
||||
}
|
||||
// we just have a path - figure it out
|
||||
fs.stat(queueItem.path, (err, stats) => {
|
||||
if(err) {
|
||||
this.client.log.warn( { error : err.message, path : queueItem.path }, 'File stat failed' );
|
||||
} else {
|
||||
downloadCount += 1;
|
||||
downloadBytes += stats.size;
|
||||
}
|
||||
|
||||
return next(null);
|
||||
});
|
||||
}, () => {
|
||||
// All stats/meta currently updated via fire & forget - if this is ever a issue, we can wait for callbacks
|
||||
StatLog.incrementUserStat(this.client.user, 'dl_total_count', downloadCount);
|
||||
StatLog.incrementUserStat(this.client.user, 'dl_total_bytes', downloadBytes);
|
||||
StatLog.incrementSystemStat('dl_total_count', downloadCount);
|
||||
StatLog.incrementSystemStat('dl_total_bytes', downloadBytes);
|
||||
return next(null);
|
||||
});
|
||||
}, () => {
|
||||
// All stats/meta currently updated via fire & forget - if this is ever a issue, we can wait for callbacks
|
||||
StatLog.incrementUserStat(this.client.user, 'dl_total_count', downloadCount);
|
||||
StatLog.incrementUserStat(this.client.user, 'dl_total_bytes', downloadBytes);
|
||||
StatLog.incrementSystemStat('dl_total_count', downloadCount);
|
||||
StatLog.incrementSystemStat('dl_total_bytes', downloadBytes);
|
||||
|
||||
fileIds.forEach(fileId => {
|
||||
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
|
||||
});
|
||||
fileIds.forEach(fileId => {
|
||||
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
|
||||
});
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
updateRecvStats(cb) {
|
||||
let uploadBytes = 0;
|
||||
let uploadCount = 0;
|
||||
updateRecvStats(cb) {
|
||||
let uploadBytes = 0;
|
||||
let uploadCount = 0;
|
||||
|
||||
async.each(this.recvFilePaths, (filePath, next) => {
|
||||
// we just have a path - figure it out
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err) {
|
||||
this.client.log.warn( { error : err.message, path : filePath }, 'File stat failed' );
|
||||
} else {
|
||||
uploadCount += 1;
|
||||
uploadBytes += stats.size;
|
||||
}
|
||||
async.each(this.recvFilePaths, (filePath, next) => {
|
||||
// we just have a path - figure it out
|
||||
fs.stat(filePath, (err, stats) => {
|
||||
if(err) {
|
||||
this.client.log.warn( { error : err.message, path : filePath }, 'File stat failed' );
|
||||
} else {
|
||||
uploadCount += 1;
|
||||
uploadBytes += stats.size;
|
||||
}
|
||||
|
||||
return next(null);
|
||||
});
|
||||
}, () => {
|
||||
StatLog.incrementUserStat(this.client.user, 'ul_total_count', uploadCount);
|
||||
StatLog.incrementUserStat(this.client.user, 'ul_total_bytes', uploadBytes);
|
||||
StatLog.incrementSystemStat('ul_total_count', uploadCount);
|
||||
StatLog.incrementSystemStat('ul_total_bytes', uploadBytes);
|
||||
return next(null);
|
||||
});
|
||||
}, () => {
|
||||
StatLog.incrementUserStat(this.client.user, 'ul_total_count', uploadCount);
|
||||
StatLog.incrementUserStat(this.client.user, 'ul_total_bytes', uploadBytes);
|
||||
StatLog.incrementSystemStat('ul_total_count', uploadCount);
|
||||
StatLog.incrementSystemStat('ul_total_bytes', uploadBytes);
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
// :TODO: break this up to send|recv
|
||||
// :TODO: break this up to send|recv
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(self.isSending()) {
|
||||
if(!Array.isArray(self.sendQueue)) {
|
||||
self.sendQueue = [ self.sendQueue ];
|
||||
}
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
if(self.isSending()) {
|
||||
if(!Array.isArray(self.sendQueue)) {
|
||||
self.sendQueue = [ self.sendQueue ];
|
||||
}
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function transferFiles(callback) {
|
||||
if(self.isSending()) {
|
||||
self.sendFiles( err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function transferFiles(callback) {
|
||||
if(self.isSending()) {
|
||||
self.sendFiles( err => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
const sentFileIds = [];
|
||||
self.sendQueue.forEach(queueItem => {
|
||||
if(queueItem.sent && queueItem.fileId) {
|
||||
sentFileIds.push(queueItem.fileId);
|
||||
}
|
||||
});
|
||||
const sentFileIds = [];
|
||||
self.sendQueue.forEach(queueItem => {
|
||||
if(queueItem.sent && queueItem.fileId) {
|
||||
sentFileIds.push(queueItem.fileId);
|
||||
}
|
||||
});
|
||||
|
||||
if(sentFileIds.length > 0) {
|
||||
// remove items we sent from the D/L queue
|
||||
const dlQueue = new DownloadQueue(self.client);
|
||||
const dlFileEntries = dlQueue.removeItems(sentFileIds);
|
||||
if(sentFileIds.length > 0) {
|
||||
// remove items we sent from the D/L queue
|
||||
const dlQueue = new DownloadQueue(self.client);
|
||||
const dlFileEntries = dlQueue.removeItems(sentFileIds);
|
||||
|
||||
// fire event for downloaded entries
|
||||
Events.emit(
|
||||
Events.getSystemEvents().UserDownload,
|
||||
{
|
||||
user : self.client.user,
|
||||
files : dlFileEntries
|
||||
}
|
||||
);
|
||||
// fire event for downloaded entries
|
||||
Events.emit(
|
||||
Events.getSystemEvents().UserDownload,
|
||||
{
|
||||
user : self.client.user,
|
||||
files : dlFileEntries
|
||||
}
|
||||
);
|
||||
|
||||
self.sentFileIds = sentFileIds;
|
||||
}
|
||||
self.sentFileIds = sentFileIds;
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
});
|
||||
} else {
|
||||
self.recvFiles( err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
function cleanupTempFiles(callback) {
|
||||
temptmp.cleanup( paths => {
|
||||
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
||||
});
|
||||
return callback(null);
|
||||
});
|
||||
} else {
|
||||
self.recvFiles( err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
},
|
||||
function cleanupTempFiles(callback) {
|
||||
temptmp.cleanup( paths => {
|
||||
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
||||
});
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function updateUserAndSystemStats(callback) {
|
||||
if(self.isSending()) {
|
||||
return self.updateSendStats(callback);
|
||||
} else {
|
||||
return self.updateRecvStats(callback);
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'File transfer error');
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function updateUserAndSystemStats(callback) {
|
||||
if(self.isSending()) {
|
||||
return self.updateSendStats(callback);
|
||||
} else {
|
||||
return self.updateRecvStats(callback);
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'File transfer error');
|
||||
}
|
||||
|
||||
return self.prevMenu();
|
||||
}
|
||||
);
|
||||
}
|
||||
return self.prevMenu();
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,147 +12,147 @@ const async = require('async');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File transfer protocol selection',
|
||||
desc : 'Select protocol / method for file transfer',
|
||||
author : 'NuSkooler',
|
||||
name : 'File transfer protocol selection',
|
||||
desc : 'Select protocol / method for file transfer',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
protList : 1,
|
||||
protList : 1,
|
||||
};
|
||||
|
||||
exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.config = this.menuConfig.config || {};
|
||||
this.config = this.menuConfig.config || {};
|
||||
|
||||
if(options.extraArgs) {
|
||||
if(options.extraArgs.direction) {
|
||||
this.config.direction = options.extraArgs.direction;
|
||||
}
|
||||
}
|
||||
if(options.extraArgs) {
|
||||
if(options.extraArgs.direction) {
|
||||
this.config.direction = options.extraArgs.direction;
|
||||
}
|
||||
}
|
||||
|
||||
this.config.direction = this.config.direction || 'send';
|
||||
this.config.direction = this.config.direction || 'send';
|
||||
|
||||
this.extraArgs = options.extraArgs;
|
||||
this.extraArgs = options.extraArgs;
|
||||
|
||||
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
||||
}
|
||||
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
||||
}
|
||||
|
||||
if(_.has(options, 'lastMenuResult.recvFilePaths')) {
|
||||
this.recvFilePaths = options.lastMenuResult.recvFilePaths;
|
||||
}
|
||||
if(_.has(options, 'lastMenuResult.recvFilePaths')) {
|
||||
this.recvFilePaths = options.lastMenuResult.recvFilePaths;
|
||||
}
|
||||
|
||||
this.fallbackOnly = options.lastMenuResult ? true : false;
|
||||
this.fallbackOnly = options.lastMenuResult ? true : false;
|
||||
|
||||
this.loadAvailProtocols();
|
||||
this.loadAvailProtocols();
|
||||
|
||||
this.menuMethods = {
|
||||
selectProtocol : (formData, extraArgs, cb) => {
|
||||
const protocol = this.protocols[formData.value.protocol];
|
||||
const finalExtraArgs = this.extraArgs || {};
|
||||
Object.assign(finalExtraArgs, { protocol : protocol.protocol, direction : this.config.direction }, extraArgs );
|
||||
this.menuMethods = {
|
||||
selectProtocol : (formData, extraArgs, cb) => {
|
||||
const protocol = this.protocols[formData.value.protocol];
|
||||
const finalExtraArgs = this.extraArgs || {};
|
||||
Object.assign(finalExtraArgs, { protocol : protocol.protocol, direction : this.config.direction }, extraArgs );
|
||||
|
||||
const modOpts = {
|
||||
extraArgs : finalExtraArgs,
|
||||
};
|
||||
const modOpts = {
|
||||
extraArgs : finalExtraArgs,
|
||||
};
|
||||
|
||||
if('send' === this.config.direction) {
|
||||
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
|
||||
} else {
|
||||
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
if('send' === this.config.direction) {
|
||||
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
|
||||
} else {
|
||||
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
if(this.sentFileIds) {
|
||||
return { sentFileIds : this.sentFileIds };
|
||||
}
|
||||
getMenuResult() {
|
||||
if(this.sentFileIds) {
|
||||
return { sentFileIds : this.sentFileIds };
|
||||
}
|
||||
|
||||
if(this.recvFilePaths) {
|
||||
return { recvFilePaths : this.recvFilePaths };
|
||||
}
|
||||
}
|
||||
if(this.recvFilePaths) {
|
||||
return { recvFilePaths : this.recvFilePaths };
|
||||
}
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
if(this.sentFileIds || this.recvFilePaths) {
|
||||
// nothing to do here; move along (we're just falling through)
|
||||
this.prevMenu();
|
||||
} else {
|
||||
super.initSequence();
|
||||
}
|
||||
}
|
||||
initSequence() {
|
||||
if(this.sentFileIds || this.recvFilePaths) {
|
||||
// nothing to do here; move along (we're just falling through)
|
||||
this.prevMenu();
|
||||
} else {
|
||||
super.initSequence();
|
||||
}
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu
|
||||
};
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const protListView = vc.getView(MciViewIds.protList);
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const protListView = vc.getView(MciViewIds.protList);
|
||||
|
||||
const protListFormat = self.config.protListFormat || '{name}';
|
||||
const protListFocusFormat = self.config.protListFocusFormat || protListFormat;
|
||||
const protListFormat = self.config.protListFormat || '{name}';
|
||||
const protListFocusFormat = self.config.protListFocusFormat || protListFormat;
|
||||
|
||||
protListView.setItems(self.protocols.map(p => stringFormat(protListFormat, p) ) );
|
||||
protListView.setFocusItems(self.protocols.map(p => stringFormat(protListFocusFormat, p) ) );
|
||||
protListView.setItems(self.protocols.map(p => stringFormat(protListFormat, p) ) );
|
||||
protListView.setFocusItems(self.protocols.map(p => stringFormat(protListFocusFormat, p) ) );
|
||||
|
||||
protListView.redraw();
|
||||
protListView.redraw();
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
loadAvailProtocols() {
|
||||
this.protocols = _.map(Config().fileTransferProtocols, (protInfo, protocol) => {
|
||||
return {
|
||||
protocol : protocol,
|
||||
name : protInfo.name,
|
||||
hasBatch : _.has(protInfo, 'external.recvArgs'),
|
||||
hasNonBatch : _.has(protInfo, 'external.recvArgsNonBatch'),
|
||||
sort : protInfo.sort,
|
||||
};
|
||||
});
|
||||
loadAvailProtocols() {
|
||||
this.protocols = _.map(Config().fileTransferProtocols, (protInfo, protocol) => {
|
||||
return {
|
||||
protocol : protocol,
|
||||
name : protInfo.name,
|
||||
hasBatch : _.has(protInfo, 'external.recvArgs'),
|
||||
hasNonBatch : _.has(protInfo, 'external.recvArgsNonBatch'),
|
||||
sort : protInfo.sort,
|
||||
};
|
||||
});
|
||||
|
||||
// Filter out batch vs non-batch only protocols
|
||||
if(this.extraArgs.recvFileName) { // non-batch aka non-blind
|
||||
this.protocols = this.protocols.filter( prot => prot.hasNonBatch );
|
||||
} else {
|
||||
this.protocols = this.protocols.filter( prot => prot.hasBatch );
|
||||
}
|
||||
// Filter out batch vs non-batch only protocols
|
||||
if(this.extraArgs.recvFileName) { // non-batch aka non-blind
|
||||
this.protocols = this.protocols.filter( prot => prot.hasNonBatch );
|
||||
} else {
|
||||
this.protocols = this.protocols.filter( prot => prot.hasBatch );
|
||||
}
|
||||
|
||||
// natural sort taking explicit orders into consideration
|
||||
this.protocols.sort( (a, b) => {
|
||||
if(_.isNumber(a.sort) && _.isNumber(b.sort)) {
|
||||
return a.sort - b.sort;
|
||||
} else {
|
||||
return a.name.localeCompare(b.name, { sensitivity : false, numeric : true } );
|
||||
}
|
||||
});
|
||||
}
|
||||
// natural sort taking explicit orders into consideration
|
||||
this.protocols.sort( (a, b) => {
|
||||
if(_.isNumber(a.sort) && _.isNumber(b.sort)) {
|
||||
return a.sort - b.sort;
|
||||
} else {
|
||||
return a.name.localeCompare(b.name, { sensitivity : false, numeric : true } );
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,59 +14,59 @@ exports.copyFileWithCollisionHandling = copyFileWithCollisionHandling;
|
|||
exports.pathWithTerminatingSeparator = pathWithTerminatingSeparator;
|
||||
|
||||
function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
|
||||
operation = operation || 'copy';
|
||||
const dstPath = paths.dirname(dst);
|
||||
const dstFileExt = paths.extname(dst);
|
||||
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
||||
operation = operation || 'copy';
|
||||
const dstPath = paths.dirname(dst);
|
||||
const dstFileExt = paths.extname(dst);
|
||||
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
||||
|
||||
EnigAssert('move' === operation || 'copy' === operation);
|
||||
EnigAssert('move' === operation || 'copy' === operation);
|
||||
|
||||
let renameIndex = 0;
|
||||
let opOk = false;
|
||||
let tryDstPath;
|
||||
let renameIndex = 0;
|
||||
let opOk = false;
|
||||
let tryDstPath;
|
||||
|
||||
function tryOperation(src, dst, callback) {
|
||||
if('move' === operation) {
|
||||
fse.move(src, tryDstPath, err => {
|
||||
return callback(err);
|
||||
});
|
||||
} else if('copy' === operation) {
|
||||
fse.copy(src, tryDstPath, { overwrite : false, errorOnExist : true }, err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
function tryOperation(src, dst, callback) {
|
||||
if('move' === operation) {
|
||||
fse.move(src, tryDstPath, err => {
|
||||
return callback(err);
|
||||
});
|
||||
} else if('copy' === operation) {
|
||||
fse.copy(src, tryDstPath, { overwrite : false, errorOnExist : true }, err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async.until(
|
||||
() => opOk, // until moved OK
|
||||
(cb) => {
|
||||
if(0 === renameIndex) {
|
||||
// try originally supplied path first
|
||||
tryDstPath = dst;
|
||||
} else {
|
||||
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
||||
}
|
||||
async.until(
|
||||
() => opOk, // until moved OK
|
||||
(cb) => {
|
||||
if(0 === renameIndex) {
|
||||
// try originally supplied path first
|
||||
tryDstPath = dst;
|
||||
} else {
|
||||
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
||||
}
|
||||
|
||||
tryOperation(src, tryDstPath, err => {
|
||||
if(err) {
|
||||
// for some reason fs-extra copy doesn't pass err.code
|
||||
// :TODO: this is dangerous: submit a PR to fs-extra to set EEXIST
|
||||
if('EEXIST' === err.code || 'copy' === operation) {
|
||||
renameIndex += 1;
|
||||
return cb(null); // keep trying
|
||||
}
|
||||
tryOperation(src, tryDstPath, err => {
|
||||
if(err) {
|
||||
// for some reason fs-extra copy doesn't pass err.code
|
||||
// :TODO: this is dangerous: submit a PR to fs-extra to set EEXIST
|
||||
if('EEXIST' === err.code || 'copy' === operation) {
|
||||
renameIndex += 1;
|
||||
return cb(null); // keep trying
|
||||
}
|
||||
|
||||
return cb(err);
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
opOk = true;
|
||||
return cb(null, tryDstPath);
|
||||
});
|
||||
},
|
||||
(err, finalPath) => {
|
||||
return cb(err, finalPath);
|
||||
}
|
||||
);
|
||||
opOk = true;
|
||||
return cb(null, tryDstPath);
|
||||
});
|
||||
},
|
||||
(err, finalPath) => {
|
||||
return cb(err, finalPath);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -74,16 +74,16 @@ function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
|
|||
// in the case of collisions.
|
||||
//
|
||||
function moveFileWithCollisionHandling(src, dst, cb) {
|
||||
return moveOrCopyFileWithCollisionHandling(src, dst, 'move', cb);
|
||||
return moveOrCopyFileWithCollisionHandling(src, dst, 'move', cb);
|
||||
}
|
||||
|
||||
function copyFileWithCollisionHandling(src, dst, cb) {
|
||||
return moveOrCopyFileWithCollisionHandling(src, dst, 'copy', cb);
|
||||
return moveOrCopyFileWithCollisionHandling(src, dst, 'copy', cb);
|
||||
}
|
||||
|
||||
function pathWithTerminatingSeparator(path) {
|
||||
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
||||
path = path + paths.sep;
|
||||
}
|
||||
return path;
|
||||
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
||||
path = path + paths.sep;
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
|
|
@ -5,46 +5,46 @@ let _ = require('lodash');
|
|||
|
||||
// FNV-1a based on work here: https://github.com/wiedi/node-fnv
|
||||
module.exports = class FNV1a {
|
||||
constructor(data) {
|
||||
this.hash = 0x811c9dc5;
|
||||
constructor(data) {
|
||||
this.hash = 0x811c9dc5;
|
||||
|
||||
if(!_.isUndefined(data)) {
|
||||
this.update(data);
|
||||
}
|
||||
}
|
||||
if(!_.isUndefined(data)) {
|
||||
this.update(data);
|
||||
}
|
||||
}
|
||||
|
||||
update(data) {
|
||||
if(_.isNumber(data)) {
|
||||
data = data.toString();
|
||||
}
|
||||
update(data) {
|
||||
if(_.isNumber(data)) {
|
||||
data = data.toString();
|
||||
}
|
||||
|
||||
if(_.isString(data)) {
|
||||
data = Buffer.from(data);
|
||||
}
|
||||
if(_.isString(data)) {
|
||||
data = Buffer.from(data);
|
||||
}
|
||||
|
||||
if(!Buffer.isBuffer(data)) {
|
||||
throw new Error('data must be String or Buffer!');
|
||||
}
|
||||
if(!Buffer.isBuffer(data)) {
|
||||
throw new Error('data must be String or Buffer!');
|
||||
}
|
||||
|
||||
for(let b of data) {
|
||||
this.hash = this.hash ^ b;
|
||||
this.hash +=
|
||||
for(let b of data) {
|
||||
this.hash = this.hash ^ b;
|
||||
this.hash +=
|
||||
(this.hash << 24) + (this.hash << 8) + (this.hash << 7) +
|
||||
(this.hash << 4) + (this.hash << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
digest(encoding) {
|
||||
encoding = encoding || 'binary';
|
||||
const buf = Buffer.alloc(4);
|
||||
buf.writeInt32BE(this.hash & 0xffffffff, 0);
|
||||
return buf.toString(encoding);
|
||||
}
|
||||
digest(encoding) {
|
||||
encoding = encoding || 'binary';
|
||||
const buf = Buffer.alloc(4);
|
||||
buf.writeInt32BE(this.hash & 0xffffffff, 0);
|
||||
return buf.toString(encoding);
|
||||
}
|
||||
|
||||
get value() {
|
||||
return this.hash & 0xffffffff;
|
||||
}
|
||||
get value() {
|
||||
return this.hash & 0xffffffff;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
1804
core/fse.js
1804
core/fse.js
File diff suppressed because it is too large
Load Diff
|
@ -7,94 +7,94 @@ const FTN_ADDRESS_REGEXP = /^([0-9]+:)?([0-9]+)(\/[0-9]+)?(\.[0-9]+)?(@[a-z0-9\-
|
|||
const FTN_PATTERN_REGEXP = /^([0-9*]+:)?([0-9*]+)(\/[0-9*]+)?(\.[0-9*]+)?(@[a-z0-9\-.*]+)?$/i;
|
||||
|
||||
module.exports = class Address {
|
||||
constructor(addr) {
|
||||
if(addr) {
|
||||
if(_.isObject(addr)) {
|
||||
Object.assign(this, addr);
|
||||
} else if(_.isString(addr)) {
|
||||
const temp = Address.fromString(addr);
|
||||
if(temp) {
|
||||
Object.assign(this, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
constructor(addr) {
|
||||
if(addr) {
|
||||
if(_.isObject(addr)) {
|
||||
Object.assign(this, addr);
|
||||
} else if(_.isString(addr)) {
|
||||
const temp = Address.fromString(addr);
|
||||
if(temp) {
|
||||
Object.assign(this, temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static isValidAddress(addr) {
|
||||
return addr && addr.isValid();
|
||||
}
|
||||
static isValidAddress(addr) {
|
||||
return addr && addr.isValid();
|
||||
}
|
||||
|
||||
isValid() {
|
||||
// FTN address is valid if we have at least a net/node
|
||||
return _.isNumber(this.net) && _.isNumber(this.node);
|
||||
}
|
||||
isValid() {
|
||||
// FTN address is valid if we have at least a net/node
|
||||
return _.isNumber(this.net) && _.isNumber(this.node);
|
||||
}
|
||||
|
||||
isEqual(other) {
|
||||
if(_.isString(other)) {
|
||||
other = Address.fromString(other);
|
||||
}
|
||||
isEqual(other) {
|
||||
if(_.isString(other)) {
|
||||
other = Address.fromString(other);
|
||||
}
|
||||
|
||||
return (
|
||||
this.net === other.net &&
|
||||
return (
|
||||
this.net === other.net &&
|
||||
this.node === other.node &&
|
||||
this.zone === other.zone &&
|
||||
this.point === other.point &&
|
||||
this.domain === other.domain
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getMatchAddr(pattern) {
|
||||
const m = FTN_PATTERN_REGEXP.exec(pattern);
|
||||
if(m) {
|
||||
let addr = { };
|
||||
getMatchAddr(pattern) {
|
||||
const m = FTN_PATTERN_REGEXP.exec(pattern);
|
||||
if(m) {
|
||||
let addr = { };
|
||||
|
||||
if(m[1]) {
|
||||
addr.zone = m[1].slice(0, -1);
|
||||
if('*' !== addr.zone) {
|
||||
addr.zone = parseInt(addr.zone);
|
||||
}
|
||||
} else {
|
||||
addr.zone = '*';
|
||||
}
|
||||
if(m[1]) {
|
||||
addr.zone = m[1].slice(0, -1);
|
||||
if('*' !== addr.zone) {
|
||||
addr.zone = parseInt(addr.zone);
|
||||
}
|
||||
} else {
|
||||
addr.zone = '*';
|
||||
}
|
||||
|
||||
if(m[2]) {
|
||||
addr.net = m[2];
|
||||
if('*' !== addr.net) {
|
||||
addr.net = parseInt(addr.net);
|
||||
}
|
||||
} else {
|
||||
addr.net = '*';
|
||||
}
|
||||
if(m[2]) {
|
||||
addr.net = m[2];
|
||||
if('*' !== addr.net) {
|
||||
addr.net = parseInt(addr.net);
|
||||
}
|
||||
} else {
|
||||
addr.net = '*';
|
||||
}
|
||||
|
||||
if(m[3]) {
|
||||
addr.node = m[3].substr(1);
|
||||
if('*' !== addr.node) {
|
||||
addr.node = parseInt(addr.node);
|
||||
}
|
||||
} else {
|
||||
addr.node = '*';
|
||||
}
|
||||
if(m[3]) {
|
||||
addr.node = m[3].substr(1);
|
||||
if('*' !== addr.node) {
|
||||
addr.node = parseInt(addr.node);
|
||||
}
|
||||
} else {
|
||||
addr.node = '*';
|
||||
}
|
||||
|
||||
if(m[4]) {
|
||||
addr.point = m[4].substr(1);
|
||||
if('*' !== addr.point) {
|
||||
addr.point = parseInt(addr.point);
|
||||
}
|
||||
} else {
|
||||
addr.point = '*';
|
||||
}
|
||||
if(m[4]) {
|
||||
addr.point = m[4].substr(1);
|
||||
if('*' !== addr.point) {
|
||||
addr.point = parseInt(addr.point);
|
||||
}
|
||||
} else {
|
||||
addr.point = '*';
|
||||
}
|
||||
|
||||
if(m[5]) {
|
||||
addr.domain = m[5].substr(1);
|
||||
} else {
|
||||
addr.domain = '*';
|
||||
}
|
||||
if(m[5]) {
|
||||
addr.domain = m[5].substr(1);
|
||||
} else {
|
||||
addr.domain = '*';
|
||||
}
|
||||
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
getMatchScore(pattern) {
|
||||
let score = 0;
|
||||
const addr = this.getMatchAddr(pattern);
|
||||
|
@ -116,92 +116,92 @@ module.exports = class Address {
|
|||
}
|
||||
*/
|
||||
|
||||
isPatternMatch(pattern) {
|
||||
const addr = this.getMatchAddr(pattern);
|
||||
if(addr) {
|
||||
return (
|
||||
('*' === addr.net || this.net === addr.net) &&
|
||||
isPatternMatch(pattern) {
|
||||
const addr = this.getMatchAddr(pattern);
|
||||
if(addr) {
|
||||
return (
|
||||
('*' === addr.net || this.net === addr.net) &&
|
||||
('*' === addr.node || this.node === addr.node) &&
|
||||
('*' === addr.zone || this.zone === addr.zone) &&
|
||||
('*' === addr.point || this.point === addr.point) &&
|
||||
('*' === addr.domain || this.domain === addr.domain)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static fromString(addrStr) {
|
||||
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
||||
static fromString(addrStr) {
|
||||
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
||||
|
||||
if(m) {
|
||||
// start with a 2D
|
||||
let addr = {
|
||||
net : parseInt(m[2]),
|
||||
node : parseInt(m[3].substr(1)),
|
||||
};
|
||||
if(m) {
|
||||
// start with a 2D
|
||||
let addr = {
|
||||
net : parseInt(m[2]),
|
||||
node : parseInt(m[3].substr(1)),
|
||||
};
|
||||
|
||||
// 3D: Addition of zone if present
|
||||
if(m[1]) {
|
||||
addr.zone = parseInt(m[1].slice(0, -1));
|
||||
}
|
||||
// 3D: Addition of zone if present
|
||||
if(m[1]) {
|
||||
addr.zone = parseInt(m[1].slice(0, -1));
|
||||
}
|
||||
|
||||
// 4D if optional point is present
|
||||
if(m[4]) {
|
||||
addr.point = parseInt(m[4].substr(1));
|
||||
}
|
||||
// 4D if optional point is present
|
||||
if(m[4]) {
|
||||
addr.point = parseInt(m[4].substr(1));
|
||||
}
|
||||
|
||||
// 5D with @domain
|
||||
if(m[5]) {
|
||||
addr.domain = m[5].substr(1);
|
||||
}
|
||||
// 5D with @domain
|
||||
if(m[5]) {
|
||||
addr.domain = m[5].substr(1);
|
||||
}
|
||||
|
||||
return new Address(addr);
|
||||
}
|
||||
}
|
||||
return new Address(addr);
|
||||
}
|
||||
}
|
||||
|
||||
toString(dimensions) {
|
||||
dimensions = dimensions || '5D';
|
||||
toString(dimensions) {
|
||||
dimensions = dimensions || '5D';
|
||||
|
||||
let addrStr = `${this.zone}:${this.net}`;
|
||||
let addrStr = `${this.zone}:${this.net}`;
|
||||
|
||||
// allow for e.g. '4D' or 5
|
||||
const dim = parseInt(dimensions.toString()[0]);
|
||||
// allow for e.g. '4D' or 5
|
||||
const dim = parseInt(dimensions.toString()[0]);
|
||||
|
||||
if(dim >= 3) {
|
||||
addrStr += `/${this.node}`;
|
||||
}
|
||||
if(dim >= 3) {
|
||||
addrStr += `/${this.node}`;
|
||||
}
|
||||
|
||||
// missing & .0 are equiv for point
|
||||
if(dim >= 4 && this.point) {
|
||||
addrStr += `.${this.point}`;
|
||||
}
|
||||
// missing & .0 are equiv for point
|
||||
if(dim >= 4 && this.point) {
|
||||
addrStr += `.${this.point}`;
|
||||
}
|
||||
|
||||
if(5 === dim && this.domain) {
|
||||
addrStr += `@${this.domain.toLowerCase()}`;
|
||||
}
|
||||
if(5 === dim && this.domain) {
|
||||
addrStr += `@${this.domain.toLowerCase()}`;
|
||||
}
|
||||
|
||||
return addrStr;
|
||||
}
|
||||
return addrStr;
|
||||
}
|
||||
|
||||
static getComparator() {
|
||||
return function(left, right) {
|
||||
let c = (left.zone || 0) - (right.zone || 0);
|
||||
if(0 !== c) {
|
||||
return c;
|
||||
}
|
||||
static getComparator() {
|
||||
return function(left, right) {
|
||||
let c = (left.zone || 0) - (right.zone || 0);
|
||||
if(0 !== c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
c = (left.net || 0) - (right.net || 0);
|
||||
if(0 !== c) {
|
||||
return c;
|
||||
}
|
||||
c = (left.net || 0) - (right.net || 0);
|
||||
if(0 !== c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
c = (left.node || 0) - (right.node || 0);
|
||||
if(0 !== c) {
|
||||
return c;
|
||||
}
|
||||
c = (left.node || 0) - (right.node || 0);
|
||||
if(0 !== c) {
|
||||
return c;
|
||||
}
|
||||
|
||||
return (left.domain || '').localeCompare(right.domain || '');
|
||||
};
|
||||
}
|
||||
return (left.domain || '').localeCompare(right.domain || '');
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
354
core/ftn_util.js
354
core/ftn_util.js
|
@ -45,12 +45,12 @@ exports.getQuotePrefix = getQuotePrefix;
|
|||
// See list here: https://github.com/Mithgol/node-fidonet-jam
|
||||
|
||||
function stringToNullPaddedBuffer(s, bufLen) {
|
||||
let buffer = Buffer.alloc(bufLen);
|
||||
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
|
||||
for(let i = 0; i < enc.length; ++i) {
|
||||
buffer[i] = enc[i];
|
||||
}
|
||||
return buffer;
|
||||
let buffer = Buffer.alloc(bufLen);
|
||||
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
|
||||
for(let i = 0; i < enc.length; ++i) {
|
||||
buffer[i] = enc[i];
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -58,45 +58,45 @@ function stringToNullPaddedBuffer(s, bufLen) {
|
|||
//
|
||||
// :TODO: Name the next couple methods better - for FTN *packets*
|
||||
function getDateFromFtnDateTime(dateTime) {
|
||||
//
|
||||
// Examples seen in the wild (Working):
|
||||
// "12 Sep 88 18:17:59"
|
||||
// "Tue 01 Jan 80 00:00"
|
||||
// "27 Feb 15 00:00:03"
|
||||
//
|
||||
// :TODO: Use moment.js here
|
||||
return moment(Date.parse(dateTime)); // Date.parse() allows funky formats
|
||||
//
|
||||
// Examples seen in the wild (Working):
|
||||
// "12 Sep 88 18:17:59"
|
||||
// "Tue 01 Jan 80 00:00"
|
||||
// "27 Feb 15 00:00:03"
|
||||
//
|
||||
// :TODO: Use moment.js here
|
||||
return moment(Date.parse(dateTime)); // Date.parse() allows funky formats
|
||||
// return (new Date(Date.parse(dateTime))).toISOString();
|
||||
}
|
||||
|
||||
function getDateTimeString(m) {
|
||||
//
|
||||
// From http://ftsc.org/docs/fts-0001.016:
|
||||
// DateTime = (* a character string 20 characters long *)
|
||||
// (* 01 Jan 86 02:34:56 *)
|
||||
// DayOfMonth " " Month " " Year " "
|
||||
// " " HH ":" MM ":" SS
|
||||
// Null
|
||||
//
|
||||
// DayOfMonth = "01" | "02" | "03" | ... | "31" (* Fido 0 fills *)
|
||||
// Month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
|
||||
// "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
|
||||
// Year = "01" | "02" | .. | "85" | "86" | ... | "99" | "00"
|
||||
// HH = "00" | .. | "23"
|
||||
// MM = "00" | .. | "59"
|
||||
// SS = "00" | .. | "59"
|
||||
//
|
||||
if(!moment.isMoment(m)) {
|
||||
m = moment(m);
|
||||
}
|
||||
//
|
||||
// From http://ftsc.org/docs/fts-0001.016:
|
||||
// DateTime = (* a character string 20 characters long *)
|
||||
// (* 01 Jan 86 02:34:56 *)
|
||||
// DayOfMonth " " Month " " Year " "
|
||||
// " " HH ":" MM ":" SS
|
||||
// Null
|
||||
//
|
||||
// DayOfMonth = "01" | "02" | "03" | ... | "31" (* Fido 0 fills *)
|
||||
// Month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
|
||||
// "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
|
||||
// Year = "01" | "02" | .. | "85" | "86" | ... | "99" | "00"
|
||||
// HH = "00" | .. | "23"
|
||||
// MM = "00" | .. | "59"
|
||||
// SS = "00" | .. | "59"
|
||||
//
|
||||
if(!moment.isMoment(m)) {
|
||||
m = moment(m);
|
||||
}
|
||||
|
||||
return m.format('DD MMM YY HH:mm:ss');
|
||||
return m.format('DD MMM YY HH:mm:ss');
|
||||
}
|
||||
|
||||
function getMessageSerialNumber(messageId) {
|
||||
const msSinceEnigmaEpoc = (Date.now() - Date.UTC(2016, 1, 1));
|
||||
const hash = Math.abs(new FNV1a(msSinceEnigmaEpoc + messageId).value).toString(16);
|
||||
return `00000000${hash}`.substr(-8);
|
||||
const msSinceEnigmaEpoc = (Date.now() - Date.UTC(2016, 1, 1));
|
||||
const hash = Math.abs(new FNV1a(msSinceEnigmaEpoc + messageId).value).toString(16);
|
||||
return `00000000${hash}`.substr(-8);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -143,11 +143,11 @@ function getMessageSerialNumber(messageId) {
|
|||
// format, but that will only help when using newer Mystic versions.
|
||||
//
|
||||
function getMessageIdentifier(message, address, isNetMail = false) {
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
return isNetMail ?
|
||||
`${addrStr} ${getMessageSerialNumber(message.messageId)}` :
|
||||
`${message.messageId}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(message.messageId)}`
|
||||
;
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
return isNetMail ?
|
||||
`${addrStr} ${getMessageSerialNumber(message.messageId)}` :
|
||||
`${message.messageId}.${message.areaTag.toLowerCase()}@${addrStr} ${getMessageSerialNumber(message.messageId)}`
|
||||
;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -158,10 +158,10 @@ function getMessageIdentifier(message, address, isNetMail = false) {
|
|||
// in which (<os>; <arch>; <nodeVer>) is used instead
|
||||
//
|
||||
function getProductIdentifier() {
|
||||
const version = getCleanEnigmaVersion();
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
const version = getCleanEnigmaVersion();
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
|
||||
return `ENiGMA1/2 ${version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
return `ENiGMA1/2 ${version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -171,7 +171,7 @@ function getProductIdentifier() {
|
|||
// http://ftsc.org/docs/frl-1004.002
|
||||
//
|
||||
function getUTCTimeZoneOffset() {
|
||||
return moment().format('ZZ').replace(/\+/, '');
|
||||
return moment().format('ZZ').replace(/\+/, '');
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -179,18 +179,18 @@ function getUTCTimeZoneOffset() {
|
|||
// http://ftsc.org/docs/fsc-0032.001
|
||||
//
|
||||
function getQuotePrefix(name) {
|
||||
let initials;
|
||||
let initials;
|
||||
|
||||
const parts = name.split(' ');
|
||||
if(parts.length > 1) {
|
||||
// First & Last initials - (Bryan Ashby -> BA)
|
||||
initials = `${parts[0].slice(0, 1)}${parts[parts.length - 1].slice(0, 1)}`.toUpperCase();
|
||||
} else {
|
||||
// Just use the first two - (NuSkooler -> Nu)
|
||||
initials = _.capitalize(name.slice(0, 2));
|
||||
}
|
||||
const parts = name.split(' ');
|
||||
if(parts.length > 1) {
|
||||
// First & Last initials - (Bryan Ashby -> BA)
|
||||
initials = `${parts[0].slice(0, 1)}${parts[parts.length - 1].slice(0, 1)}`.toUpperCase();
|
||||
} else {
|
||||
// Just use the first two - (NuSkooler -> Nu)
|
||||
initials = _.capitalize(name.slice(0, 2));
|
||||
}
|
||||
|
||||
return ` ${initials}> `;
|
||||
return ` ${initials}> `;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -198,18 +198,18 @@ function getQuotePrefix(name) {
|
|||
// http://ftsc.org/docs/fts-0004.001
|
||||
//
|
||||
function getOrigin(address) {
|
||||
const config = Config();
|
||||
const origin = _.has(config, 'messageNetworks.originLine') ?
|
||||
config.messageNetworks.originLine :
|
||||
config.general.boardName;
|
||||
const config = Config();
|
||||
const origin = _.has(config, 'messageNetworks.originLine') ?
|
||||
config.messageNetworks.originLine :
|
||||
config.general.boardName;
|
||||
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
return ` * Origin: ${origin} (${addrStr})`;
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
return ` * Origin: ${origin} (${addrStr})`;
|
||||
}
|
||||
|
||||
function getTearLine() {
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
return `--- ENiGMA 1/2 v${packageJson.version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
return `--- ENiGMA 1/2 v${packageJson.version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -217,17 +217,17 @@ function getTearLine() {
|
|||
// http://ftsc.org/docs/frl-1005.001
|
||||
//
|
||||
function getVia(address) {
|
||||
/*
|
||||
/*
|
||||
FRL-1005.001 states teh following format:
|
||||
|
||||
^AVia: <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone]
|
||||
<Program Name> <Version> [Serial Number]<CR>
|
||||
*/
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
const dateTime = moment().utc().format('YYYYMMDD.HHmmSS.SSSS.UTC');
|
||||
const version = getCleanEnigmaVersion();
|
||||
const addrStr = new Address(address).toString('5D');
|
||||
const dateTime = moment().utc().format('YYYYMMDD.HHmmSS.SSSS.UTC');
|
||||
const version = getCleanEnigmaVersion();
|
||||
|
||||
return `${addrStr} @${dateTime} ENiGMA1/2 ${version}`;
|
||||
return `${addrStr} @${dateTime} ENiGMA1/2 ${version}`;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -235,50 +235,50 @@ function getVia(address) {
|
|||
// http://retro.fidoweb.ru/docs/index=ftsc&doc=FTS-4001&enc=mac
|
||||
//
|
||||
function getIntl(toAddress, fromAddress) {
|
||||
//
|
||||
// INTL differs from 'standard' kludges in that there is no ':' after "INTL"
|
||||
//
|
||||
// "<SOH>"INTL "<destination address>" "<origin address><CR>"
|
||||
// "...These addresses shall be given on the form <zone>:<net>/<node>"
|
||||
//
|
||||
return `${toAddress.toString('3D')} ${fromAddress.toString('3D')}`;
|
||||
//
|
||||
// INTL differs from 'standard' kludges in that there is no ':' after "INTL"
|
||||
//
|
||||
// "<SOH>"INTL "<destination address>" "<origin address><CR>"
|
||||
// "...These addresses shall be given on the form <zone>:<net>/<node>"
|
||||
//
|
||||
return `${toAddress.toString('3D')} ${fromAddress.toString('3D')}`;
|
||||
}
|
||||
|
||||
function getAbbreviatedNetNodeList(netNodes) {
|
||||
let abbrList = '';
|
||||
let currNet;
|
||||
netNodes.forEach(netNode => {
|
||||
if(_.isString(netNode)) {
|
||||
netNode = Address.fromString(netNode);
|
||||
}
|
||||
if(currNet !== netNode.net) {
|
||||
abbrList += `${netNode.net}/`;
|
||||
currNet = netNode.net;
|
||||
}
|
||||
abbrList += `${netNode.node} `;
|
||||
});
|
||||
let abbrList = '';
|
||||
let currNet;
|
||||
netNodes.forEach(netNode => {
|
||||
if(_.isString(netNode)) {
|
||||
netNode = Address.fromString(netNode);
|
||||
}
|
||||
if(currNet !== netNode.net) {
|
||||
abbrList += `${netNode.net}/`;
|
||||
currNet = netNode.net;
|
||||
}
|
||||
abbrList += `${netNode.node} `;
|
||||
});
|
||||
|
||||
return abbrList.trim(); // remove trailing space
|
||||
return abbrList.trim(); // remove trailing space
|
||||
}
|
||||
|
||||
//
|
||||
// Parse an abbreviated net/node list commonly used for SEEN-BY and PATH
|
||||
//
|
||||
function parseAbbreviatedNetNodeList(netNodes) {
|
||||
const re = /([0-9]+)\/([0-9]+)\s?|([0-9]+)\s?/g;
|
||||
let net;
|
||||
let m;
|
||||
let results = [];
|
||||
while(null !== (m = re.exec(netNodes))) {
|
||||
if(m[1] && m[2]) {
|
||||
net = parseInt(m[1]);
|
||||
results.push(new Address( { net : net, node : parseInt(m[2]) } ));
|
||||
} else if(net) {
|
||||
results.push(new Address( { net : net, node : parseInt(m[3]) } ));
|
||||
}
|
||||
}
|
||||
const re = /([0-9]+)\/([0-9]+)\s?|([0-9]+)\s?/g;
|
||||
let net;
|
||||
let m;
|
||||
let results = [];
|
||||
while(null !== (m = re.exec(netNodes))) {
|
||||
if(m[1] && m[2]) {
|
||||
net = parseInt(m[1]);
|
||||
results.push(new Address( { net : net, node : parseInt(m[2]) } ));
|
||||
} else if(net) {
|
||||
results.push(new Address( { net : net, node : parseInt(m[3]) } ));
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
return results;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -295,7 +295,7 @@ function parseAbbreviatedNetNodeList(netNodes) {
|
|||
// not the "SEEN-BY" prefix itself
|
||||
//
|
||||
function getUpdatedSeenByEntries(existingEntries, additions) {
|
||||
/*
|
||||
/*
|
||||
From FTS-0004:
|
||||
|
||||
"There can be many seen-by lines at the end of Conference
|
||||
|
@ -316,37 +316,37 @@ function getUpdatedSeenByEntries(existingEntries, additions) {
|
|||
this field is not put in place by other Echomail compatible
|
||||
programs."
|
||||
*/
|
||||
existingEntries = existingEntries || [];
|
||||
if(!_.isArray(existingEntries)) {
|
||||
existingEntries = [ existingEntries ];
|
||||
}
|
||||
existingEntries = existingEntries || [];
|
||||
if(!_.isArray(existingEntries)) {
|
||||
existingEntries = [ existingEntries ];
|
||||
}
|
||||
|
||||
if(!_.isString(additions)) {
|
||||
additions = parseAbbreviatedNetNodeList(getAbbreviatedNetNodeList(additions));
|
||||
}
|
||||
if(!_.isString(additions)) {
|
||||
additions = parseAbbreviatedNetNodeList(getAbbreviatedNetNodeList(additions));
|
||||
}
|
||||
|
||||
additions = additions.sort(Address.getComparator());
|
||||
additions = additions.sort(Address.getComparator());
|
||||
|
||||
//
|
||||
// For now, we'll just append a new SEEN-BY entry
|
||||
//
|
||||
// :TODO: we should at least try and update what is already there in a smart way
|
||||
existingEntries.push(getAbbreviatedNetNodeList(additions));
|
||||
return existingEntries;
|
||||
//
|
||||
// For now, we'll just append a new SEEN-BY entry
|
||||
//
|
||||
// :TODO: we should at least try and update what is already there in a smart way
|
||||
existingEntries.push(getAbbreviatedNetNodeList(additions));
|
||||
return existingEntries;
|
||||
}
|
||||
|
||||
function getUpdatedPathEntries(existingEntries, localAddress) {
|
||||
// :TODO: append to PATH in a smart way! We shoudl try to fit at least the last existing line
|
||||
// :TODO: append to PATH in a smart way! We shoudl try to fit at least the last existing line
|
||||
|
||||
existingEntries = existingEntries || [];
|
||||
if(!_.isArray(existingEntries)) {
|
||||
existingEntries = [ existingEntries ];
|
||||
}
|
||||
existingEntries = existingEntries || [];
|
||||
if(!_.isArray(existingEntries)) {
|
||||
existingEntries = [ existingEntries ];
|
||||
}
|
||||
|
||||
existingEntries.push(getAbbreviatedNetNodeList(
|
||||
parseAbbreviatedNetNodeList(localAddress)));
|
||||
existingEntries.push(getAbbreviatedNetNodeList(
|
||||
parseAbbreviatedNetNodeList(localAddress)));
|
||||
|
||||
return existingEntries;
|
||||
return existingEntries;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -354,71 +354,71 @@ function getUpdatedPathEntries(existingEntries, localAddress) {
|
|||
// http://ftsc.org/docs/fts-5003.001
|
||||
//
|
||||
const ENCODING_TO_FTS_5003_001_CHARS = {
|
||||
// level 1 - generally should not be used
|
||||
ascii : [ 'ASCII', 1 ],
|
||||
'us-ascii' : [ 'ASCII', 1 ],
|
||||
// level 1 - generally should not be used
|
||||
ascii : [ 'ASCII', 1 ],
|
||||
'us-ascii' : [ 'ASCII', 1 ],
|
||||
|
||||
// level 2 - 8 bit, ASCII based
|
||||
cp437 : [ 'CP437', 2 ],
|
||||
cp850 : [ 'CP850', 2 ],
|
||||
// level 2 - 8 bit, ASCII based
|
||||
cp437 : [ 'CP437', 2 ],
|
||||
cp850 : [ 'CP850', 2 ],
|
||||
|
||||
// level 3 - reserved
|
||||
// level 3 - reserved
|
||||
|
||||
// level 4
|
||||
utf8 : [ 'UTF-8', 4 ],
|
||||
'utf-8' : [ 'UTF-8', 4 ],
|
||||
// level 4
|
||||
utf8 : [ 'UTF-8', 4 ],
|
||||
'utf-8' : [ 'UTF-8', 4 ],
|
||||
};
|
||||
|
||||
|
||||
function getCharacterSetIdentifierByEncoding(encodingName) {
|
||||
const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()];
|
||||
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
|
||||
const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()];
|
||||
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
|
||||
}
|
||||
|
||||
function getEncodingFromCharacterSetIdentifier(chrs) {
|
||||
const ident = chrs.split(' ')[0].toUpperCase();
|
||||
const ident = chrs.split(' ')[0].toUpperCase();
|
||||
|
||||
// :TODO: fill in the rest!!!
|
||||
return {
|
||||
// level 1
|
||||
'ASCII' : 'iso-646-1',
|
||||
'DUTCH' : 'iso-646',
|
||||
'FINNISH' : 'iso-646-10',
|
||||
'FRENCH' : 'iso-646',
|
||||
'CANADIAN' : 'iso-646',
|
||||
'GERMAN' : 'iso-646',
|
||||
'ITALIAN' : 'iso-646',
|
||||
'NORWEIG' : 'iso-646',
|
||||
'PORTU' : 'iso-646',
|
||||
'SPANISH' : 'iso-656',
|
||||
'SWEDISH' : 'iso-646-10',
|
||||
'SWISS' : 'iso-646',
|
||||
'UK' : 'iso-646',
|
||||
'ISO-10' : 'iso-646-10',
|
||||
// :TODO: fill in the rest!!!
|
||||
return {
|
||||
// level 1
|
||||
'ASCII' : 'iso-646-1',
|
||||
'DUTCH' : 'iso-646',
|
||||
'FINNISH' : 'iso-646-10',
|
||||
'FRENCH' : 'iso-646',
|
||||
'CANADIAN' : 'iso-646',
|
||||
'GERMAN' : 'iso-646',
|
||||
'ITALIAN' : 'iso-646',
|
||||
'NORWEIG' : 'iso-646',
|
||||
'PORTU' : 'iso-646',
|
||||
'SPANISH' : 'iso-656',
|
||||
'SWEDISH' : 'iso-646-10',
|
||||
'SWISS' : 'iso-646',
|
||||
'UK' : 'iso-646',
|
||||
'ISO-10' : 'iso-646-10',
|
||||
|
||||
// level 2
|
||||
'CP437' : 'cp437',
|
||||
'CP850' : 'cp850',
|
||||
'CP852' : 'cp852',
|
||||
'CP866' : 'cp866',
|
||||
'CP848' : 'cp848',
|
||||
'CP1250' : 'cp1250',
|
||||
'CP1251' : 'cp1251',
|
||||
'CP1252' : 'cp1252',
|
||||
'CP10000' : 'macroman',
|
||||
'LATIN-1' : 'iso-8859-1',
|
||||
'LATIN-2' : 'iso-8859-2',
|
||||
'LATIN-5' : 'iso-8859-9',
|
||||
'LATIN-9' : 'iso-8859-15',
|
||||
// level 2
|
||||
'CP437' : 'cp437',
|
||||
'CP850' : 'cp850',
|
||||
'CP852' : 'cp852',
|
||||
'CP866' : 'cp866',
|
||||
'CP848' : 'cp848',
|
||||
'CP1250' : 'cp1250',
|
||||
'CP1251' : 'cp1251',
|
||||
'CP1252' : 'cp1252',
|
||||
'CP10000' : 'macroman',
|
||||
'LATIN-1' : 'iso-8859-1',
|
||||
'LATIN-2' : 'iso-8859-2',
|
||||
'LATIN-5' : 'iso-8859-9',
|
||||
'LATIN-9' : 'iso-8859-15',
|
||||
|
||||
// level 4
|
||||
'UTF-8' : 'utf8',
|
||||
// level 4
|
||||
'UTF-8' : 'utf8',
|
||||
|
||||
// deprecated stuff
|
||||
'IBMPC' : 'cp1250', // :TODO: validate
|
||||
'+7_FIDO' : 'cp866',
|
||||
'+7' : 'cp866',
|
||||
'MAC' : 'macroman', // :TODO: validate
|
||||
// deprecated stuff
|
||||
'IBMPC' : 'cp1250', // :TODO: validate
|
||||
'+7_FIDO' : 'cp866',
|
||||
'+7' : 'cp866',
|
||||
'MAC' : 'macroman', // :TODO: validate
|
||||
|
||||
}[ident];
|
||||
}[ident];
|
||||
}
|
|
@ -15,154 +15,154 @@ exports.HorizontalMenuView = HorizontalMenuView;
|
|||
// :TODO: Update this to allow scrolling if number of items cannot fit in width (similar to VerticalMenuView)
|
||||
|
||||
function HorizontalMenuView(options) {
|
||||
options.cursor = options.cursor || 'hide';
|
||||
options.cursor = options.cursor || 'hide';
|
||||
|
||||
if(!_.isNumber(options.itemSpacing)) {
|
||||
options.itemSpacing = 1;
|
||||
}
|
||||
if(!_.isNumber(options.itemSpacing)) {
|
||||
options.itemSpacing = 1;
|
||||
}
|
||||
|
||||
MenuView.call(this, options);
|
||||
MenuView.call(this, options);
|
||||
|
||||
this.dimens.height = 1; // always the case
|
||||
this.dimens.height = 1; // always the case
|
||||
|
||||
var self = this;
|
||||
var self = this;
|
||||
|
||||
this.getSpacer = function() {
|
||||
return new Array(self.itemSpacing + 1).join(' ');
|
||||
};
|
||||
this.getSpacer = function() {
|
||||
return new Array(self.itemSpacing + 1).join(' ');
|
||||
};
|
||||
|
||||
this.performAutoScale = function() {
|
||||
if(self.autoScale.width) {
|
||||
var spacer = self.getSpacer();
|
||||
var width = self.items.join(spacer).length + (spacer.length * 2);
|
||||
assert(width <= self.client.term.termWidth - self.position.col);
|
||||
self.dimens.width = width;
|
||||
}
|
||||
};
|
||||
this.performAutoScale = function() {
|
||||
if(self.autoScale.width) {
|
||||
var spacer = self.getSpacer();
|
||||
var width = self.items.join(spacer).length + (spacer.length * 2);
|
||||
assert(width <= self.client.term.termWidth - self.position.col);
|
||||
self.dimens.width = width;
|
||||
}
|
||||
};
|
||||
|
||||
this.performAutoScale();
|
||||
this.performAutoScale();
|
||||
|
||||
this.cachePositions = function() {
|
||||
if(this.positionCacheExpired) {
|
||||
var col = self.position.col;
|
||||
var spacer = self.getSpacer();
|
||||
this.cachePositions = function() {
|
||||
if(this.positionCacheExpired) {
|
||||
var col = self.position.col;
|
||||
var spacer = self.getSpacer();
|
||||
|
||||
for(var i = 0; i < self.items.length; ++i) {
|
||||
self.items[i].col = col;
|
||||
col += spacer.length + self.items[i].text.length + spacer.length;
|
||||
}
|
||||
}
|
||||
for(var i = 0; i < self.items.length; ++i) {
|
||||
self.items[i].col = col;
|
||||
col += spacer.length + self.items[i].text.length + spacer.length;
|
||||
}
|
||||
}
|
||||
|
||||
this.positionCacheExpired = false;
|
||||
};
|
||||
this.positionCacheExpired = false;
|
||||
};
|
||||
|
||||
this.drawItem = function(index) {
|
||||
assert(!this.positionCacheExpired);
|
||||
this.drawItem = function(index) {
|
||||
assert(!this.positionCacheExpired);
|
||||
|
||||
const item = self.items[index];
|
||||
if(!item) {
|
||||
return;
|
||||
}
|
||||
const item = self.items[index];
|
||||
if(!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
let text;
|
||||
let sgr;
|
||||
if(item.focused && self.hasFocusItems()) {
|
||||
const focusItem = self.focusItems[index];
|
||||
text = focusItem ? focusItem.text : item.text;
|
||||
sgr = '';
|
||||
} else if(this.complexItems) {
|
||||
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
|
||||
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
||||
} else {
|
||||
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
|
||||
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
||||
}
|
||||
let text;
|
||||
let sgr;
|
||||
if(item.focused && self.hasFocusItems()) {
|
||||
const focusItem = self.focusItems[index];
|
||||
text = focusItem ? focusItem.text : item.text;
|
||||
sgr = '';
|
||||
} else if(this.complexItems) {
|
||||
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
|
||||
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
||||
} else {
|
||||
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
|
||||
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
||||
}
|
||||
|
||||
const drawWidth = strUtil.renderStringLength(text) + (self.getSpacer().length * 2);
|
||||
const drawWidth = strUtil.renderStringLength(text) + (self.getSpacer().length * 2);
|
||||
|
||||
self.client.term.write(
|
||||
`${goto(self.position.row, item.col)}${sgr}${strUtil.pad(text, drawWidth, self.fillChar, 'center')}`
|
||||
);
|
||||
};
|
||||
self.client.term.write(
|
||||
`${goto(self.position.row, item.col)}${sgr}${strUtil.pad(text, drawWidth, self.fillChar, 'center')}`
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
require('util').inherits(HorizontalMenuView, MenuView);
|
||||
|
||||
HorizontalMenuView.prototype.setHeight = function(height) {
|
||||
height = parseInt(height, 10);
|
||||
assert(1 === height); // nothing else allowed here
|
||||
HorizontalMenuView.super_.prototype.setHeight(this, height);
|
||||
height = parseInt(height, 10);
|
||||
assert(1 === height); // nothing else allowed here
|
||||
HorizontalMenuView.super_.prototype.setHeight(this, height);
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.redraw = function() {
|
||||
HorizontalMenuView.super_.prototype.redraw.call(this);
|
||||
HorizontalMenuView.super_.prototype.redraw.call(this);
|
||||
|
||||
this.cachePositions();
|
||||
this.cachePositions();
|
||||
|
||||
for(var i = 0; i < this.items.length; ++i) {
|
||||
this.items[i].focused = this.focusedItemIndex === i;
|
||||
this.drawItem(i);
|
||||
}
|
||||
for(var i = 0; i < this.items.length; ++i) {
|
||||
this.items[i].focused = this.focusedItemIndex === i;
|
||||
this.drawItem(i);
|
||||
}
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.setPosition = function(pos) {
|
||||
HorizontalMenuView.super_.prototype.setPosition.call(this, pos);
|
||||
HorizontalMenuView.super_.prototype.setPosition.call(this, pos);
|
||||
|
||||
this.positionCacheExpired = true;
|
||||
this.positionCacheExpired = true;
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.setFocus = function(focused) {
|
||||
HorizontalMenuView.super_.prototype.setFocus.call(this, focused);
|
||||
HorizontalMenuView.super_.prototype.setFocus.call(this, focused);
|
||||
|
||||
this.redraw();
|
||||
this.redraw();
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.setItems = function(items) {
|
||||
HorizontalMenuView.super_.prototype.setItems.call(this, items);
|
||||
HorizontalMenuView.super_.prototype.setItems.call(this, items);
|
||||
|
||||
this.positionCacheExpired = true;
|
||||
this.positionCacheExpired = true;
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.focusNext = function() {
|
||||
if(this.items.length - 1 === this.focusedItemIndex) {
|
||||
this.focusedItemIndex = 0;
|
||||
} else {
|
||||
this.focusedItemIndex++;
|
||||
}
|
||||
if(this.items.length - 1 === this.focusedItemIndex) {
|
||||
this.focusedItemIndex = 0;
|
||||
} else {
|
||||
this.focusedItemIndex++;
|
||||
}
|
||||
|
||||
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
||||
this.redraw();
|
||||
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
||||
this.redraw();
|
||||
|
||||
HorizontalMenuView.super_.prototype.focusNext.call(this);
|
||||
HorizontalMenuView.super_.prototype.focusNext.call(this);
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.focusPrevious = function() {
|
||||
|
||||
if(0 === this.focusedItemIndex) {
|
||||
this.focusedItemIndex = this.items.length - 1;
|
||||
} else {
|
||||
this.focusedItemIndex--;
|
||||
}
|
||||
if(0 === this.focusedItemIndex) {
|
||||
this.focusedItemIndex = this.items.length - 1;
|
||||
} else {
|
||||
this.focusedItemIndex--;
|
||||
}
|
||||
|
||||
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
||||
this.redraw();
|
||||
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
||||
this.redraw();
|
||||
|
||||
HorizontalMenuView.super_.prototype.focusPrevious.call(this);
|
||||
HorizontalMenuView.super_.prototype.focusPrevious.call(this);
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.onKeyPress = function(ch, key) {
|
||||
if(key) {
|
||||
if(this.isKeyMapped('left', key.name)) {
|
||||
this.focusPrevious();
|
||||
} else if(this.isKeyMapped('right', key.name)) {
|
||||
this.focusNext();
|
||||
}
|
||||
}
|
||||
if(key) {
|
||||
if(this.isKeyMapped('left', key.name)) {
|
||||
this.focusPrevious();
|
||||
} else if(this.isKeyMapped('right', key.name)) {
|
||||
this.focusNext();
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalMenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
HorizontalMenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
};
|
||||
|
||||
HorizontalMenuView.prototype.getData = function() {
|
||||
const item = this.getItem(this.focusedItemIndex);
|
||||
return _.isString(item.data) ? item.data : this.focusedItemIndex;
|
||||
const item = this.getItem(this.focusedItemIndex);
|
||||
return _.isString(item.data) ? item.data : this.focusedItemIndex;
|
||||
};
|
|
@ -9,69 +9,69 @@ const stylizeString = require('./string_util.js').stylizeString;
|
|||
const _ = require('lodash');
|
||||
|
||||
module.exports = class KeyEntryView extends View {
|
||||
constructor(options) {
|
||||
options.acceptsFocus = valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = valueWithDefault(options.acceptsInput, true);
|
||||
constructor(options) {
|
||||
options.acceptsFocus = valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = valueWithDefault(options.acceptsInput, true);
|
||||
|
||||
super(options);
|
||||
super(options);
|
||||
|
||||
this.eatTabKey = options.eatTabKey || true;
|
||||
this.caseInsensitive = options.caseInsensitive || true;
|
||||
this.eatTabKey = options.eatTabKey || true;
|
||||
this.caseInsensitive = options.caseInsensitive || true;
|
||||
|
||||
if(Array.isArray(options.keys)) {
|
||||
if(this.caseInsensitive) {
|
||||
this.keys = options.keys.map( k => k.toUpperCase() );
|
||||
} else {
|
||||
this.keys = options.keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Array.isArray(options.keys)) {
|
||||
if(this.caseInsensitive) {
|
||||
this.keys = options.keys.map( k => k.toUpperCase() );
|
||||
} else {
|
||||
this.keys = options.keys;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onKeyPress(ch, key) {
|
||||
const drawKey = ch;
|
||||
onKeyPress(ch, key) {
|
||||
const drawKey = ch;
|
||||
|
||||
if(ch && this.caseInsensitive) {
|
||||
ch = ch.toUpperCase();
|
||||
}
|
||||
if(ch && this.caseInsensitive) {
|
||||
ch = ch.toUpperCase();
|
||||
}
|
||||
|
||||
if(drawKey && isPrintable(drawKey) && (!this.keys || this.keys.indexOf(ch) > -1)) {
|
||||
this.redraw(); // sets position
|
||||
this.client.term.write(stylizeString(ch, this.textStyle));
|
||||
}
|
||||
if(drawKey && isPrintable(drawKey) && (!this.keys || this.keys.indexOf(ch) > -1)) {
|
||||
this.redraw(); // sets position
|
||||
this.client.term.write(stylizeString(ch, this.textStyle));
|
||||
}
|
||||
|
||||
this.keyEntered = ch || key.name;
|
||||
this.keyEntered = ch || key.name;
|
||||
|
||||
if(key && 'tab' === key.name && !this.eatTabKey) {
|
||||
return this.emit('action', 'next', key);
|
||||
}
|
||||
if(key && 'tab' === key.name && !this.eatTabKey) {
|
||||
return this.emit('action', 'next', key);
|
||||
}
|
||||
|
||||
this.emit('action', 'accept');
|
||||
// NOTE: we don't call super here. KeyEntryView is a special snowflake.
|
||||
}
|
||||
this.emit('action', 'accept');
|
||||
// NOTE: we don't call super here. KeyEntryView is a special snowflake.
|
||||
}
|
||||
|
||||
setPropertyValue(propName, propValue) {
|
||||
switch(propName) {
|
||||
case 'eatTabKey' :
|
||||
if(_.isBoolean(propValue)) {
|
||||
this.eatTabKey = propValue;
|
||||
}
|
||||
break;
|
||||
setPropertyValue(propName, propValue) {
|
||||
switch(propName) {
|
||||
case 'eatTabKey' :
|
||||
if(_.isBoolean(propValue)) {
|
||||
this.eatTabKey = propValue;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'caseInsensitive' :
|
||||
if(_.isBoolean(propValue)) {
|
||||
this.caseInsensitive = propValue;
|
||||
}
|
||||
break;
|
||||
case 'caseInsensitive' :
|
||||
if(_.isBoolean(propValue)) {
|
||||
this.caseInsensitive = propValue;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'keys' :
|
||||
if(Array.isArray(propValue)) {
|
||||
this.keys = propValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'keys' :
|
||||
if(Array.isArray(propValue)) {
|
||||
this.keys = propValue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
super.setPropertyValue(propName, propValue);
|
||||
}
|
||||
super.setPropertyValue(propName, propValue);
|
||||
}
|
||||
|
||||
getData() { return this.keyEntered; }
|
||||
getData() { return this.keyEntered; }
|
||||
};
|
|
@ -24,128 +24,128 @@ const _ = require('lodash');
|
|||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Last Callers',
|
||||
desc : 'Last callers to the system',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.lastcallers'
|
||||
name : 'Last Callers',
|
||||
desc : 'Last callers to the system',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.lastcallers'
|
||||
};
|
||||
|
||||
const MciCodeIds = {
|
||||
CallerList : 1,
|
||||
CallerList : 1,
|
||||
};
|
||||
|
||||
exports.getModule = class LastCallersModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
constructor(options) {
|
||||
super(options);
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
|
||||
let loginHistory;
|
||||
let callersView;
|
||||
let loginHistory;
|
||||
let callersView;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
noInput : true,
|
||||
};
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
noInput : true,
|
||||
};
|
||||
|
||||
vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function fetchHistory(callback) {
|
||||
callersView = vc.getView(MciCodeIds.CallerList);
|
||||
vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function fetchHistory(callback) {
|
||||
callersView = vc.getView(MciCodeIds.CallerList);
|
||||
|
||||
// fetch up
|
||||
StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => {
|
||||
loginHistory = lh;
|
||||
// fetch up
|
||||
StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => {
|
||||
loginHistory = lh;
|
||||
|
||||
if(self.menuConfig.config.hideSysOpLogin) {
|
||||
const noOpLoginHistory = loginHistory.filter(lh => {
|
||||
return false === User.isRootUserId(parseInt(lh.log_value)); // log_value=userId
|
||||
});
|
||||
if(self.menuConfig.config.hideSysOpLogin) {
|
||||
const noOpLoginHistory = loginHistory.filter(lh => {
|
||||
return false === User.isRootUserId(parseInt(lh.log_value)); // log_value=userId
|
||||
});
|
||||
|
||||
//
|
||||
// If we have enough items to display, or hideSysOpLogin is set to 'always',
|
||||
// then set loginHistory to our filtered list. Else, we'll leave it be.
|
||||
//
|
||||
if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) {
|
||||
loginHistory = noOpLoginHistory;
|
||||
}
|
||||
}
|
||||
//
|
||||
// If we have enough items to display, or hideSysOpLogin is set to 'always',
|
||||
// then set loginHistory to our filtered list. Else, we'll leave it be.
|
||||
//
|
||||
if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) {
|
||||
loginHistory = noOpLoginHistory;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Finally, we need to trim up the list to the needed size
|
||||
//
|
||||
loginHistory = loginHistory.slice(0, callersView.dimens.height);
|
||||
//
|
||||
// Finally, we need to trim up the list to the needed size
|
||||
//
|
||||
loginHistory = loginHistory.slice(0, callersView.dimens.height);
|
||||
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function getUserNamesAndProperties(callback) {
|
||||
const getPropOpts = {
|
||||
names : [ 'location', 'affiliation' ]
|
||||
};
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function getUserNamesAndProperties(callback) {
|
||||
const getPropOpts = {
|
||||
names : [ 'location', 'affiliation' ]
|
||||
};
|
||||
|
||||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
|
||||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
|
||||
|
||||
async.each(
|
||||
loginHistory,
|
||||
(item, next) => {
|
||||
item.userId = parseInt(item.log_value);
|
||||
item.ts = moment(item.timestamp).format(dateTimeFormat);
|
||||
async.each(
|
||||
loginHistory,
|
||||
(item, next) => {
|
||||
item.userId = parseInt(item.log_value);
|
||||
item.ts = moment(item.timestamp).format(dateTimeFormat);
|
||||
|
||||
User.getUserName(item.userId, (err, userName) => {
|
||||
if(err) {
|
||||
item.deleted = true;
|
||||
return next(null);
|
||||
} else {
|
||||
item.userName = userName || 'N/A';
|
||||
User.getUserName(item.userId, (err, userName) => {
|
||||
if(err) {
|
||||
item.deleted = true;
|
||||
return next(null);
|
||||
} else {
|
||||
item.userName = userName || 'N/A';
|
||||
|
||||
User.loadProperties(item.userId, getPropOpts, (err, props) => {
|
||||
if(!err && props) {
|
||||
item.location = props.location || 'N/A';
|
||||
item.affiliation = item.affils = (props.affiliation || 'N/A');
|
||||
} else {
|
||||
item.location = 'N/A';
|
||||
item.affiliation = item.affils = 'N/A';
|
||||
}
|
||||
return next(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
err => {
|
||||
loginHistory = loginHistory.filter(lh => true !== lh.deleted);
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affiliation} - {ts}';
|
||||
User.loadProperties(item.userId, getPropOpts, (err, props) => {
|
||||
if(!err && props) {
|
||||
item.location = props.location || 'N/A';
|
||||
item.affiliation = item.affils = (props.affiliation || 'N/A');
|
||||
} else {
|
||||
item.location = 'N/A';
|
||||
item.affiliation = item.affils = 'N/A';
|
||||
}
|
||||
return next(null);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
err => {
|
||||
loginHistory = loginHistory.filter(lh => true !== lh.deleted);
|
||||
return callback(err);
|
||||
}
|
||||
);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affiliation} - {ts}';
|
||||
|
||||
callersView.setItems(_.map(loginHistory, ce => stringFormat(listFormat, ce) ) );
|
||||
callersView.setItems(_.map(loginHistory, ce => stringFormat(listFormat, ce) ) );
|
||||
|
||||
callersView.redraw();
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
(err) => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.toString() }, 'Error loading last callers');
|
||||
}
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
callersView.redraw();
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
(err) => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.toString() }, 'Error loading last callers');
|
||||
}
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,51 +14,51 @@ exports.shutdown = shutdown;
|
|||
exports.getServer = getServer;
|
||||
|
||||
function startup(cb) {
|
||||
return startListening(cb);
|
||||
return startListening(cb);
|
||||
}
|
||||
|
||||
function shutdown(cb) {
|
||||
return cb(null);
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
function getServer(packageName) {
|
||||
return listeningServers[packageName];
|
||||
return listeningServers[packageName];
|
||||
}
|
||||
|
||||
function startListening(cb) {
|
||||
const moduleUtil = require('./module_util.js'); // late load so we get Config
|
||||
const moduleUtil = require('./module_util.js'); // late load so we get Config
|
||||
|
||||
async.each( [ 'login', 'content' ], (category, next) => {
|
||||
moduleUtil.loadModulesForCategory(`${category}Servers`, (err, module) => {
|
||||
// :TODO: use enig error here!
|
||||
if(err) {
|
||||
if('EENIGMODDISABLED' === err.code) {
|
||||
logger.log.debug(err.message);
|
||||
} else {
|
||||
logger.log.info( { err : err }, 'Failed loading module');
|
||||
}
|
||||
return;
|
||||
}
|
||||
async.each( [ 'login', 'content' ], (category, next) => {
|
||||
moduleUtil.loadModulesForCategory(`${category}Servers`, (err, module) => {
|
||||
// :TODO: use enig error here!
|
||||
if(err) {
|
||||
if('EENIGMODDISABLED' === err.code) {
|
||||
logger.log.debug(err.message);
|
||||
} else {
|
||||
logger.log.info( { err : err }, 'Failed loading module');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const moduleInst = new module.getModule();
|
||||
try {
|
||||
moduleInst.createServer();
|
||||
if(!moduleInst.listen()) {
|
||||
throw new Error('Failed listening');
|
||||
}
|
||||
const moduleInst = new module.getModule();
|
||||
try {
|
||||
moduleInst.createServer();
|
||||
if(!moduleInst.listen()) {
|
||||
throw new Error('Failed listening');
|
||||
}
|
||||
|
||||
listeningServers[module.moduleInfo.packageName] = {
|
||||
instance : moduleInst,
|
||||
info : module.moduleInfo,
|
||||
};
|
||||
listeningServers[module.moduleInfo.packageName] = {
|
||||
instance : moduleInst,
|
||||
info : module.moduleInfo,
|
||||
};
|
||||
|
||||
} catch(e) {
|
||||
logger.log.error(e, 'Exception caught creating server!');
|
||||
}
|
||||
}, err => {
|
||||
return next(err);
|
||||
});
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
} catch(e) {
|
||||
logger.log.error(e, 'Exception caught creating server!');
|
||||
}
|
||||
}, err => {
|
||||
return next(err);
|
||||
});
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
|
108
core/logger.js
108
core/logger.js
|
@ -9,66 +9,66 @@ const _ = require('lodash');
|
|||
|
||||
module.exports = class Log {
|
||||
|
||||
static init() {
|
||||
const Config = require('./config.js').get();
|
||||
const logPath = Config.paths.logs;
|
||||
static init() {
|
||||
const Config = require('./config.js').get();
|
||||
const logPath = Config.paths.logs;
|
||||
|
||||
const err = this.checkLogPath(logPath);
|
||||
if(err) {
|
||||
console.error(err.message); // eslint-disable-line no-console
|
||||
return process.exit();
|
||||
}
|
||||
const err = this.checkLogPath(logPath);
|
||||
if(err) {
|
||||
console.error(err.message); // eslint-disable-line no-console
|
||||
return process.exit();
|
||||
}
|
||||
|
||||
const logStreams = [];
|
||||
if(_.isObject(Config.logging.rotatingFile)) {
|
||||
Config.logging.rotatingFile.path = paths.join(logPath, Config.logging.rotatingFile.fileName);
|
||||
logStreams.push(Config.logging.rotatingFile);
|
||||
}
|
||||
const logStreams = [];
|
||||
if(_.isObject(Config.logging.rotatingFile)) {
|
||||
Config.logging.rotatingFile.path = paths.join(logPath, Config.logging.rotatingFile.fileName);
|
||||
logStreams.push(Config.logging.rotatingFile);
|
||||
}
|
||||
|
||||
const serializers = {
|
||||
err : bunyan.stdSerializers.err, // handle 'err' fields with stack/etc.
|
||||
};
|
||||
const serializers = {
|
||||
err : bunyan.stdSerializers.err, // handle 'err' fields with stack/etc.
|
||||
};
|
||||
|
||||
// try to remove sensitive info by default, e.g. 'password' fields
|
||||
[ 'formData', 'formValue' ].forEach(keyName => {
|
||||
serializers[keyName] = (fd) => Log.hideSensitive(fd);
|
||||
});
|
||||
// try to remove sensitive info by default, e.g. 'password' fields
|
||||
[ 'formData', 'formValue' ].forEach(keyName => {
|
||||
serializers[keyName] = (fd) => Log.hideSensitive(fd);
|
||||
});
|
||||
|
||||
this.log = bunyan.createLogger({
|
||||
name : 'ENiGMA½ BBS',
|
||||
streams : logStreams,
|
||||
serializers : serializers,
|
||||
});
|
||||
}
|
||||
this.log = bunyan.createLogger({
|
||||
name : 'ENiGMA½ BBS',
|
||||
streams : logStreams,
|
||||
serializers : serializers,
|
||||
});
|
||||
}
|
||||
|
||||
static checkLogPath(logPath) {
|
||||
try {
|
||||
if(!fs.statSync(logPath).isDirectory()) {
|
||||
return new Error(`${logPath} is not a directory`);
|
||||
}
|
||||
static checkLogPath(logPath) {
|
||||
try {
|
||||
if(!fs.statSync(logPath).isDirectory()) {
|
||||
return new Error(`${logPath} is not a directory`);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch(e) {
|
||||
if('ENOENT' === e.code) {
|
||||
return new Error(`${logPath} does not exist`);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch(e) {
|
||||
if('ENOENT' === e.code) {
|
||||
return new Error(`${logPath} does not exist`);
|
||||
}
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
static hideSensitive(obj) {
|
||||
try {
|
||||
//
|
||||
// Use a regexp -- we don't know how nested fields we want to seek and destroy may be
|
||||
//
|
||||
return JSON.parse(
|
||||
JSON.stringify(obj).replace(/"(password|passwordConfirm|key|authCode)"\s?:\s?"([^"]+)"/, (match, valueName) => {
|
||||
return `"${valueName}":"********"`;
|
||||
})
|
||||
);
|
||||
} catch(e) {
|
||||
// be safe and return empty obj!
|
||||
return {};
|
||||
}
|
||||
}
|
||||
static hideSensitive(obj) {
|
||||
try {
|
||||
//
|
||||
// Use a regexp -- we don't know how nested fields we want to seek and destroy may be
|
||||
//
|
||||
return JSON.parse(
|
||||
JSON.stringify(obj).replace(/"(password|passwordConfirm|key|authCode)"\s?:\s?"([^"]+)"/, (match, valueName) => {
|
||||
return `"${valueName}":"********"`;
|
||||
})
|
||||
);
|
||||
} catch(e) {
|
||||
// be safe and return empty obj!
|
||||
return {};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -11,77 +11,77 @@ const clientConns = require('./client_connections.js');
|
|||
const _ = require('lodash');
|
||||
|
||||
module.exports = class LoginServerModule extends ServerModule {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
// :TODO: we need to max connections -- e.g. from config 'maxConnections'
|
||||
// :TODO: we need to max connections -- e.g. from config 'maxConnections'
|
||||
|
||||
prepareClient(client, cb) {
|
||||
const theme = require('./theme.js');
|
||||
prepareClient(client, cb) {
|
||||
const theme = require('./theme.js');
|
||||
|
||||
//
|
||||
// Choose initial theme before we have user context
|
||||
//
|
||||
if('*' === conf.config.preLoginTheme) {
|
||||
client.user.properties.theme_id = theme.getRandomTheme() || '';
|
||||
} else {
|
||||
client.user.properties.theme_id = conf.config.preLoginTheme;
|
||||
}
|
||||
//
|
||||
// Choose initial theme before we have user context
|
||||
//
|
||||
if('*' === conf.config.preLoginTheme) {
|
||||
client.user.properties.theme_id = theme.getRandomTheme() || '';
|
||||
} else {
|
||||
client.user.properties.theme_id = conf.config.preLoginTheme;
|
||||
}
|
||||
|
||||
theme.setClientTheme(client, client.user.properties.theme_id);
|
||||
return cb(null); // note: currently useless to use cb here - but this may change...again...
|
||||
}
|
||||
theme.setClientTheme(client, client.user.properties.theme_id);
|
||||
return cb(null); // note: currently useless to use cb here - but this may change...again...
|
||||
}
|
||||
|
||||
handleNewClient(client, clientSock, modInfo) {
|
||||
//
|
||||
// Start tracking the client. We'll assign it an ID which is
|
||||
// just the index in our connections array.
|
||||
//
|
||||
if(_.isUndefined(client.session)) {
|
||||
client.session = {};
|
||||
}
|
||||
handleNewClient(client, clientSock, modInfo) {
|
||||
//
|
||||
// Start tracking the client. We'll assign it an ID which is
|
||||
// just the index in our connections array.
|
||||
//
|
||||
if(_.isUndefined(client.session)) {
|
||||
client.session = {};
|
||||
}
|
||||
|
||||
client.session.serverName = modInfo.name;
|
||||
client.session.isSecure = _.isBoolean(client.isSecure) ? client.isSecure : (modInfo.isSecure || false);
|
||||
client.session.serverName = modInfo.name;
|
||||
client.session.isSecure = _.isBoolean(client.isSecure) ? client.isSecure : (modInfo.isSecure || false);
|
||||
|
||||
clientConns.addNewClient(client, clientSock);
|
||||
clientConns.addNewClient(client, clientSock);
|
||||
|
||||
client.on('ready', readyOptions => {
|
||||
client.on('ready', readyOptions => {
|
||||
|
||||
client.startIdleMonitor();
|
||||
client.startIdleMonitor();
|
||||
|
||||
// Go to module -- use default error handler
|
||||
this.prepareClient(client, () => {
|
||||
require('./connect.js').connectEntry(client, readyOptions.firstMenu);
|
||||
});
|
||||
});
|
||||
// Go to module -- use default error handler
|
||||
this.prepareClient(client, () => {
|
||||
require('./connect.js').connectEntry(client, readyOptions.firstMenu);
|
||||
});
|
||||
});
|
||||
|
||||
client.on('end', () => {
|
||||
clientConns.removeClient(client);
|
||||
});
|
||||
client.on('end', () => {
|
||||
clientConns.removeClient(client);
|
||||
});
|
||||
|
||||
client.on('error', err => {
|
||||
logger.log.info({ clientId : client.session.id }, 'Connection error: %s' % err.message);
|
||||
});
|
||||
client.on('error', err => {
|
||||
logger.log.info({ clientId : client.session.id }, 'Connection error: %s' % err.message);
|
||||
});
|
||||
|
||||
client.on('close', err => {
|
||||
const logFunc = err ? logger.log.info : logger.log.debug;
|
||||
logFunc( { clientId : client.session.id }, 'Connection closed');
|
||||
client.on('close', err => {
|
||||
const logFunc = err ? logger.log.info : logger.log.debug;
|
||||
logFunc( { clientId : client.session.id }, 'Connection closed');
|
||||
|
||||
clientConns.removeClient(client);
|
||||
});
|
||||
clientConns.removeClient(client);
|
||||
});
|
||||
|
||||
client.on('idle timeout', () => {
|
||||
client.log.info('User idle timeout expired');
|
||||
client.on('idle timeout', () => {
|
||||
client.log.info('User idle timeout expired');
|
||||
|
||||
client.menuStack.goto('idleLogoff', err => {
|
||||
if(err) {
|
||||
// likely just doesn't exist
|
||||
client.term.write('\nIdle timeout expired. Goodbye!\n');
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
client.menuStack.goto('idleLogoff', err => {
|
||||
if(err) {
|
||||
// likely just doesn't exist
|
||||
client.term.write('\nIdle timeout expired. Goodbye!\n');
|
||||
client.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,29 +8,29 @@ var _ = require('lodash');
|
|||
module.exports = MailPacket;
|
||||
|
||||
function MailPacket(options) {
|
||||
events.EventEmitter.call(this);
|
||||
events.EventEmitter.call(this);
|
||||
|
||||
// map of network name -> address obj ( { zone, net, node, point, domain } )
|
||||
this.nodeAddresses = options.nodeAddresses || {};
|
||||
// map of network name -> address obj ( { zone, net, node, point, domain } )
|
||||
this.nodeAddresses = options.nodeAddresses || {};
|
||||
}
|
||||
|
||||
require('util').inherits(MailPacket, events.EventEmitter);
|
||||
|
||||
MailPacket.prototype.read = function(options) {
|
||||
//
|
||||
// options.packetPath | opts.packetBuffer: supplies a path-to-file
|
||||
// or a buffer containing packet data
|
||||
//
|
||||
// emits 'message' event per message read
|
||||
//
|
||||
assert(_.isString(options.packetPath) || Buffer.isBuffer(options.packetBuffer));
|
||||
//
|
||||
// options.packetPath | opts.packetBuffer: supplies a path-to-file
|
||||
// or a buffer containing packet data
|
||||
//
|
||||
// emits 'message' event per message read
|
||||
//
|
||||
assert(_.isString(options.packetPath) || Buffer.isBuffer(options.packetBuffer));
|
||||
};
|
||||
|
||||
MailPacket.prototype.write = function(options) {
|
||||
//
|
||||
// options.messages[]: array of message(s) to create packets from
|
||||
//
|
||||
// emits 'packet' event per packet constructed
|
||||
//
|
||||
assert(_.isArray(options.messages));
|
||||
//
|
||||
// options.messages[]: array of message(s) to create packets from
|
||||
//
|
||||
// emits 'packet' event per packet constructed
|
||||
//
|
||||
assert(_.isArray(options.messages));
|
||||
};
|
|
@ -22,60 +22,60 @@ const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))
|
|||
Bar <baz@foobar.net> { name : 'Bar', flavor : 'email', remote : 'baz@foobar.com' }
|
||||
*/
|
||||
function getAddressedToInfo(input) {
|
||||
input = input.trim();
|
||||
input = input.trim();
|
||||
|
||||
const firstAtPos = input.indexOf('@');
|
||||
const firstAtPos = input.indexOf('@');
|
||||
|
||||
if(firstAtPos < 0) {
|
||||
let addr = Address.fromString(input);
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { flavor : Message.AddressFlavor.FTN, remote : input };
|
||||
}
|
||||
if(firstAtPos < 0) {
|
||||
let addr = Address.fromString(input);
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { flavor : Message.AddressFlavor.FTN, remote : input };
|
||||
}
|
||||
|
||||
const lessThanPos = input.indexOf('<');
|
||||
if(lessThanPos < 0) {
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
const lessThanPos = input.indexOf('<');
|
||||
if(lessThanPos < 0) {
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
|
||||
const greaterThanPos = input.indexOf('>');
|
||||
if(greaterThanPos < lessThanPos) {
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
const greaterThanPos = input.indexOf('>');
|
||||
if(greaterThanPos < lessThanPos) {
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
|
||||
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() };
|
||||
}
|
||||
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() };
|
||||
}
|
||||
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
|
||||
const lessThanPos = input.indexOf('<');
|
||||
const greaterThanPos = input.indexOf('>');
|
||||
if(lessThanPos > 0 && greaterThanPos > lessThanPos) {
|
||||
const addr = input.slice(lessThanPos + 1, greaterThanPos);
|
||||
const m = addr.match(EMAIL_REGEX);
|
||||
if(m) {
|
||||
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.Email, remote : addr };
|
||||
}
|
||||
const lessThanPos = input.indexOf('<');
|
||||
const greaterThanPos = input.indexOf('>');
|
||||
if(lessThanPos > 0 && greaterThanPos > lessThanPos) {
|
||||
const addr = input.slice(lessThanPos + 1, greaterThanPos);
|
||||
const m = addr.match(EMAIL_REGEX);
|
||||
if(m) {
|
||||
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.Email, remote : addr };
|
||||
}
|
||||
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
|
||||
let m = input.match(EMAIL_REGEX);
|
||||
if(m) {
|
||||
return { name : input.slice(0, firstAtPos), flavor : Message.AddressFlavor.Email, remote : input };
|
||||
}
|
||||
let m = input.match(EMAIL_REGEX);
|
||||
if(m) {
|
||||
return { name : input.slice(0, firstAtPos), flavor : Message.AddressFlavor.Email, remote : input };
|
||||
}
|
||||
|
||||
let addr = Address.fromString(input); // 5D?
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { flavor : Message.AddressFlavor.FTN, remote : addr.toString() } ;
|
||||
}
|
||||
let addr = Address.fromString(input); // 5D?
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { flavor : Message.AddressFlavor.FTN, remote : addr.toString() } ;
|
||||
}
|
||||
|
||||
addr = Address.fromString(input.slice(firstAtPos + 1).trim());
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { name : input.slice(0, firstAtPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() };
|
||||
}
|
||||
addr = Address.fromString(input.slice(firstAtPos + 1).trim());
|
||||
if(Address.isValidAddress(addr)) {
|
||||
return { name : input.slice(0, firstAtPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() };
|
||||
}
|
||||
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||
}
|
||||
|
|
|
@ -28,181 +28,181 @@ exports.MaskEditTextView = MaskEditTextView;
|
|||
//
|
||||
|
||||
function MaskEditTextView(options) {
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||
options.resizable = false;
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||
options.resizable = false;
|
||||
|
||||
TextView.call(this, options);
|
||||
TextView.call(this, options);
|
||||
|
||||
this.cursorPos = { x : 0 };
|
||||
this.patternArrayPos = 0;
|
||||
this.cursorPos = { x : 0 };
|
||||
this.patternArrayPos = 0;
|
||||
|
||||
var self = this;
|
||||
var self = this;
|
||||
|
||||
this.maskPattern = options.maskPattern || '';
|
||||
this.maskPattern = options.maskPattern || '';
|
||||
|
||||
this.clientBackspace = function() {
|
||||
var fillCharSGR = this.getStyleSGR(3) || this.getSGR();
|
||||
this.client.term.write('\b' + fillCharSGR + this.fillChar + '\b' + this.getFocusSGR());
|
||||
};
|
||||
this.clientBackspace = function() {
|
||||
var fillCharSGR = this.getStyleSGR(3) || this.getSGR();
|
||||
this.client.term.write('\b' + fillCharSGR + this.fillChar + '\b' + this.getFocusSGR());
|
||||
};
|
||||
|
||||
this.drawText = function(s) {
|
||||
var textToDraw = strUtil.stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
this.drawText = function(s) {
|
||||
var textToDraw = strUtil.stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
||||
|
||||
assert(textToDraw.length <= self.patternArray.length);
|
||||
assert(textToDraw.length <= self.patternArray.length);
|
||||
|
||||
// draw out the text we have so far
|
||||
var i = 0;
|
||||
var t = 0;
|
||||
while(i < self.patternArray.length) {
|
||||
if(_.isRegExp(self.patternArray[i])) {
|
||||
if(t < textToDraw.length) {
|
||||
self.client.term.write((self.hasFocus ? self.getFocusSGR() : self.getSGR()) + textToDraw[t]);
|
||||
t++;
|
||||
} else {
|
||||
self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar);
|
||||
}
|
||||
} else {
|
||||
var styleSgr = this.hasFocus ? (self.getStyleSGR(2) || '') : (self.getStyleSGR(1) || '');
|
||||
self.client.term.write(styleSgr + self.maskPattern[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
};
|
||||
// draw out the text we have so far
|
||||
var i = 0;
|
||||
var t = 0;
|
||||
while(i < self.patternArray.length) {
|
||||
if(_.isRegExp(self.patternArray[i])) {
|
||||
if(t < textToDraw.length) {
|
||||
self.client.term.write((self.hasFocus ? self.getFocusSGR() : self.getSGR()) + textToDraw[t]);
|
||||
t++;
|
||||
} else {
|
||||
self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar);
|
||||
}
|
||||
} else {
|
||||
var styleSgr = this.hasFocus ? (self.getStyleSGR(2) || '') : (self.getStyleSGR(1) || '');
|
||||
self.client.term.write(styleSgr + self.maskPattern[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
};
|
||||
|
||||
this.buildPattern = function() {
|
||||
self.patternArray = [];
|
||||
self.maxLength = 0;
|
||||
this.buildPattern = function() {
|
||||
self.patternArray = [];
|
||||
self.maxLength = 0;
|
||||
|
||||
for(var i = 0; i < self.maskPattern.length; i++) {
|
||||
// :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark!
|
||||
if(self.maskPattern[i] in MaskEditTextView.maskPatternCharacterRegEx) {
|
||||
self.patternArray.push(MaskEditTextView.maskPatternCharacterRegEx[self.maskPattern[i]]);
|
||||
++self.maxLength;
|
||||
} else {
|
||||
self.patternArray.push(self.maskPattern[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
for(var i = 0; i < self.maskPattern.length; i++) {
|
||||
// :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark!
|
||||
if(self.maskPattern[i] in MaskEditTextView.maskPatternCharacterRegEx) {
|
||||
self.patternArray.push(MaskEditTextView.maskPatternCharacterRegEx[self.maskPattern[i]]);
|
||||
++self.maxLength;
|
||||
} else {
|
||||
self.patternArray.push(self.maskPattern[i]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.getEndOfTextColumn = function() {
|
||||
return this.position.col + this.patternArrayPos;
|
||||
};
|
||||
this.getEndOfTextColumn = function() {
|
||||
return this.position.col + this.patternArrayPos;
|
||||
};
|
||||
|
||||
this.buildPattern();
|
||||
this.buildPattern();
|
||||
|
||||
}
|
||||
|
||||
require('util').inherits(MaskEditTextView, TextView);
|
||||
|
||||
MaskEditTextView.maskPatternCharacterRegEx = {
|
||||
'#' : /[0-9]/, // Numeric
|
||||
'A' : /[a-zA-Z]/, // Alpha
|
||||
'@' : /[0-9a-zA-Z]/, // Alphanumeric
|
||||
'&' : /[\w\d\s]/, // Any "printable" 32-126, 128-255
|
||||
'#' : /[0-9]/, // Numeric
|
||||
'A' : /[a-zA-Z]/, // Alpha
|
||||
'@' : /[0-9a-zA-Z]/, // Alphanumeric
|
||||
'&' : /[\w\d\s]/, // Any "printable" 32-126, 128-255
|
||||
};
|
||||
|
||||
MaskEditTextView.prototype.setText = function(text) {
|
||||
MaskEditTextView.super_.prototype.setText.call(this, text);
|
||||
MaskEditTextView.super_.prototype.setText.call(this, text);
|
||||
|
||||
if(this.patternArray) { // :TODO: This is a hack - see TextView ctor note about setText()
|
||||
this.patternArrayPos = this.patternArray.length;
|
||||
}
|
||||
if(this.patternArray) { // :TODO: This is a hack - see TextView ctor note about setText()
|
||||
this.patternArrayPos = this.patternArray.length;
|
||||
}
|
||||
};
|
||||
|
||||
MaskEditTextView.prototype.setMaskPattern = function(pattern) {
|
||||
this.dimens.width = pattern.length;
|
||||
this.dimens.width = pattern.length;
|
||||
|
||||
this.maskPattern = pattern;
|
||||
this.buildPattern();
|
||||
this.maskPattern = pattern;
|
||||
this.buildPattern();
|
||||
};
|
||||
|
||||
MaskEditTextView.prototype.onKeyPress = function(ch, key) {
|
||||
if(key) {
|
||||
if(this.isKeyMapped('backspace', key.name)) {
|
||||
if(this.text.length > 0) {
|
||||
this.patternArrayPos--;
|
||||
assert(this.patternArrayPos >= 0);
|
||||
if(key) {
|
||||
if(this.isKeyMapped('backspace', key.name)) {
|
||||
if(this.text.length > 0) {
|
||||
this.patternArrayPos--;
|
||||
assert(this.patternArrayPos >= 0);
|
||||
|
||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
this.clientBackspace();
|
||||
} else {
|
||||
while(this.patternArrayPos > 0) {
|
||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn() + 1));
|
||||
this.clientBackspace();
|
||||
break;
|
||||
}
|
||||
this.patternArrayPos--;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
this.clientBackspace();
|
||||
} else {
|
||||
while(this.patternArrayPos > 0) {
|
||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||
this.text = this.text.substr(0, this.text.length - 1);
|
||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn() + 1));
|
||||
this.clientBackspace();
|
||||
break;
|
||||
}
|
||||
this.patternArrayPos--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||
this.text = '';
|
||||
this.patternArrayPos = 0;
|
||||
this.setFocus(true); // redraw + adjust cursor
|
||||
return;
|
||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||
this.text = '';
|
||||
this.patternArrayPos = 0;
|
||||
this.setFocus(true); // redraw + adjust cursor
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(ch && strUtil.isPrintable(ch)) {
|
||||
if(this.text.length < this.maxLength) {
|
||||
ch = strUtil.stylizeString(ch, this.textStyle);
|
||||
if(ch && strUtil.isPrintable(ch)) {
|
||||
if(this.text.length < this.maxLength) {
|
||||
ch = strUtil.stylizeString(ch, this.textStyle);
|
||||
|
||||
if(!ch.match(this.patternArray[this.patternArrayPos])) {
|
||||
return;
|
||||
}
|
||||
if(!ch.match(this.patternArray[this.patternArrayPos])) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.text += ch;
|
||||
this.patternArrayPos++;
|
||||
this.text += ch;
|
||||
this.patternArrayPos++;
|
||||
|
||||
while(this.patternArrayPos < this.patternArray.length &&
|
||||
while(this.patternArrayPos < this.patternArray.length &&
|
||||
!_.isRegExp(this.patternArray[this.patternArrayPos]))
|
||||
{
|
||||
this.patternArrayPos++;
|
||||
}
|
||||
{
|
||||
this.patternArrayPos++;
|
||||
}
|
||||
|
||||
this.redraw();
|
||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn()));
|
||||
}
|
||||
}
|
||||
this.redraw();
|
||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn()));
|
||||
}
|
||||
}
|
||||
|
||||
MaskEditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
MaskEditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
};
|
||||
|
||||
MaskEditTextView.prototype.setPropertyValue = function(propName, value) {
|
||||
switch(propName) {
|
||||
case 'maskPattern' : this.setMaskPattern(value); break;
|
||||
}
|
||||
switch(propName) {
|
||||
case 'maskPattern' : this.setMaskPattern(value); break;
|
||||
}
|
||||
|
||||
MaskEditTextView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||
MaskEditTextView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||
};
|
||||
|
||||
MaskEditTextView.prototype.getData = function() {
|
||||
var rawData = MaskEditTextView.super_.prototype.getData.call(this);
|
||||
var rawData = MaskEditTextView.super_.prototype.getData.call(this);
|
||||
|
||||
if(!rawData || 0 === rawData.length) {
|
||||
return rawData;
|
||||
}
|
||||
if(!rawData || 0 === rawData.length) {
|
||||
return rawData;
|
||||
}
|
||||
|
||||
var data = '';
|
||||
var data = '';
|
||||
|
||||
assert(rawData.length <= this.patternArray.length);
|
||||
assert(rawData.length <= this.patternArray.length);
|
||||
|
||||
var p = 0;
|
||||
for(var i = 0; i < this.patternArray.length; ++i) {
|
||||
if(_.isRegExp(this.patternArray[i])) {
|
||||
data += rawData[p++];
|
||||
} else {
|
||||
data += this.patternArray[i];
|
||||
}
|
||||
}
|
||||
var p = 0;
|
||||
for(var i = 0; i < this.patternArray.length; ++i) {
|
||||
if(_.isRegExp(this.patternArray[i])) {
|
||||
data += rawData[p++];
|
||||
} else {
|
||||
data += this.patternArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
return data;
|
||||
};
|
||||
|
|
|
@ -22,186 +22,186 @@ const _ = require('lodash');
|
|||
exports.MCIViewFactory = MCIViewFactory;
|
||||
|
||||
function MCIViewFactory(client) {
|
||||
this.client = client;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
MCIViewFactory.UserViewCodes = [
|
||||
'TL', 'ET', 'ME', 'MT', 'PL', 'BT', 'VM', 'HM', 'SM', 'TM', 'KE',
|
||||
'TL', 'ET', 'ME', 'MT', 'PL', 'BT', 'VM', 'HM', 'SM', 'TM', 'KE',
|
||||
|
||||
//
|
||||
// XY is a special MCI code that allows finding positions
|
||||
// and counts for key lookup, but does not explicitly
|
||||
// represent a visible View on it's own
|
||||
//
|
||||
'XY',
|
||||
//
|
||||
// XY is a special MCI code that allows finding positions
|
||||
// and counts for key lookup, but does not explicitly
|
||||
// represent a visible View on it's own
|
||||
//
|
||||
'XY',
|
||||
];
|
||||
|
||||
MCIViewFactory.prototype.createFromMCI = function(mci) {
|
||||
assert(mci.code);
|
||||
assert(mci.id > 0);
|
||||
assert(mci.position);
|
||||
assert(mci.code);
|
||||
assert(mci.id > 0);
|
||||
assert(mci.position);
|
||||
|
||||
var view;
|
||||
var options = {
|
||||
client : this.client,
|
||||
id : mci.id,
|
||||
ansiSGR : mci.SGR,
|
||||
ansiFocusSGR : mci.focusSGR,
|
||||
position : { row : mci.position[0], col : mci.position[1] },
|
||||
};
|
||||
var view;
|
||||
var options = {
|
||||
client : this.client,
|
||||
id : mci.id,
|
||||
ansiSGR : mci.SGR,
|
||||
ansiFocusSGR : mci.focusSGR,
|
||||
position : { row : mci.position[0], col : mci.position[1] },
|
||||
};
|
||||
|
||||
// :TODO: These should use setPropertyValue()!
|
||||
function setOption(pos, name) {
|
||||
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
||||
options[name] = mci.args[pos];
|
||||
}
|
||||
}
|
||||
// :TODO: These should use setPropertyValue()!
|
||||
function setOption(pos, name) {
|
||||
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
||||
options[name] = mci.args[pos];
|
||||
}
|
||||
}
|
||||
|
||||
function setWidth(pos) {
|
||||
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
||||
if(!_.isObject(options.dimens)) {
|
||||
options.dimens = {};
|
||||
}
|
||||
options.dimens.width = parseInt(mci.args[pos], 10);
|
||||
}
|
||||
}
|
||||
function setWidth(pos) {
|
||||
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
||||
if(!_.isObject(options.dimens)) {
|
||||
options.dimens = {};
|
||||
}
|
||||
options.dimens.width = parseInt(mci.args[pos], 10);
|
||||
}
|
||||
}
|
||||
|
||||
function setFocusOption(pos, name) {
|
||||
if(mci.focusArgs && mci.focusArgs.length > pos && mci.focusArgs[pos].length > 0) {
|
||||
options[name] = mci.focusArgs[pos];
|
||||
}
|
||||
}
|
||||
function setFocusOption(pos, name) {
|
||||
if(mci.focusArgs && mci.focusArgs.length > pos && mci.focusArgs[pos].length > 0) {
|
||||
options[name] = mci.focusArgs[pos];
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Note: Keep this in sync with UserViewCodes above!
|
||||
//
|
||||
switch(mci.code) {
|
||||
// Text Label (Text View)
|
||||
case 'TL' :
|
||||
setOption(0, 'textStyle');
|
||||
setOption(1, 'justify');
|
||||
setWidth(2);
|
||||
//
|
||||
// Note: Keep this in sync with UserViewCodes above!
|
||||
//
|
||||
switch(mci.code) {
|
||||
// Text Label (Text View)
|
||||
case 'TL' :
|
||||
setOption(0, 'textStyle');
|
||||
setOption(1, 'justify');
|
||||
setWidth(2);
|
||||
|
||||
view = new TextView(options);
|
||||
break;
|
||||
view = new TextView(options);
|
||||
break;
|
||||
|
||||
// Edit Text
|
||||
case 'ET' :
|
||||
setWidth(0);
|
||||
// Edit Text
|
||||
case 'ET' :
|
||||
setWidth(0);
|
||||
|
||||
setOption(1, 'textStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
setOption(1, 'textStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new EditTextView(options);
|
||||
break;
|
||||
view = new EditTextView(options);
|
||||
break;
|
||||
|
||||
// Masked Edit Text
|
||||
case 'ME' :
|
||||
setOption(0, 'textStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
// Masked Edit Text
|
||||
case 'ME' :
|
||||
setOption(0, 'textStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new MaskEditTextView(options);
|
||||
break;
|
||||
view = new MaskEditTextView(options);
|
||||
break;
|
||||
|
||||
// Multi Line Edit Text
|
||||
case 'MT' :
|
||||
// :TODO: apply params
|
||||
view = new MultiLineEditTextView(options);
|
||||
break;
|
||||
// Multi Line Edit Text
|
||||
case 'MT' :
|
||||
// :TODO: apply params
|
||||
view = new MultiLineEditTextView(options);
|
||||
break;
|
||||
|
||||
// Pre-defined Label (Text View)
|
||||
// :TODO: Currently no real point of PL -- @method replaces this pretty much... probably remove
|
||||
case 'PL' :
|
||||
if(mci.args.length > 0) {
|
||||
options.text = getPredefinedMCIValue(this.client, mci.args[0]);
|
||||
if(options.text) {
|
||||
setOption(1, 'textStyle');
|
||||
setOption(2, 'justify');
|
||||
setWidth(3);
|
||||
// Pre-defined Label (Text View)
|
||||
// :TODO: Currently no real point of PL -- @method replaces this pretty much... probably remove
|
||||
case 'PL' :
|
||||
if(mci.args.length > 0) {
|
||||
options.text = getPredefinedMCIValue(this.client, mci.args[0]);
|
||||
if(options.text) {
|
||||
setOption(1, 'textStyle');
|
||||
setOption(2, 'justify');
|
||||
setWidth(3);
|
||||
|
||||
view = new TextView(options);
|
||||
}
|
||||
}
|
||||
break;
|
||||
view = new TextView(options);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Button
|
||||
case 'BT' :
|
||||
if(mci.args.length > 0) {
|
||||
options.dimens = { width : parseInt(mci.args[0], 10) };
|
||||
}
|
||||
// Button
|
||||
case 'BT' :
|
||||
if(mci.args.length > 0) {
|
||||
options.dimens = { width : parseInt(mci.args[0], 10) };
|
||||
}
|
||||
|
||||
setOption(1, 'textStyle');
|
||||
setOption(2, 'justify');
|
||||
setOption(1, 'textStyle');
|
||||
setOption(2, 'justify');
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new ButtonView(options);
|
||||
break;
|
||||
view = new ButtonView(options);
|
||||
break;
|
||||
|
||||
// Vertial Menu
|
||||
case 'VM' :
|
||||
setOption(0, 'itemSpacing');
|
||||
setOption(1, 'justify');
|
||||
setOption(2, 'textStyle');
|
||||
// Vertial Menu
|
||||
case 'VM' :
|
||||
setOption(0, 'itemSpacing');
|
||||
setOption(1, 'justify');
|
||||
setOption(2, 'textStyle');
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new VerticalMenuView(options);
|
||||
break;
|
||||
view = new VerticalMenuView(options);
|
||||
break;
|
||||
|
||||
// Horizontal Menu
|
||||
case 'HM' :
|
||||
setOption(0, 'itemSpacing');
|
||||
setOption(1, 'textStyle');
|
||||
// Horizontal Menu
|
||||
case 'HM' :
|
||||
setOption(0, 'itemSpacing');
|
||||
setOption(1, 'textStyle');
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new HorizontalMenuView(options);
|
||||
break;
|
||||
view = new HorizontalMenuView(options);
|
||||
break;
|
||||
|
||||
case 'SM' :
|
||||
setOption(0, 'textStyle');
|
||||
setOption(1, 'justify');
|
||||
case 'SM' :
|
||||
setOption(0, 'textStyle');
|
||||
setOption(1, 'justify');
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new SpinnerMenuView(options);
|
||||
break;
|
||||
view = new SpinnerMenuView(options);
|
||||
break;
|
||||
|
||||
case 'TM' :
|
||||
if(mci.args.length > 0) {
|
||||
var styleSG1 = { fg : parseInt(mci.args[0], 10) };
|
||||
if(mci.args.length > 1) {
|
||||
styleSG1.bg = parseInt(mci.args[1], 10);
|
||||
}
|
||||
options.styleSG1 = ansi.getSGRFromGraphicRendition(styleSG1, true);
|
||||
}
|
||||
case 'TM' :
|
||||
if(mci.args.length > 0) {
|
||||
var styleSG1 = { fg : parseInt(mci.args[0], 10) };
|
||||
if(mci.args.length > 1) {
|
||||
styleSG1.bg = parseInt(mci.args[1], 10);
|
||||
}
|
||||
options.styleSG1 = ansi.getSGRFromGraphicRendition(styleSG1, true);
|
||||
}
|
||||
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
setFocusOption(0, 'focusTextStyle');
|
||||
|
||||
view = new ToggleMenuView(options);
|
||||
break;
|
||||
view = new ToggleMenuView(options);
|
||||
break;
|
||||
|
||||
case 'KE' :
|
||||
view = new KeyEntryView(options);
|
||||
break;
|
||||
case 'KE' :
|
||||
view = new KeyEntryView(options);
|
||||
break;
|
||||
|
||||
default :
|
||||
options.text = getPredefinedMCIValue(this.client, mci.code);
|
||||
if(_.isString(options.text)) {
|
||||
setWidth(0);
|
||||
default :
|
||||
options.text = getPredefinedMCIValue(this.client, mci.code);
|
||||
if(_.isString(options.text)) {
|
||||
setWidth(0);
|
||||
|
||||
setOption(1, 'textStyle');
|
||||
setOption(2, 'justify');
|
||||
setOption(1, 'textStyle');
|
||||
setOption(2, 'justify');
|
||||
|
||||
view = new TextView(options);
|
||||
}
|
||||
break;
|
||||
}
|
||||
view = new TextView(options);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(view) {
|
||||
view.mciCode = mci.code;
|
||||
}
|
||||
if(view) {
|
||||
view.mciCode = mci.code;
|
||||
}
|
||||
|
||||
return view;
|
||||
return view;
|
||||
};
|
||||
|
|
|
@ -19,358 +19,358 @@ const _ = require('lodash');
|
|||
|
||||
exports.MenuModule = class MenuModule extends PluginModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.menuName = options.menuName;
|
||||
this.menuConfig = options.menuConfig;
|
||||
this.client = options.client;
|
||||
this.menuConfig.options = options.menuConfig.options || {};
|
||||
this.menuMethods = {}; // methods called from @method's
|
||||
this.menuConfig.config = this.menuConfig.config || {};
|
||||
this.menuName = options.menuName;
|
||||
this.menuConfig = options.menuConfig;
|
||||
this.client = options.client;
|
||||
this.menuConfig.options = options.menuConfig.options || {};
|
||||
this.menuMethods = {}; // methods called from @method's
|
||||
this.menuConfig.config = this.menuConfig.config || {};
|
||||
|
||||
this.cls = _.isBoolean(this.menuConfig.options.cls) ? this.menuConfig.options.cls : Config().menus.cls;
|
||||
this.cls = _.isBoolean(this.menuConfig.options.cls) ? this.menuConfig.options.cls : Config().menus.cls;
|
||||
|
||||
this.viewControllers = {};
|
||||
}
|
||||
this.viewControllers = {};
|
||||
}
|
||||
|
||||
enter() {
|
||||
this.initSequence();
|
||||
}
|
||||
enter() {
|
||||
this.initSequence();
|
||||
}
|
||||
|
||||
leave() {
|
||||
this.detachViewControllers();
|
||||
}
|
||||
leave() {
|
||||
this.detachViewControllers();
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
const mciData = {};
|
||||
let pausePosition;
|
||||
initSequence() {
|
||||
const self = this;
|
||||
const mciData = {};
|
||||
let pausePosition;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
self.beforeArt(callback);
|
||||
},
|
||||
function displayMenuArt(callback) {
|
||||
if(!_.isString(self.menuConfig.art)) {
|
||||
return callback(null);
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
self.beforeArt(callback);
|
||||
},
|
||||
function displayMenuArt(callback) {
|
||||
if(!_.isString(self.menuConfig.art)) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
self.displayAsset(
|
||||
self.menuConfig.art,
|
||||
self.menuConfig.options,
|
||||
(err, artData) => {
|
||||
if(err) {
|
||||
self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } );
|
||||
} else {
|
||||
mciData.menu = artData.mciMap;
|
||||
}
|
||||
self.displayAsset(
|
||||
self.menuConfig.art,
|
||||
self.menuConfig.options,
|
||||
(err, artData) => {
|
||||
if(err) {
|
||||
self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } );
|
||||
} else {
|
||||
mciData.menu = artData.mciMap;
|
||||
}
|
||||
|
||||
return callback(null); // any errors are non-fatal
|
||||
}
|
||||
);
|
||||
},
|
||||
function moveToPromptLocation(callback) {
|
||||
if(self.menuConfig.prompt) {
|
||||
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
|
||||
}
|
||||
return callback(null); // any errors are non-fatal
|
||||
}
|
||||
);
|
||||
},
|
||||
function moveToPromptLocation(callback) {
|
||||
if(self.menuConfig.prompt) {
|
||||
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function displayPromptArt(callback) {
|
||||
if(!_.isString(self.menuConfig.prompt)) {
|
||||
return callback(null);
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function displayPromptArt(callback) {
|
||||
if(!_.isString(self.menuConfig.prompt)) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
if(!_.isObject(self.menuConfig.promptConfig)) {
|
||||
return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found'));
|
||||
}
|
||||
if(!_.isObject(self.menuConfig.promptConfig)) {
|
||||
return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found'));
|
||||
}
|
||||
|
||||
self.displayAsset(
|
||||
self.menuConfig.promptConfig.art,
|
||||
self.menuConfig.options,
|
||||
(err, artData) => {
|
||||
if(artData) {
|
||||
mciData.prompt = artData.mciMap;
|
||||
}
|
||||
return callback(err); // pass err here; prompts *must* have art
|
||||
}
|
||||
);
|
||||
},
|
||||
function recordCursorPosition(callback) {
|
||||
if(!self.shouldPause()) {
|
||||
return callback(null); // cursor position not needed
|
||||
}
|
||||
self.displayAsset(
|
||||
self.menuConfig.promptConfig.art,
|
||||
self.menuConfig.options,
|
||||
(err, artData) => {
|
||||
if(artData) {
|
||||
mciData.prompt = artData.mciMap;
|
||||
}
|
||||
return callback(err); // pass err here; prompts *must* have art
|
||||
}
|
||||
);
|
||||
},
|
||||
function recordCursorPosition(callback) {
|
||||
if(!self.shouldPause()) {
|
||||
return callback(null); // cursor position not needed
|
||||
}
|
||||
|
||||
self.client.once('cursor position report', pos => {
|
||||
pausePosition = { row : pos[0], col : 1 };
|
||||
self.client.log.trace('After art position recorded', pausePosition );
|
||||
return callback(null);
|
||||
});
|
||||
self.client.once('cursor position report', pos => {
|
||||
pausePosition = { row : pos[0], col : 1 };
|
||||
self.client.log.trace('After art position recorded', pausePosition );
|
||||
return callback(null);
|
||||
});
|
||||
|
||||
self.client.term.rawWrite(ansi.queryPos());
|
||||
},
|
||||
function afterArtDisplayed(callback) {
|
||||
return self.mciReady(mciData, callback);
|
||||
},
|
||||
function displayPauseIfRequested(callback) {
|
||||
if(!self.shouldPause()) {
|
||||
return callback(null);
|
||||
}
|
||||
self.client.term.rawWrite(ansi.queryPos());
|
||||
},
|
||||
function afterArtDisplayed(callback) {
|
||||
return self.mciReady(mciData, callback);
|
||||
},
|
||||
function displayPauseIfRequested(callback) {
|
||||
if(!self.shouldPause()) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
return self.pausePrompt(pausePosition, callback);
|
||||
},
|
||||
function finishAndNext(callback) {
|
||||
self.finishedLoading();
|
||||
return self.autoNextMenu(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn('Error during init sequence', { error : err.message } );
|
||||
return self.pausePrompt(pausePosition, callback);
|
||||
},
|
||||
function finishAndNext(callback) {
|
||||
self.finishedLoading();
|
||||
return self.autoNextMenu(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.warn('Error during init sequence', { error : err.message } );
|
||||
|
||||
return self.prevMenu( () => { /* dummy */ } );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return self.prevMenu( () => { /* dummy */ } );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
beforeArt(cb) {
|
||||
if(_.isNumber(this.menuConfig.options.baudRate)) {
|
||||
// :TODO: some terminals not supporting cterm style emulated baud rate end up displaying a broken ESC sequence or a single "r" here
|
||||
this.client.term.rawWrite(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate));
|
||||
}
|
||||
beforeArt(cb) {
|
||||
if(_.isNumber(this.menuConfig.options.baudRate)) {
|
||||
// :TODO: some terminals not supporting cterm style emulated baud rate end up displaying a broken ESC sequence or a single "r" here
|
||||
this.client.term.rawWrite(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate));
|
||||
}
|
||||
|
||||
if(this.cls) {
|
||||
this.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
if(this.cls) {
|
||||
this.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
}
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
// available for sub-classes
|
||||
return cb(null);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
// available for sub-classes
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
finishedLoading() {
|
||||
// nothing in base
|
||||
}
|
||||
finishedLoading() {
|
||||
// nothing in base
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
// nothing in base
|
||||
}
|
||||
getSaveState() {
|
||||
// nothing in base
|
||||
}
|
||||
|
||||
restoreSavedState(/*savedState*/) {
|
||||
// nothing in base
|
||||
}
|
||||
restoreSavedState(/*savedState*/) {
|
||||
// nothing in base
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
// default to the formData that was provided @ a submit, if any
|
||||
return this.submitFormData;
|
||||
}
|
||||
getMenuResult() {
|
||||
// default to the formData that was provided @ a submit, if any
|
||||
return this.submitFormData;
|
||||
}
|
||||
|
||||
nextMenu(cb) {
|
||||
if(!this.haveNext()) {
|
||||
return this.prevMenu(cb); // no next, go to prev
|
||||
}
|
||||
nextMenu(cb) {
|
||||
if(!this.haveNext()) {
|
||||
return this.prevMenu(cb); // no next, go to prev
|
||||
}
|
||||
|
||||
return this.client.menuStack.next(cb);
|
||||
}
|
||||
return this.client.menuStack.next(cb);
|
||||
}
|
||||
|
||||
prevMenu(cb) {
|
||||
return this.client.menuStack.prev(cb);
|
||||
}
|
||||
prevMenu(cb) {
|
||||
return this.client.menuStack.prev(cb);
|
||||
}
|
||||
|
||||
gotoMenu(name, options, cb) {
|
||||
return this.client.menuStack.goto(name, options, cb);
|
||||
}
|
||||
gotoMenu(name, options, cb) {
|
||||
return this.client.menuStack.goto(name, options, cb);
|
||||
}
|
||||
|
||||
addViewController(name, vc) {
|
||||
assert(!this.viewControllers[name], `ViewController by the name of "${name}" already exists!`);
|
||||
addViewController(name, vc) {
|
||||
assert(!this.viewControllers[name], `ViewController by the name of "${name}" already exists!`);
|
||||
|
||||
this.viewControllers[name] = vc;
|
||||
return vc;
|
||||
}
|
||||
this.viewControllers[name] = vc;
|
||||
return vc;
|
||||
}
|
||||
|
||||
detachViewControllers() {
|
||||
Object.keys(this.viewControllers).forEach( name => {
|
||||
this.viewControllers[name].detachClientEvents();
|
||||
});
|
||||
}
|
||||
detachViewControllers() {
|
||||
Object.keys(this.viewControllers).forEach( name => {
|
||||
this.viewControllers[name].detachClientEvents();
|
||||
});
|
||||
}
|
||||
|
||||
shouldPause() {
|
||||
return ('end' === this.menuConfig.options.pause || true === this.menuConfig.options.pause);
|
||||
}
|
||||
shouldPause() {
|
||||
return ('end' === this.menuConfig.options.pause || true === this.menuConfig.options.pause);
|
||||
}
|
||||
|
||||
hasNextTimeout() {
|
||||
return _.isNumber(this.menuConfig.options.nextTimeout);
|
||||
}
|
||||
hasNextTimeout() {
|
||||
return _.isNumber(this.menuConfig.options.nextTimeout);
|
||||
}
|
||||
|
||||
haveNext() {
|
||||
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
|
||||
}
|
||||
haveNext() {
|
||||
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
|
||||
}
|
||||
|
||||
autoNextMenu(cb) {
|
||||
const self = this;
|
||||
autoNextMenu(cb) {
|
||||
const self = this;
|
||||
|
||||
function gotoNextMenu() {
|
||||
if(self.haveNext()) {
|
||||
return menuUtil.handleNext(self.client, self.menuConfig.next, {}, cb);
|
||||
} else {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
function gotoNextMenu() {
|
||||
if(self.haveNext()) {
|
||||
return menuUtil.handleNext(self.client, self.menuConfig.next, {}, cb);
|
||||
} else {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
|
||||
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
||||
if(this.hasNextTimeout()) {
|
||||
setTimeout( () => {
|
||||
return gotoNextMenu();
|
||||
}, this.menuConfig.options.nextTimeout);
|
||||
} else {
|
||||
return gotoNextMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
||||
if(this.hasNextTimeout()) {
|
||||
setTimeout( () => {
|
||||
return gotoNextMenu();
|
||||
}, this.menuConfig.options.nextTimeout);
|
||||
} else {
|
||||
return gotoNextMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standardMCIReadyHandler(mciData, cb) {
|
||||
//
|
||||
// A quick rundown:
|
||||
// * We may have mciData.menu, mciData.prompt, or both.
|
||||
// * Prompt form is favored over menu form if both are present.
|
||||
// * Standard/prefdefined MCI entries must load both (e.g. %BN is expected to resolve)
|
||||
//
|
||||
const self = this;
|
||||
standardMCIReadyHandler(mciData, cb) {
|
||||
//
|
||||
// A quick rundown:
|
||||
// * We may have mciData.menu, mciData.prompt, or both.
|
||||
// * Prompt form is favored over menu form if both are present.
|
||||
// * Standard/prefdefined MCI entries must load both (e.g. %BN is expected to resolve)
|
||||
//
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function addViewControllers(callback) {
|
||||
_.forEach(mciData, (mciMap, name) => {
|
||||
assert('menu' === name || 'prompt' === name);
|
||||
self.addViewController(name, new ViewController( { client : self.client } ) );
|
||||
});
|
||||
async.series(
|
||||
[
|
||||
function addViewControllers(callback) {
|
||||
_.forEach(mciData, (mciMap, name) => {
|
||||
assert('menu' === name || 'prompt' === name);
|
||||
self.addViewController(name, new ViewController( { client : self.client } ) );
|
||||
});
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function createMenu(callback) {
|
||||
if(!self.viewControllers.menu) {
|
||||
return callback(null);
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function createMenu(callback) {
|
||||
if(!self.viewControllers.menu) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
const menuLoadOpts = {
|
||||
mciMap : mciData.menu,
|
||||
callingMenu : self,
|
||||
withoutForm : _.isObject(mciData.prompt),
|
||||
};
|
||||
const menuLoadOpts = {
|
||||
mciMap : mciData.menu,
|
||||
callingMenu : self,
|
||||
withoutForm : _.isObject(mciData.prompt),
|
||||
};
|
||||
|
||||
self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, err => {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function createPrompt(callback) {
|
||||
if(!self.viewControllers.prompt) {
|
||||
return callback(null);
|
||||
}
|
||||
self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, err => {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function createPrompt(callback) {
|
||||
if(!self.viewControllers.prompt) {
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
const promptLoadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.prompt,
|
||||
};
|
||||
const promptLoadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.prompt,
|
||||
};
|
||||
|
||||
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayAsset(name, options, cb) {
|
||||
if(_.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
displayAsset(name, options, cb) {
|
||||
if(_.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
if(options.clearScreen) {
|
||||
this.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
if(options.clearScreen) {
|
||||
this.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
return theme.displayThemedAsset(
|
||||
name,
|
||||
this.client,
|
||||
Object.assign( { font : this.menuConfig.config.font }, options ),
|
||||
(err, artData) => {
|
||||
if(cb) {
|
||||
return cb(err, artData);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return theme.displayThemedAsset(
|
||||
name,
|
||||
this.client,
|
||||
Object.assign( { font : this.menuConfig.config.font }, options ),
|
||||
(err, artData) => {
|
||||
if(cb) {
|
||||
return cb(err, artData);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
prepViewController(name, formId, mciMap, cb) {
|
||||
if(_.isUndefined(this.viewControllers[name])) {
|
||||
const vcOpts = {
|
||||
client : this.client,
|
||||
formId : formId,
|
||||
};
|
||||
prepViewController(name, formId, mciMap, cb) {
|
||||
if(_.isUndefined(this.viewControllers[name])) {
|
||||
const vcOpts = {
|
||||
client : this.client,
|
||||
formId : formId,
|
||||
};
|
||||
|
||||
const vc = this.addViewController(name, new ViewController(vcOpts));
|
||||
const vc = this.addViewController(name, new ViewController(vcOpts));
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : this,
|
||||
mciMap : mciMap,
|
||||
formId : formId,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : this,
|
||||
mciMap : mciMap,
|
||||
formId : formId,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, err => {
|
||||
return cb(err, vc);
|
||||
});
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, err => {
|
||||
return cb(err, vc);
|
||||
});
|
||||
}
|
||||
|
||||
this.viewControllers[name].setFocus(true);
|
||||
this.viewControllers[name].setFocus(true);
|
||||
|
||||
return cb(null, this.viewControllers[name]);
|
||||
}
|
||||
return cb(null, this.viewControllers[name]);
|
||||
}
|
||||
|
||||
prepViewControllerWithArt(name, formId, options, cb) {
|
||||
this.displayAsset(
|
||||
this.menuConfig.config.art[name],
|
||||
options,
|
||||
(err, artData) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
prepViewControllerWithArt(name, formId, options, cb) {
|
||||
this.displayAsset(
|
||||
this.menuConfig.config.art[name],
|
||||
options,
|
||||
(err, artData) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
return this.prepViewController(name, formId, artData.mciMap, cb);
|
||||
}
|
||||
);
|
||||
}
|
||||
return this.prepViewController(name, formId, artData.mciMap, cb);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
optionalMoveToPosition(position) {
|
||||
if(position) {
|
||||
position.x = position.row || position.x || 1;
|
||||
position.y = position.col || position.y || 1;
|
||||
optionalMoveToPosition(position) {
|
||||
if(position) {
|
||||
position.x = position.row || position.x || 1;
|
||||
position.y = position.col || position.y || 1;
|
||||
|
||||
this.client.term.rawWrite(ansi.goto(position.x, position.y));
|
||||
}
|
||||
}
|
||||
this.client.term.rawWrite(ansi.goto(position.x, position.y));
|
||||
}
|
||||
}
|
||||
|
||||
pausePrompt(position, cb) {
|
||||
if(!cb && _.isFunction(position)) {
|
||||
cb = position;
|
||||
position = null;
|
||||
}
|
||||
pausePrompt(position, cb) {
|
||||
if(!cb && _.isFunction(position)) {
|
||||
cb = position;
|
||||
position = null;
|
||||
}
|
||||
|
||||
this.optionalMoveToPosition(position);
|
||||
this.optionalMoveToPosition(position);
|
||||
|
||||
return theme.displayThemedPause(this.client, cb);
|
||||
}
|
||||
return theme.displayThemedPause(this.client, cb);
|
||||
}
|
||||
|
||||
/*
|
||||
/*
|
||||
:TODO: this needs quite a bit of work - but would be nice: promptForInput(..., (err, formData) => ... )
|
||||
promptForInput(formName, name, options, cb) {
|
||||
if(!cb && _.isFunction(options)) {
|
||||
|
@ -386,55 +386,55 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
|||
}
|
||||
*/
|
||||
|
||||
setViewText(formName, mciId, text, appendMultiLine) {
|
||||
const view = this.viewControllers[formName].getView(mciId);
|
||||
if(!view) {
|
||||
return;
|
||||
}
|
||||
setViewText(formName, mciId, text, appendMultiLine) {
|
||||
const view = this.viewControllers[formName].getView(mciId);
|
||||
if(!view) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(appendMultiLine && (view instanceof MultiLineEditTextView)) {
|
||||
view.addText(text);
|
||||
} else {
|
||||
view.setText(text);
|
||||
}
|
||||
}
|
||||
if(appendMultiLine && (view instanceof MultiLineEditTextView)) {
|
||||
view.addText(text);
|
||||
} else {
|
||||
view.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
updateCustomViewTextsWithFilter(formName, startId, fmtObj, options) {
|
||||
options = options || {};
|
||||
updateCustomViewTextsWithFilter(formName, startId, fmtObj, options) {
|
||||
options = options || {};
|
||||
|
||||
let textView;
|
||||
let customMciId = startId;
|
||||
const config = this.menuConfig.config;
|
||||
const endId = options.endId || 99; // we'll fail to get a view before 99
|
||||
let textView;
|
||||
let customMciId = startId;
|
||||
const config = this.menuConfig.config;
|
||||
const endId = options.endId || 99; // we'll fail to get a view before 99
|
||||
|
||||
while(customMciId <= endId && (textView = this.viewControllers[formName].getView(customMciId)) ) {
|
||||
const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10"
|
||||
const format = config[key];
|
||||
while(customMciId <= endId && (textView = this.viewControllers[formName].getView(customMciId)) ) {
|
||||
const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10"
|
||||
const format = config[key];
|
||||
|
||||
if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) {
|
||||
const text = stringFormat(format, fmtObj);
|
||||
if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) {
|
||||
const text = stringFormat(format, fmtObj);
|
||||
|
||||
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) {
|
||||
textView.addText(text);
|
||||
} else {
|
||||
textView.setText(text);
|
||||
}
|
||||
}
|
||||
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) {
|
||||
textView.addText(text);
|
||||
} else {
|
||||
textView.setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
++customMciId;
|
||||
}
|
||||
}
|
||||
++customMciId;
|
||||
}
|
||||
}
|
||||
|
||||
refreshPredefinedMciViewsByCode(formName, mciCodes) {
|
||||
const form = _.get(this, [ 'viewControllers', formName] );
|
||||
if(form) {
|
||||
form.getViewsByMciCode(mciCodes).forEach(v => {
|
||||
if(!v.setText) {
|
||||
return;
|
||||
}
|
||||
refreshPredefinedMciViewsByCode(formName, mciCodes) {
|
||||
const form = _.get(this, [ 'viewControllers', formName] );
|
||||
if(form) {
|
||||
form.getViewsByMciCode(mciCodes).forEach(v => {
|
||||
if(!v.setText) {
|
||||
return;
|
||||
}
|
||||
|
||||
v.setText(getPredefinedMCIValue(this.client, v.mciCode));
|
||||
});
|
||||
}
|
||||
}
|
||||
v.setText(getPredefinedMCIValue(this.client, v.mciCode));
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -12,180 +12,180 @@ const assert = require('assert');
|
|||
// :TODO: Stack is backwards.... top should be most recent! :)
|
||||
|
||||
module.exports = class MenuStack {
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
this.stack = [];
|
||||
}
|
||||
constructor(client) {
|
||||
this.client = client;
|
||||
this.stack = [];
|
||||
}
|
||||
|
||||
push(moduleInfo) {
|
||||
return this.stack.push(moduleInfo);
|
||||
}
|
||||
push(moduleInfo) {
|
||||
return this.stack.push(moduleInfo);
|
||||
}
|
||||
|
||||
pop() {
|
||||
return this.stack.pop();
|
||||
}
|
||||
pop() {
|
||||
return this.stack.pop();
|
||||
}
|
||||
|
||||
peekPrev() {
|
||||
if(this.stackSize > 1) {
|
||||
return this.stack[this.stack.length - 2];
|
||||
}
|
||||
}
|
||||
peekPrev() {
|
||||
if(this.stackSize > 1) {
|
||||
return this.stack[this.stack.length - 2];
|
||||
}
|
||||
}
|
||||
|
||||
top() {
|
||||
if(this.stackSize > 0) {
|
||||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
}
|
||||
top() {
|
||||
if(this.stackSize > 0) {
|
||||
return this.stack[this.stack.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
get stackSize() {
|
||||
return this.stack.length;
|
||||
}
|
||||
get stackSize() {
|
||||
return this.stack.length;
|
||||
}
|
||||
|
||||
get currentModule() {
|
||||
const top = this.top();
|
||||
if(top) {
|
||||
return top.instance;
|
||||
}
|
||||
}
|
||||
get currentModule() {
|
||||
const top = this.top();
|
||||
if(top) {
|
||||
return top.instance;
|
||||
}
|
||||
}
|
||||
|
||||
next(cb) {
|
||||
const currentModuleInfo = this.top();
|
||||
assert(currentModuleInfo, 'Empty menu stack!');
|
||||
next(cb) {
|
||||
const currentModuleInfo = this.top();
|
||||
assert(currentModuleInfo, 'Empty menu stack!');
|
||||
|
||||
const menuConfig = currentModuleInfo.instance.menuConfig;
|
||||
const nextMenu = this.client.acs.getConditionalValue(menuConfig.next, 'next');
|
||||
if(!nextMenu) {
|
||||
return cb(Array.isArray(menuConfig.next) ?
|
||||
Errors.MenuStack('No matching condition for "next"', 'NOCONDMATCH') :
|
||||
Errors.MenuStack('Invalid or missing "next" member in menu config', 'BADNEXT')
|
||||
);
|
||||
}
|
||||
const menuConfig = currentModuleInfo.instance.menuConfig;
|
||||
const nextMenu = this.client.acs.getConditionalValue(menuConfig.next, 'next');
|
||||
if(!nextMenu) {
|
||||
return cb(Array.isArray(menuConfig.next) ?
|
||||
Errors.MenuStack('No matching condition for "next"', 'NOCONDMATCH') :
|
||||
Errors.MenuStack('Invalid or missing "next" member in menu config', 'BADNEXT')
|
||||
);
|
||||
}
|
||||
|
||||
if(nextMenu === currentModuleInfo.name) {
|
||||
return cb(Errors.MenuStack('Menu config "next" specifies current menu', 'ALREADYTHERE'));
|
||||
}
|
||||
if(nextMenu === currentModuleInfo.name) {
|
||||
return cb(Errors.MenuStack('Menu config "next" specifies current menu', 'ALREADYTHERE'));
|
||||
}
|
||||
|
||||
this.goto(nextMenu, { }, cb);
|
||||
}
|
||||
this.goto(nextMenu, { }, cb);
|
||||
}
|
||||
|
||||
prev(cb) {
|
||||
const menuResult = this.top().instance.getMenuResult();
|
||||
prev(cb) {
|
||||
const menuResult = this.top().instance.getMenuResult();
|
||||
|
||||
// :TODO: leave() should really take a cb...
|
||||
this.pop().instance.leave(); // leave & remove current
|
||||
// :TODO: leave() should really take a cb...
|
||||
this.pop().instance.leave(); // leave & remove current
|
||||
|
||||
const previousModuleInfo = this.pop(); // get previous
|
||||
const previousModuleInfo = this.pop(); // get previous
|
||||
|
||||
if(previousModuleInfo) {
|
||||
const opts = {
|
||||
extraArgs : previousModuleInfo.extraArgs,
|
||||
savedState : previousModuleInfo.savedState,
|
||||
lastMenuResult : menuResult,
|
||||
};
|
||||
if(previousModuleInfo) {
|
||||
const opts = {
|
||||
extraArgs : previousModuleInfo.extraArgs,
|
||||
savedState : previousModuleInfo.savedState,
|
||||
lastMenuResult : menuResult,
|
||||
};
|
||||
|
||||
return this.goto(previousModuleInfo.name, opts, cb);
|
||||
}
|
||||
return this.goto(previousModuleInfo.name, opts, cb);
|
||||
}
|
||||
|
||||
return cb(Errors.MenuStack('No previous menu available', 'NOPREV'));
|
||||
}
|
||||
return cb(Errors.MenuStack('No previous menu available', 'NOPREV'));
|
||||
}
|
||||
|
||||
goto(name, options, cb) {
|
||||
const currentModuleInfo = this.top();
|
||||
goto(name, options, cb) {
|
||||
const currentModuleInfo = this.top();
|
||||
|
||||
if(!cb && _.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
if(!cb && _.isFunction(options)) {
|
||||
cb = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
options = options || {};
|
||||
const self = this;
|
||||
options = options || {};
|
||||
const self = this;
|
||||
|
||||
if(currentModuleInfo && name === currentModuleInfo.name) {
|
||||
if(cb) {
|
||||
cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if(currentModuleInfo && name === currentModuleInfo.name) {
|
||||
if(cb) {
|
||||
cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const loadOpts = {
|
||||
name : name,
|
||||
client : self.client,
|
||||
};
|
||||
const loadOpts = {
|
||||
name : name,
|
||||
client : self.client,
|
||||
};
|
||||
|
||||
if(currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) {
|
||||
loadOpts.extraArgs = currentModuleInfo.extraArgs;
|
||||
} else {
|
||||
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
||||
}
|
||||
loadOpts.lastMenuResult = options.lastMenuResult;
|
||||
if(currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) {
|
||||
loadOpts.extraArgs = currentModuleInfo.extraArgs;
|
||||
} else {
|
||||
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
||||
}
|
||||
loadOpts.lastMenuResult = options.lastMenuResult;
|
||||
|
||||
loadMenu(loadOpts, (err, modInst) => {
|
||||
if(err) {
|
||||
// :TODO: probably should just require a cb...
|
||||
const errCb = cb || self.client.defaultHandlerMissingMod();
|
||||
errCb(err);
|
||||
} else {
|
||||
self.client.log.debug( { menuName : name }, 'Goto menu module');
|
||||
loadMenu(loadOpts, (err, modInst) => {
|
||||
if(err) {
|
||||
// :TODO: probably should just require a cb...
|
||||
const errCb = cb || self.client.defaultHandlerMissingMod();
|
||||
errCb(err);
|
||||
} else {
|
||||
self.client.log.debug( { menuName : name }, 'Goto menu module');
|
||||
|
||||
//
|
||||
// If menuFlags were supplied in menu.hjson, they should win over
|
||||
// anything supplied in code.
|
||||
//
|
||||
let menuFlags;
|
||||
if(0 === modInst.menuConfig.options.menuFlags.length) {
|
||||
menuFlags = Array.isArray(options.menuFlags) ? options.menuFlags : [];
|
||||
} else {
|
||||
menuFlags = modInst.menuConfig.options.menuFlags;
|
||||
//
|
||||
// If menuFlags were supplied in menu.hjson, they should win over
|
||||
// anything supplied in code.
|
||||
//
|
||||
let menuFlags;
|
||||
if(0 === modInst.menuConfig.options.menuFlags.length) {
|
||||
menuFlags = Array.isArray(options.menuFlags) ? options.menuFlags : [];
|
||||
} else {
|
||||
menuFlags = modInst.menuConfig.options.menuFlags;
|
||||
|
||||
// in code we can ask to merge in
|
||||
if(Array.isArray(options.menuFlags) && options.menuFlags.includes('mergeFlags')) {
|
||||
menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
|
||||
}
|
||||
}
|
||||
// in code we can ask to merge in
|
||||
if(Array.isArray(options.menuFlags) && options.menuFlags.includes('mergeFlags')) {
|
||||
menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
|
||||
}
|
||||
}
|
||||
|
||||
if(currentModuleInfo) {
|
||||
// save stack state
|
||||
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
|
||||
if(currentModuleInfo) {
|
||||
// save stack state
|
||||
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
|
||||
|
||||
currentModuleInfo.instance.leave();
|
||||
currentModuleInfo.instance.leave();
|
||||
|
||||
if(currentModuleInfo.menuFlags.includes('noHistory')) {
|
||||
this.pop();
|
||||
}
|
||||
if(currentModuleInfo.menuFlags.includes('noHistory')) {
|
||||
this.pop();
|
||||
}
|
||||
|
||||
if(menuFlags.includes('popParent')) {
|
||||
this.pop().instance.leave(); // leave & remove current
|
||||
}
|
||||
}
|
||||
if(menuFlags.includes('popParent')) {
|
||||
this.pop().instance.leave(); // leave & remove current
|
||||
}
|
||||
}
|
||||
|
||||
self.push({
|
||||
name : name,
|
||||
instance : modInst,
|
||||
extraArgs : loadOpts.extraArgs,
|
||||
menuFlags : menuFlags,
|
||||
});
|
||||
self.push({
|
||||
name : name,
|
||||
instance : modInst,
|
||||
extraArgs : loadOpts.extraArgs,
|
||||
menuFlags : menuFlags,
|
||||
});
|
||||
|
||||
// restore previous state if requested
|
||||
if(options.savedState) {
|
||||
modInst.restoreSavedState(options.savedState);
|
||||
}
|
||||
// restore previous state if requested
|
||||
if(options.savedState) {
|
||||
modInst.restoreSavedState(options.savedState);
|
||||
}
|
||||
|
||||
const stackEntries = self.stack.map(stackEntry => {
|
||||
let name = stackEntry.name;
|
||||
if(stackEntry.instance.menuConfig.options.menuFlags.length > 0) {
|
||||
name += ` (${stackEntry.instance.menuConfig.options.menuFlags.join(', ')})`;
|
||||
}
|
||||
return name;
|
||||
});
|
||||
const stackEntries = self.stack.map(stackEntry => {
|
||||
let name = stackEntry.name;
|
||||
if(stackEntry.instance.menuConfig.options.menuFlags.length > 0) {
|
||||
name += ` (${stackEntry.instance.menuConfig.options.menuFlags.join(', ')})`;
|
||||
}
|
||||
return name;
|
||||
});
|
||||
|
||||
self.client.log.trace( { stack : stackEntries }, 'Updated menu stack' );
|
||||
self.client.log.trace( { stack : stackEntries }, 'Updated menu stack' );
|
||||
|
||||
modInst.enter();
|
||||
modInst.enter();
|
||||
|
||||
if(cb) {
|
||||
cb(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if(cb) {
|
||||
cb(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -19,243 +19,243 @@ exports.handleAction = handleAction;
|
|||
exports.handleNext = handleNext;
|
||||
|
||||
function getMenuConfig(client, name, cb) {
|
||||
var menuConfig;
|
||||
var menuConfig;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function locateMenuConfig(callback) {
|
||||
if(_.has(client.currentTheme, [ 'menus', name ])) {
|
||||
menuConfig = client.currentTheme.menus[name];
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('No menu entry for \'' + name + '\''));
|
||||
}
|
||||
},
|
||||
function locatePromptConfig(callback) {
|
||||
if(_.isString(menuConfig.prompt)) {
|
||||
if(_.has(client.currentTheme, [ 'prompts', menuConfig.prompt ])) {
|
||||
menuConfig.promptConfig = client.currentTheme.prompts[menuConfig.prompt];
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('No prompt entry for \'' + menuConfig.prompt + '\''));
|
||||
}
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err, menuConfig);
|
||||
}
|
||||
);
|
||||
async.waterfall(
|
||||
[
|
||||
function locateMenuConfig(callback) {
|
||||
if(_.has(client.currentTheme, [ 'menus', name ])) {
|
||||
menuConfig = client.currentTheme.menus[name];
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('No menu entry for \'' + name + '\''));
|
||||
}
|
||||
},
|
||||
function locatePromptConfig(callback) {
|
||||
if(_.isString(menuConfig.prompt)) {
|
||||
if(_.has(client.currentTheme, [ 'prompts', menuConfig.prompt ])) {
|
||||
menuConfig.promptConfig = client.currentTheme.prompts[menuConfig.prompt];
|
||||
callback(null);
|
||||
} else {
|
||||
callback(new Error('No prompt entry for \'' + menuConfig.prompt + '\''));
|
||||
}
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err, menuConfig);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function loadMenu(options, cb) {
|
||||
assert(_.isObject(options));
|
||||
assert(_.isString(options.name));
|
||||
assert(_.isObject(options.client));
|
||||
assert(_.isObject(options));
|
||||
assert(_.isString(options.name));
|
||||
assert(_.isObject(options.client));
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function getMenuConfiguration(callback) {
|
||||
getMenuConfig(options.client, options.name, (err, menuConfig) => {
|
||||
return callback(err, menuConfig);
|
||||
});
|
||||
},
|
||||
function loadMenuModule(menuConfig, callback) {
|
||||
async.waterfall(
|
||||
[
|
||||
function getMenuConfiguration(callback) {
|
||||
getMenuConfig(options.client, options.name, (err, menuConfig) => {
|
||||
return callback(err, menuConfig);
|
||||
});
|
||||
},
|
||||
function loadMenuModule(menuConfig, callback) {
|
||||
|
||||
menuConfig.options = menuConfig.options || {};
|
||||
menuConfig.options.menuFlags = menuConfig.options.menuFlags || [];
|
||||
if(!Array.isArray(menuConfig.options.menuFlags)) {
|
||||
menuConfig.options.menuFlags = [ menuConfig.options.menuFlags ];
|
||||
}
|
||||
menuConfig.options = menuConfig.options || {};
|
||||
menuConfig.options.menuFlags = menuConfig.options.menuFlags || [];
|
||||
if(!Array.isArray(menuConfig.options.menuFlags)) {
|
||||
menuConfig.options.menuFlags = [ menuConfig.options.menuFlags ];
|
||||
}
|
||||
|
||||
const modAsset = asset.getModuleAsset(menuConfig.module);
|
||||
const modSupplied = null !== modAsset;
|
||||
const modAsset = asset.getModuleAsset(menuConfig.module);
|
||||
const modSupplied = null !== modAsset;
|
||||
|
||||
const modLoadOpts = {
|
||||
name : modSupplied ? modAsset.asset : 'standard_menu',
|
||||
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config().paths.mods,
|
||||
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
|
||||
};
|
||||
const modLoadOpts = {
|
||||
name : modSupplied ? modAsset.asset : 'standard_menu',
|
||||
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config().paths.mods,
|
||||
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
|
||||
};
|
||||
|
||||
moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => {
|
||||
const modData = {
|
||||
name : modLoadOpts.name,
|
||||
config : menuConfig,
|
||||
mod : mod,
|
||||
};
|
||||
moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => {
|
||||
const modData = {
|
||||
name : modLoadOpts.name,
|
||||
config : menuConfig,
|
||||
mod : mod,
|
||||
};
|
||||
|
||||
return callback(err, modData);
|
||||
});
|
||||
},
|
||||
function createModuleInstance(modData, callback) {
|
||||
Log.trace(
|
||||
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
||||
'Creating menu module instance');
|
||||
return callback(err, modData);
|
||||
});
|
||||
},
|
||||
function createModuleInstance(modData, callback) {
|
||||
Log.trace(
|
||||
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
||||
'Creating menu module instance');
|
||||
|
||||
let moduleInstance;
|
||||
try {
|
||||
moduleInstance = new modData.mod.getModule({
|
||||
menuName : options.name,
|
||||
menuConfig : modData.config,
|
||||
extraArgs : options.extraArgs,
|
||||
client : options.client,
|
||||
lastMenuResult : options.lastMenuResult,
|
||||
});
|
||||
} catch(e) {
|
||||
return callback(e);
|
||||
}
|
||||
let moduleInstance;
|
||||
try {
|
||||
moduleInstance = new modData.mod.getModule({
|
||||
menuName : options.name,
|
||||
menuConfig : modData.config,
|
||||
extraArgs : options.extraArgs,
|
||||
client : options.client,
|
||||
lastMenuResult : options.lastMenuResult,
|
||||
});
|
||||
} catch(e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
return callback(null, moduleInstance);
|
||||
}
|
||||
],
|
||||
(err, modInst) => {
|
||||
return cb(err, modInst);
|
||||
}
|
||||
);
|
||||
return callback(null, moduleInstance);
|
||||
}
|
||||
],
|
||||
(err, modInst) => {
|
||||
return cb(err, modInst);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
|
||||
assert(_.isObject(menuConfig));
|
||||
assert(_.isObject(menuConfig));
|
||||
|
||||
if(!_.isObject(menuConfig.form)) {
|
||||
cb(new Error('Invalid or missing \'form\' member for menu'));
|
||||
return;
|
||||
}
|
||||
if(!_.isObject(menuConfig.form)) {
|
||||
cb(new Error('Invalid or missing \'form\' member for menu'));
|
||||
return;
|
||||
}
|
||||
|
||||
if(!_.isObject(menuConfig.form[formId])) {
|
||||
cb(new Error('No form found for formId ' + formId));
|
||||
return;
|
||||
}
|
||||
if(!_.isObject(menuConfig.form[formId])) {
|
||||
cb(new Error('No form found for formId ' + formId));
|
||||
return;
|
||||
}
|
||||
|
||||
const formForId = menuConfig.form[formId];
|
||||
const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), (mci) => {
|
||||
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
|
||||
}).join('');
|
||||
const formForId = menuConfig.form[formId];
|
||||
const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), (mci) => {
|
||||
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
|
||||
}).join('');
|
||||
|
||||
Log.trace( { mciKey : mciReqKey }, 'Looking for MCI configuration key');
|
||||
Log.trace( { mciKey : mciReqKey }, 'Looking for MCI configuration key');
|
||||
|
||||
//
|
||||
// Exact, explicit match?
|
||||
//
|
||||
if(_.isObject(formForId[mciReqKey])) {
|
||||
Log.trace( { mciKey : mciReqKey }, 'Using exact configuration key match');
|
||||
cb(null, formForId[mciReqKey]);
|
||||
return;
|
||||
}
|
||||
//
|
||||
// Exact, explicit match?
|
||||
//
|
||||
if(_.isObject(formForId[mciReqKey])) {
|
||||
Log.trace( { mciKey : mciReqKey }, 'Using exact configuration key match');
|
||||
cb(null, formForId[mciReqKey]);
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Generic match
|
||||
//
|
||||
if(_.has(formForId, 'mci') || _.has(formForId, 'submit')) {
|
||||
Log.trace('Using generic configuration');
|
||||
return cb(null, formForId);
|
||||
}
|
||||
//
|
||||
// Generic match
|
||||
//
|
||||
if(_.has(formForId, 'mci') || _.has(formForId, 'submit')) {
|
||||
Log.trace('Using generic configuration');
|
||||
return cb(null, formForId);
|
||||
}
|
||||
|
||||
cb(new Error('No matching form configuration found for key \'' + mciReqKey + '\''));
|
||||
cb(new Error('No matching form configuration found for key \'' + mciReqKey + '\''));
|
||||
}
|
||||
|
||||
// :TODO: Most of this should be moved elsewhere .... DRY...
|
||||
function callModuleMenuMethod(client, asset, path, formData, extraArgs, cb) {
|
||||
if('' === paths.extname(path)) {
|
||||
path += '.js';
|
||||
}
|
||||
if('' === paths.extname(path)) {
|
||||
path += '.js';
|
||||
}
|
||||
|
||||
try {
|
||||
client.log.trace(
|
||||
{ path : path, methodName : asset.asset, formData : formData, extraArgs : extraArgs },
|
||||
'Calling menu method');
|
||||
try {
|
||||
client.log.trace(
|
||||
{ path : path, methodName : asset.asset, formData : formData, extraArgs : extraArgs },
|
||||
'Calling menu method');
|
||||
|
||||
const methodMod = require(path);
|
||||
return methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs, cb);
|
||||
} catch(e) {
|
||||
client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method');
|
||||
return cb(e);
|
||||
}
|
||||
const methodMod = require(path);
|
||||
return methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs, cb);
|
||||
} catch(e) {
|
||||
client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method');
|
||||
return cb(e);
|
||||
}
|
||||
}
|
||||
|
||||
function handleAction(client, formData, conf, cb) {
|
||||
assert(_.isObject(conf));
|
||||
assert(_.isString(conf.action));
|
||||
assert(_.isObject(conf));
|
||||
assert(_.isString(conf.action));
|
||||
|
||||
const actionAsset = asset.parseAsset(conf.action);
|
||||
assert(_.isObject(actionAsset));
|
||||
const actionAsset = asset.parseAsset(conf.action);
|
||||
assert(_.isObject(actionAsset));
|
||||
|
||||
switch(actionAsset.type) {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(actionAsset.location)) {
|
||||
return callModuleMenuMethod(
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(Config().paths.mods, actionAsset.location),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
cb);
|
||||
} else if('systemMethod' === actionAsset.type) {
|
||||
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
|
||||
// :TODO: Probably better as system_method.js
|
||||
return callModuleMenuMethod(
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(__dirname, 'system_menu_method.js'),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
cb);
|
||||
} else {
|
||||
// local to current module
|
||||
const currentModule = client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
||||
return currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs, cb);
|
||||
}
|
||||
switch(actionAsset.type) {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(actionAsset.location)) {
|
||||
return callModuleMenuMethod(
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(Config().paths.mods, actionAsset.location),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
cb);
|
||||
} else if('systemMethod' === actionAsset.type) {
|
||||
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
|
||||
// :TODO: Probably better as system_method.js
|
||||
return callModuleMenuMethod(
|
||||
client,
|
||||
actionAsset,
|
||||
paths.join(__dirname, 'system_menu_method.js'),
|
||||
formData,
|
||||
conf.extraArgs,
|
||||
cb);
|
||||
} else {
|
||||
// local to current module
|
||||
const currentModule = client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
||||
return currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs, cb);
|
||||
}
|
||||
|
||||
const err = new Error('Method does not exist');
|
||||
client.log.warn( { method : actionAsset.asset }, err.message);
|
||||
return cb(err);
|
||||
}
|
||||
const err = new Error('Method does not exist');
|
||||
client.log.warn( { method : actionAsset.asset }, err.message);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
case 'menu' :
|
||||
return client.currentMenuModule.gotoMenu(actionAsset.asset, { formData : formData, extraArgs : conf.extraArgs }, cb );
|
||||
}
|
||||
case 'menu' :
|
||||
return client.currentMenuModule.gotoMenu(actionAsset.asset, { formData : formData, extraArgs : conf.extraArgs }, cb );
|
||||
}
|
||||
}
|
||||
|
||||
function handleNext(client, nextSpec, conf, cb) {
|
||||
nextSpec = client.acs.getConditionalValue(nextSpec, 'next'); // handle any conditionals
|
||||
nextSpec = client.acs.getConditionalValue(nextSpec, 'next'); // handle any conditionals
|
||||
|
||||
const nextAsset = asset.getAssetWithShorthand(nextSpec, 'menu');
|
||||
// :TODO: getAssetWithShorthand() can return undefined - handle it!
|
||||
const nextAsset = asset.getAssetWithShorthand(nextSpec, 'menu');
|
||||
// :TODO: getAssetWithShorthand() can return undefined - handle it!
|
||||
|
||||
conf = conf || {};
|
||||
const extraArgs = conf.extraArgs || {};
|
||||
conf = conf || {};
|
||||
const extraArgs = conf.extraArgs || {};
|
||||
|
||||
// :TODO: DRY this with handleAction()
|
||||
switch(nextAsset.type) {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(nextAsset.location)) {
|
||||
return callModuleMenuMethod(client, nextAsset, paths.join(Config().paths.mods, nextAsset.location), {}, extraArgs, cb);
|
||||
} else if('systemMethod' === nextAsset.type) {
|
||||
// :TODO: see other notes about system_menu_method.js here
|
||||
return callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'), {}, extraArgs, cb);
|
||||
} else {
|
||||
// local to current module
|
||||
const currentModule = client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[nextAsset.asset])) {
|
||||
const formData = {}; // we don't have any
|
||||
return currentModule.menuMethods[nextAsset.asset]( formData, extraArgs, cb );
|
||||
}
|
||||
// :TODO: DRY this with handleAction()
|
||||
switch(nextAsset.type) {
|
||||
case 'method' :
|
||||
case 'systemMethod' :
|
||||
if(_.isString(nextAsset.location)) {
|
||||
return callModuleMenuMethod(client, nextAsset, paths.join(Config().paths.mods, nextAsset.location), {}, extraArgs, cb);
|
||||
} else if('systemMethod' === nextAsset.type) {
|
||||
// :TODO: see other notes about system_menu_method.js here
|
||||
return callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'), {}, extraArgs, cb);
|
||||
} else {
|
||||
// local to current module
|
||||
const currentModule = client.currentMenuModule;
|
||||
if(_.isFunction(currentModule.menuMethods[nextAsset.asset])) {
|
||||
const formData = {}; // we don't have any
|
||||
return currentModule.menuMethods[nextAsset.asset]( formData, extraArgs, cb );
|
||||
}
|
||||
|
||||
const err = new Error('Method does not exist');
|
||||
client.log.warn( { method : nextAsset.asset }, err.message);
|
||||
return cb(err);
|
||||
}
|
||||
const err = new Error('Method does not exist');
|
||||
client.log.warn( { method : nextAsset.asset }, err.message);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
case 'menu' :
|
||||
return client.currentMenuModule.gotoMenu(nextAsset.asset, { extraArgs : extraArgs }, cb );
|
||||
}
|
||||
case 'menu' :
|
||||
return client.currentMenuModule.gotoMenu(nextAsset.asset, { extraArgs : extraArgs }, cb );
|
||||
}
|
||||
|
||||
const err = new Error('Invalid asset type for "next"');
|
||||
client.log.error( { nextSpec : nextSpec }, err.message);
|
||||
return cb(err);
|
||||
const err = new Error('Invalid asset type for "next"');
|
||||
client.log.error( { nextSpec : nextSpec }, err.message);
|
||||
return cb(err);
|
||||
}
|
||||
|
|
|
@ -14,264 +14,264 @@ const _ = require('lodash');
|
|||
exports.MenuView = MenuView;
|
||||
|
||||
function MenuView(options) {
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||
|
||||
View.call(this, options);
|
||||
View.call(this, options);
|
||||
|
||||
this.disablePipe = options.disablePipe || false;
|
||||
this.disablePipe = options.disablePipe || false;
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
if(options.items) {
|
||||
this.setItems(options.items);
|
||||
} else {
|
||||
this.items = [];
|
||||
}
|
||||
if(options.items) {
|
||||
this.setItems(options.items);
|
||||
} else {
|
||||
this.items = [];
|
||||
}
|
||||
|
||||
this.renderCache = {};
|
||||
this.renderCache = {};
|
||||
|
||||
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true);
|
||||
this.caseInsensitiveHotKeys = miscUtil.valueWithDefault(options.caseInsensitiveHotKeys, true);
|
||||
|
||||
this.setHotKeys(options.hotKeys);
|
||||
this.setHotKeys(options.hotKeys);
|
||||
|
||||
this.focusedItemIndex = options.focusedItemIndex || 0;
|
||||
this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0;
|
||||
this.focusedItemIndex = options.focusedItemIndex || 0;
|
||||
this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.focusedItemIndex : 0;
|
||||
|
||||
this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0;
|
||||
this.itemSpacing = _.isNumber(options.itemSpacing) ? options.itemSpacing : 0;
|
||||
|
||||
// :TODO: probably just replace this with owner draw / pipe codes / etc. more control, less specialization
|
||||
this.focusPrefix = options.focusPrefix || '';
|
||||
this.focusSuffix = options.focusSuffix || '';
|
||||
// :TODO: probably just replace this with owner draw / pipe codes / etc. more control, less specialization
|
||||
this.focusPrefix = options.focusPrefix || '';
|
||||
this.focusSuffix = options.focusSuffix || '';
|
||||
|
||||
this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1);
|
||||
this.justify = options.justify || 'none';
|
||||
this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1);
|
||||
this.justify = options.justify || 'none';
|
||||
|
||||
this.hasFocusItems = function() {
|
||||
return !_.isUndefined(self.focusItems);
|
||||
};
|
||||
this.hasFocusItems = function() {
|
||||
return !_.isUndefined(self.focusItems);
|
||||
};
|
||||
|
||||
this.getHotKeyItemIndex = function(ch) {
|
||||
if(ch && self.hotKeys) {
|
||||
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
|
||||
if(_.isNumber(keyIndex)) {
|
||||
return keyIndex;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
this.getHotKeyItemIndex = function(ch) {
|
||||
if(ch && self.hotKeys) {
|
||||
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
|
||||
if(_.isNumber(keyIndex)) {
|
||||
return keyIndex;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
this.emitIndexUpdate = function() {
|
||||
self.emit('index update', self.focusedItemIndex);
|
||||
}
|
||||
this.emitIndexUpdate = function() {
|
||||
self.emit('index update', self.focusedItemIndex);
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(MenuView, View);
|
||||
|
||||
MenuView.prototype.setItems = function(items) {
|
||||
if(Array.isArray(items)) {
|
||||
this.sorted = false;
|
||||
this.renderCache = {};
|
||||
if(Array.isArray(items)) {
|
||||
this.sorted = false;
|
||||
this.renderCache = {};
|
||||
|
||||
//
|
||||
// Items can be an array of strings or an array of objects.
|
||||
//
|
||||
// In the case of objects, items are considered complex and
|
||||
// may have one or more members that can later be formatted
|
||||
// against. The default member is 'text'. The member 'data'
|
||||
// may be overridden to provide a form value other than the
|
||||
// item's index.
|
||||
//
|
||||
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
|
||||
//
|
||||
let text;
|
||||
let stringItem;
|
||||
this.items = items.map(item => {
|
||||
stringItem = _.isString(item);
|
||||
if(stringItem) {
|
||||
text = item;
|
||||
} else {
|
||||
text = item.text || '';
|
||||
this.complexItems = true;
|
||||
}
|
||||
//
|
||||
// Items can be an array of strings or an array of objects.
|
||||
//
|
||||
// In the case of objects, items are considered complex and
|
||||
// may have one or more members that can later be formatted
|
||||
// against. The default member is 'text'. The member 'data'
|
||||
// may be overridden to provide a form value other than the
|
||||
// item's index.
|
||||
//
|
||||
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
|
||||
//
|
||||
let text;
|
||||
let stringItem;
|
||||
this.items = items.map(item => {
|
||||
stringItem = _.isString(item);
|
||||
if(stringItem) {
|
||||
text = item;
|
||||
} else {
|
||||
text = item.text || '';
|
||||
this.complexItems = true;
|
||||
}
|
||||
|
||||
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
|
||||
return Object.assign({ }, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
|
||||
});
|
||||
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
|
||||
return Object.assign({ }, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
|
||||
});
|
||||
|
||||
if(this.complexItems) {
|
||||
this.itemFormat = this.itemFormat || '{text}';
|
||||
}
|
||||
}
|
||||
if(this.complexItems) {
|
||||
this.itemFormat = this.itemFormat || '{text}';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MenuView.prototype.getRenderCacheItem = function(index, focusItem = false) {
|
||||
const item = this.renderCache[index];
|
||||
return item && item[focusItem ? 'focus' : 'standard'];
|
||||
const item = this.renderCache[index];
|
||||
return item && item[focusItem ? 'focus' : 'standard'];
|
||||
};
|
||||
|
||||
MenuView.prototype.setRenderCacheItem = function(index, rendered, focusItem = false) {
|
||||
this.renderCache[index] = this.renderCache[index] || {};
|
||||
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
|
||||
this.renderCache[index] = this.renderCache[index] || {};
|
||||
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
|
||||
};
|
||||
|
||||
MenuView.prototype.setSort = function(sort) {
|
||||
if(this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
|
||||
return;
|
||||
}
|
||||
if(this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = true === sort ? 'text' : sort;
|
||||
if('text' !== sort && !this.complexItems) {
|
||||
return; // need a valid sort key
|
||||
}
|
||||
const key = true === sort ? 'text' : sort;
|
||||
if('text' !== sort && !this.complexItems) {
|
||||
return; // need a valid sort key
|
||||
}
|
||||
|
||||
this.items.sort( (a, b) => {
|
||||
const a1 = a[key];
|
||||
const b1 = b[key];
|
||||
if(!a1) {
|
||||
return -1;
|
||||
}
|
||||
if(!b1) {
|
||||
return 1;
|
||||
}
|
||||
return a1.localeCompare( b1, { sensitivity : false, numeric : true } );
|
||||
});
|
||||
this.items.sort( (a, b) => {
|
||||
const a1 = a[key];
|
||||
const b1 = b[key];
|
||||
if(!a1) {
|
||||
return -1;
|
||||
}
|
||||
if(!b1) {
|
||||
return 1;
|
||||
}
|
||||
return a1.localeCompare( b1, { sensitivity : false, numeric : true } );
|
||||
});
|
||||
|
||||
this.sorted = true;
|
||||
this.sorted = true;
|
||||
};
|
||||
|
||||
MenuView.prototype.removeItem = function(index) {
|
||||
this.sorted = false;
|
||||
this.items.splice(index, 1);
|
||||
this.sorted = false;
|
||||
this.items.splice(index, 1);
|
||||
|
||||
if(this.focusItems) {
|
||||
this.focusItems.splice(index, 1);
|
||||
}
|
||||
if(this.focusItems) {
|
||||
this.focusItems.splice(index, 1);
|
||||
}
|
||||
|
||||
if(this.focusedItemIndex >= index) {
|
||||
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
|
||||
}
|
||||
if(this.focusedItemIndex >= index) {
|
||||
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
|
||||
}
|
||||
|
||||
this.positionCacheExpired = true;
|
||||
this.positionCacheExpired = true;
|
||||
};
|
||||
|
||||
MenuView.prototype.getCount = function() {
|
||||
return this.items.length;
|
||||
return this.items.length;
|
||||
};
|
||||
|
||||
MenuView.prototype.getItems = function() {
|
||||
if(this.complexItems) {
|
||||
return this.items;
|
||||
}
|
||||
if(this.complexItems) {
|
||||
return this.items;
|
||||
}
|
||||
|
||||
return this.items.map( item => {
|
||||
return item.text;
|
||||
});
|
||||
return this.items.map( item => {
|
||||
return item.text;
|
||||
});
|
||||
};
|
||||
|
||||
MenuView.prototype.getItem = function(index) {
|
||||
if(this.complexItems) {
|
||||
return this.items[index];
|
||||
}
|
||||
if(this.complexItems) {
|
||||
return this.items[index];
|
||||
}
|
||||
|
||||
return this.items[index].text;
|
||||
return this.items[index].text;
|
||||
};
|
||||
|
||||
MenuView.prototype.focusNext = function() {
|
||||
this.emitIndexUpdate();
|
||||
this.emitIndexUpdate();
|
||||
};
|
||||
|
||||
MenuView.prototype.focusPrevious = function() {
|
||||
this.emitIndexUpdate();
|
||||
this.emitIndexUpdate();
|
||||
};
|
||||
|
||||
MenuView.prototype.focusNextPageItem = function() {
|
||||
this.emitIndexUpdate();
|
||||
this.emitIndexUpdate();
|
||||
};
|
||||
|
||||
MenuView.prototype.focusPreviousPageItem = function() {
|
||||
this.emitIndexUpdate();
|
||||
this.emitIndexUpdate();
|
||||
};
|
||||
|
||||
MenuView.prototype.focusFirst = function() {
|
||||
this.emitIndexUpdate();
|
||||
this.emitIndexUpdate();
|
||||
};
|
||||
|
||||
MenuView.prototype.focusLast = function() {
|
||||
this.emitIndexUpdate();
|
||||
this.emitIndexUpdate();
|
||||
};
|
||||
|
||||
MenuView.prototype.setFocusItemIndex = function(index) {
|
||||
this.focusedItemIndex = index;
|
||||
this.focusedItemIndex = index;
|
||||
};
|
||||
|
||||
MenuView.prototype.onKeyPress = function(ch, key) {
|
||||
const itemIndex = this.getHotKeyItemIndex(ch);
|
||||
if(itemIndex >= 0) {
|
||||
this.setFocusItemIndex(itemIndex);
|
||||
const itemIndex = this.getHotKeyItemIndex(ch);
|
||||
if(itemIndex >= 0) {
|
||||
this.setFocusItemIndex(itemIndex);
|
||||
|
||||
if(true === this.hotKeySubmit) {
|
||||
this.emit('action', 'accept');
|
||||
}
|
||||
}
|
||||
if(true === this.hotKeySubmit) {
|
||||
this.emit('action', 'accept');
|
||||
}
|
||||
}
|
||||
|
||||
MenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
MenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||
};
|
||||
|
||||
MenuView.prototype.setFocusItems = function(items) {
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
if(items) {
|
||||
this.focusItems = [];
|
||||
items.forEach( itemText => {
|
||||
this.focusItems.push(
|
||||
{
|
||||
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
if(items) {
|
||||
this.focusItems = [];
|
||||
items.forEach( itemText => {
|
||||
this.focusItems.push(
|
||||
{
|
||||
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
MenuView.prototype.setItemSpacing = function(itemSpacing) {
|
||||
itemSpacing = parseInt(itemSpacing);
|
||||
assert(_.isNumber(itemSpacing));
|
||||
itemSpacing = parseInt(itemSpacing);
|
||||
assert(_.isNumber(itemSpacing));
|
||||
|
||||
this.itemSpacing = itemSpacing;
|
||||
this.positionCacheExpired = true;
|
||||
this.itemSpacing = itemSpacing;
|
||||
this.positionCacheExpired = true;
|
||||
};
|
||||
|
||||
MenuView.prototype.setPropertyValue = function(propName, value) {
|
||||
switch(propName) {
|
||||
case 'itemSpacing' : this.setItemSpacing(value); break;
|
||||
case 'items' : this.setItems(value); break;
|
||||
case 'focusItems' : this.setFocusItems(value); break;
|
||||
case 'hotKeys' : this.setHotKeys(value); break;
|
||||
case 'hotKeySubmit' : this.hotKeySubmit = value; break;
|
||||
case 'justify' : this.justify = value; break;
|
||||
case 'focusItemIndex' : this.focusedItemIndex = value; break;
|
||||
switch(propName) {
|
||||
case 'itemSpacing' : this.setItemSpacing(value); break;
|
||||
case 'items' : this.setItems(value); break;
|
||||
case 'focusItems' : this.setFocusItems(value); break;
|
||||
case 'hotKeys' : this.setHotKeys(value); break;
|
||||
case 'hotKeySubmit' : this.hotKeySubmit = value; break;
|
||||
case 'justify' : this.justify = value; break;
|
||||
case 'focusItemIndex' : this.focusedItemIndex = value; break;
|
||||
|
||||
case 'itemFormat' :
|
||||
case 'focusItemFormat' :
|
||||
this[propName] = value;
|
||||
break;
|
||||
case 'itemFormat' :
|
||||
case 'focusItemFormat' :
|
||||
this[propName] = value;
|
||||
break;
|
||||
|
||||
case 'sort' : this.setSort(value); break;
|
||||
}
|
||||
case 'sort' : this.setSort(value); break;
|
||||
}
|
||||
|
||||
MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||
MenuView.super_.prototype.setPropertyValue.call(this, propName, value);
|
||||
};
|
||||
|
||||
MenuView.prototype.setHotKeys = function(hotKeys) {
|
||||
if(_.isObject(hotKeys)) {
|
||||
if(this.caseInsensitiveHotKeys) {
|
||||
this.hotKeys = {};
|
||||
for(var key in hotKeys) {
|
||||
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
||||
}
|
||||
} else {
|
||||
this.hotKeys = hotKeys;
|
||||
}
|
||||
}
|
||||
if(_.isObject(hotKeys)) {
|
||||
if(this.caseInsensitiveHotKeys) {
|
||||
this.hotKeys = {};
|
||||
for(var key in hotKeys) {
|
||||
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
||||
}
|
||||
} else {
|
||||
this.hotKeys = hotKeys;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
1226
core/message.js
1226
core/message.js
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -4,9 +4,9 @@
|
|||
// ENiGMA½
|
||||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
const {
|
||||
getSortedAvailMessageConferences,
|
||||
getAvailableMessageAreasByConfTag,
|
||||
getSortedAvailMessageAreasByConfTag,
|
||||
getSortedAvailMessageConferences,
|
||||
getAvailableMessageAreasByConfTag,
|
||||
getSortedAvailMessageAreasByConfTag,
|
||||
} = require('./message_area.js');
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
const Message = require('./message.js');
|
||||
|
@ -15,134 +15,134 @@ const Message = require('./message.js');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message Base Search',
|
||||
desc : 'Module for quickly searching the message base',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message Base Search',
|
||||
desc : 'Module for quickly searching the message base',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
search : {
|
||||
searchTerms : 1,
|
||||
search : 2,
|
||||
conf : 3,
|
||||
area : 4,
|
||||
to : 5,
|
||||
from : 6,
|
||||
advSearch : 7,
|
||||
}
|
||||
search : {
|
||||
searchTerms : 1,
|
||||
search : 2,
|
||||
conf : 3,
|
||||
area : 4,
|
||||
to : 5,
|
||||
from : 6,
|
||||
advSearch : 7,
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class MessageBaseSearch extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.menuMethods = {
|
||||
search : (formData, extraArgs, cb) => {
|
||||
return this.searchNow(formData, cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
this.menuMethods = {
|
||||
search : (formData, extraArgs, cb) => {
|
||||
return this.searchNow(formData, cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
this.prepViewController('search', 0, mciData.menu, (err, vc) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
this.prepViewController('search', 0, mciData.menu, (err, vc) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const confView = vc.getView(MciViewIds.search.conf);
|
||||
const areaView = vc.getView(MciViewIds.search.area);
|
||||
const confView = vc.getView(MciViewIds.search.conf);
|
||||
const areaView = vc.getView(MciViewIds.search.area);
|
||||
|
||||
if(!confView || !areaView) {
|
||||
return cb(Errors.DoesNotExist('Missing one or more required views'));
|
||||
}
|
||||
if(!confView || !areaView) {
|
||||
return cb(Errors.DoesNotExist('Missing one or more required views'));
|
||||
}
|
||||
|
||||
const availConfs = [ { text : '-ALL-', data : '' } ].concat(
|
||||
getSortedAvailMessageConferences(this.client).map(conf => Object.assign(conf, { text : conf.conf.name, data : conf.confTag } )) || []
|
||||
);
|
||||
const availConfs = [ { text : '-ALL-', data : '' } ].concat(
|
||||
getSortedAvailMessageConferences(this.client).map(conf => Object.assign(conf, { text : conf.conf.name, data : conf.confTag } )) || []
|
||||
);
|
||||
|
||||
let availAreas = [ { text : '-ALL-', data : '' } ]; // note: will populate if conf changes from ALL
|
||||
let availAreas = [ { text : '-ALL-', data : '' } ]; // note: will populate if conf changes from ALL
|
||||
|
||||
confView.setItems(availConfs);
|
||||
areaView.setItems(availAreas);
|
||||
confView.setItems(availConfs);
|
||||
areaView.setItems(availAreas);
|
||||
|
||||
confView.setFocusItemIndex(0);
|
||||
areaView.setFocusItemIndex(0);
|
||||
confView.setFocusItemIndex(0);
|
||||
areaView.setFocusItemIndex(0);
|
||||
|
||||
confView.on('index update', idx => {
|
||||
availAreas = [ { text : '-ALL-', data : '' } ].concat(
|
||||
getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, { client : this.client }).map(
|
||||
area => Object.assign(area, { text : area.area.name, data : area.areaTag } )
|
||||
)
|
||||
);
|
||||
areaView.setItems(availAreas);
|
||||
areaView.setFocusItemIndex(0);
|
||||
});
|
||||
confView.on('index update', idx => {
|
||||
availAreas = [ { text : '-ALL-', data : '' } ].concat(
|
||||
getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, { client : this.client }).map(
|
||||
area => Object.assign(area, { text : area.area.name, data : area.areaTag } )
|
||||
)
|
||||
);
|
||||
areaView.setItems(availAreas);
|
||||
areaView.setFocusItemIndex(0);
|
||||
});
|
||||
|
||||
vc.switchFocus(MciViewIds.search.searchTerms);
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
vc.switchFocus(MciViewIds.search.searchTerms);
|
||||
return cb(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
searchNow(formData, cb) {
|
||||
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||
const value = formData.value;
|
||||
searchNow(formData, cb) {
|
||||
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||
const value = formData.value;
|
||||
|
||||
const filter = {
|
||||
resultType : 'messageList',
|
||||
sort : 'modTimestamp',
|
||||
terms : value.searchTerms,
|
||||
//extraFields : [ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ],
|
||||
limit : 2048, // :TODO: best way to handle this? we should probably let the user know if some results are returned
|
||||
};
|
||||
const filter = {
|
||||
resultType : 'messageList',
|
||||
sort : 'modTimestamp',
|
||||
terms : value.searchTerms,
|
||||
//extraFields : [ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ],
|
||||
limit : 2048, // :TODO: best way to handle this? we should probably let the user know if some results are returned
|
||||
};
|
||||
|
||||
if(isAdvanced) {
|
||||
filter.toUserName = value.toUserName;
|
||||
filter.fromUserName = value.fromUserName;
|
||||
if(isAdvanced) {
|
||||
filter.toUserName = value.toUserName;
|
||||
filter.fromUserName = value.fromUserName;
|
||||
|
||||
if(value.confTag && !value.areaTag) {
|
||||
// areaTag may be a string or array of strings
|
||||
// getAvailableMessageAreasByConfTag() returns a obj - we only need tags
|
||||
filter.areaTag = _.map(
|
||||
getAvailableMessageAreasByConfTag(value.confTag, { client : this.client } ),
|
||||
(area, areaTag) => areaTag
|
||||
);
|
||||
} else if(value.areaTag) {
|
||||
filter.areaTag = value.areaTag; // specific conf + area
|
||||
}
|
||||
}
|
||||
if(value.confTag && !value.areaTag) {
|
||||
// areaTag may be a string or array of strings
|
||||
// getAvailableMessageAreasByConfTag() returns a obj - we only need tags
|
||||
filter.areaTag = _.map(
|
||||
getAvailableMessageAreasByConfTag(value.confTag, { client : this.client } ),
|
||||
(area, areaTag) => areaTag
|
||||
);
|
||||
} else if(value.areaTag) {
|
||||
filter.areaTag = value.areaTag; // specific conf + area
|
||||
}
|
||||
}
|
||||
|
||||
Message.findMessages(filter, (err, messageList) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
Message.findMessages(filter, (err, messageList) => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
if(0 === messageList.length) {
|
||||
return this.gotoMenu(
|
||||
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
||||
{ menuFlags : [ 'popParent' ] },
|
||||
cb
|
||||
);
|
||||
}
|
||||
if(0 === messageList.length) {
|
||||
return this.gotoMenu(
|
||||
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
||||
{ menuFlags : [ 'popParent' ] },
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
messageList,
|
||||
noUpdateLastReadId : true
|
||||
},
|
||||
menuFlags : [ 'popParent' ],
|
||||
};
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
messageList,
|
||||
noUpdateLastReadId : true
|
||||
},
|
||||
menuFlags : [ 'popParent' ],
|
||||
};
|
||||
|
||||
return this.gotoMenu(
|
||||
this.menuConfig.config.messageListMenu || 'messageAreaMessageList',
|
||||
menuOpts,
|
||||
cb
|
||||
);
|
||||
});
|
||||
}
|
||||
return this.gotoMenu(
|
||||
this.menuConfig.config.messageListMenu || 'messageAreaMessageList',
|
||||
menuOpts,
|
||||
cb
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -10,33 +10,33 @@ exports.startup = startup;
|
|||
exports.resolveMimeType = resolveMimeType;
|
||||
|
||||
function startup(cb) {
|
||||
//
|
||||
// Add in types (not yet) supported by mime-db -- and therefor, mime-types
|
||||
//
|
||||
const ADDITIONAL_EXT_MIMETYPES = {
|
||||
ans : 'text/x-ansi',
|
||||
gz : 'application/gzip', // not in mime-types 2.1.15 :(
|
||||
lzx : 'application/x-lzx', // :TODO: submit to mime-types
|
||||
};
|
||||
//
|
||||
// Add in types (not yet) supported by mime-db -- and therefor, mime-types
|
||||
//
|
||||
const ADDITIONAL_EXT_MIMETYPES = {
|
||||
ans : 'text/x-ansi',
|
||||
gz : 'application/gzip', // not in mime-types 2.1.15 :(
|
||||
lzx : 'application/x-lzx', // :TODO: submit to mime-types
|
||||
};
|
||||
|
||||
_.forEach(ADDITIONAL_EXT_MIMETYPES, (mimeType, ext) => {
|
||||
// don't override any entries
|
||||
if(!_.isString(mimeTypes.types[ext])) {
|
||||
mimeTypes[ext] = mimeType;
|
||||
}
|
||||
_.forEach(ADDITIONAL_EXT_MIMETYPES, (mimeType, ext) => {
|
||||
// don't override any entries
|
||||
if(!_.isString(mimeTypes.types[ext])) {
|
||||
mimeTypes[ext] = mimeType;
|
||||
}
|
||||
|
||||
if(!mimeTypes.extensions[mimeType]) {
|
||||
mimeTypes.extensions[mimeType] = [ ext ];
|
||||
}
|
||||
});
|
||||
if(!mimeTypes.extensions[mimeType]) {
|
||||
mimeTypes.extensions[mimeType] = [ ext ];
|
||||
}
|
||||
});
|
||||
|
||||
return cb(null);
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
function resolveMimeType(query) {
|
||||
if(mimeTypes.extensions[query]) {
|
||||
return query; // alreaed a mime-type
|
||||
}
|
||||
if(mimeTypes.extensions[query]) {
|
||||
return query; // alreaed a mime-type
|
||||
}
|
||||
|
||||
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined
|
||||
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined
|
||||
}
|
|
@ -14,39 +14,39 @@ exports.getCleanEnigmaVersion = getCleanEnigmaVersion;
|
|||
exports.getEnigmaUserAgent = getEnigmaUserAgent;
|
||||
|
||||
function isProduction() {
|
||||
var env = process.env.NODE_ENV || 'dev';
|
||||
return 'production' === env;
|
||||
var env = process.env.NODE_ENV || 'dev';
|
||||
return 'production' === env;
|
||||
}
|
||||
|
||||
function isDevelopment() {
|
||||
return (!(isProduction()));
|
||||
return (!(isProduction()));
|
||||
}
|
||||
|
||||
function valueWithDefault(val, defVal) {
|
||||
return (typeof val !== 'undefined' ? val : defVal);
|
||||
return (typeof val !== 'undefined' ? val : defVal);
|
||||
}
|
||||
|
||||
function resolvePath(path) {
|
||||
if(path.substr(0, 2) === '~/') {
|
||||
var mswCombined = process.env.HOMEDRIVE + process.env.HOMEPATH;
|
||||
path = (process.env.HOME || mswCombined || process.env.HOMEPATH || process.env.HOMEDIR || process.cwd()) + path.substr(1);
|
||||
}
|
||||
return paths.resolve(path);
|
||||
if(path.substr(0, 2) === '~/') {
|
||||
var mswCombined = process.env.HOMEDRIVE + process.env.HOMEPATH;
|
||||
path = (process.env.HOME || mswCombined || process.env.HOMEPATH || process.env.HOMEDIR || process.cwd()) + path.substr(1);
|
||||
}
|
||||
return paths.resolve(path);
|
||||
}
|
||||
|
||||
function getCleanEnigmaVersion() {
|
||||
return packageJson.version
|
||||
.replace(/-/g, '.')
|
||||
.replace(/alpha/,'a')
|
||||
.replace(/beta/,'b')
|
||||
;
|
||||
return packageJson.version
|
||||
.replace(/-/g, '.')
|
||||
.replace(/alpha/,'a')
|
||||
.replace(/beta/,'b')
|
||||
;
|
||||
}
|
||||
|
||||
// See also ftn_util.js getTearLine() & getProductIdentifier()
|
||||
function getEnigmaUserAgent() {
|
||||
// can't have 1/2 or ½ in User-Agent according to RFC 1945 :(
|
||||
const version = getCleanEnigmaVersion();
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
// can't have 1/2 or ½ in User-Agent according to RFC 1945 :(
|
||||
const version = getCleanEnigmaVersion();
|
||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||
|
||||
return `ENiGMA-BBS/${version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
return `ENiGMA-BBS/${version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
||||
}
|
|
@ -7,28 +7,28 @@ const { get } = require('lodash');
|
|||
|
||||
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
||||
|
||||
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
|
||||
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
|
||||
if(!messageAreaTag) {
|
||||
return; // nothing to do!
|
||||
}
|
||||
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
|
||||
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
|
||||
if(!messageAreaTag) {
|
||||
return; // nothing to do!
|
||||
}
|
||||
|
||||
if(recordPrevious) {
|
||||
this.prevMessageConfAndArea = {
|
||||
confTag : this.client.user.properties.message_conf_tag,
|
||||
areaTag : this.client.user.properties.message_area_tag,
|
||||
};
|
||||
}
|
||||
if(recordPrevious) {
|
||||
this.prevMessageConfAndArea = {
|
||||
confTag : this.client.user.properties.message_conf_tag,
|
||||
areaTag : this.client.user.properties.message_area_tag,
|
||||
};
|
||||
}
|
||||
|
||||
if(!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) {
|
||||
this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch');
|
||||
}
|
||||
}
|
||||
if(!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) {
|
||||
this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch');
|
||||
}
|
||||
}
|
||||
|
||||
tempMessageConfAndAreaRestore() {
|
||||
if(this.prevMessageConfAndArea) {
|
||||
this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag;
|
||||
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
||||
}
|
||||
}
|
||||
tempMessageConfAndAreaRestore() {
|
||||
if(this.prevMessageConfAndArea) {
|
||||
this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag;
|
||||
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -18,93 +18,93 @@ exports.loadModulesForCategory = loadModulesForCategory;
|
|||
exports.getModulePaths = getModulePaths;
|
||||
|
||||
function loadModuleEx(options, cb) {
|
||||
assert(_.isObject(options));
|
||||
assert(_.isString(options.name));
|
||||
assert(_.isString(options.path));
|
||||
assert(_.isObject(options));
|
||||
assert(_.isString(options.name));
|
||||
assert(_.isString(options.path));
|
||||
|
||||
const modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null;
|
||||
const modConfig = _.isObject(Config[options.category]) ? Config[options.category][options.name] : null;
|
||||
|
||||
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
||||
const err = new Error(`Module "${options.name}" is disabled`);
|
||||
err.code = 'EENIGMODDISABLED';
|
||||
return cb(err);
|
||||
}
|
||||
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
||||
const err = new Error(`Module "${options.name}" is disabled`);
|
||||
err.code = 'EENIGMODDISABLED';
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
//
|
||||
// Modules are allowed to live in /path/to/<moduleName>/<moduleName>.js or
|
||||
// simply in /path/to/<moduleName>.js. This allows for more advanced modules
|
||||
// to have their own containing folder, package.json & dependencies, etc.
|
||||
//
|
||||
let mod;
|
||||
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
|
||||
try {
|
||||
mod = require(modPath);
|
||||
} catch(e) {
|
||||
if('MODULE_NOT_FOUND' === e.code) {
|
||||
modPath = paths.join(options.path, options.name, `${options.name}.js`);
|
||||
try {
|
||||
mod = require(modPath);
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
} else {
|
||||
return cb(e);
|
||||
}
|
||||
}
|
||||
//
|
||||
// Modules are allowed to live in /path/to/<moduleName>/<moduleName>.js or
|
||||
// simply in /path/to/<moduleName>.js. This allows for more advanced modules
|
||||
// to have their own containing folder, package.json & dependencies, etc.
|
||||
//
|
||||
let mod;
|
||||
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
|
||||
try {
|
||||
mod = require(modPath);
|
||||
} catch(e) {
|
||||
if('MODULE_NOT_FOUND' === e.code) {
|
||||
modPath = paths.join(options.path, options.name, `${options.name}.js`);
|
||||
try {
|
||||
mod = require(modPath);
|
||||
} catch(e) {
|
||||
return cb(e);
|
||||
}
|
||||
} else {
|
||||
return cb(e);
|
||||
}
|
||||
}
|
||||
|
||||
if(!_.isObject(mod.moduleInfo)) {
|
||||
return cb(new Error('Module is missing "moduleInfo" section'));
|
||||
}
|
||||
if(!_.isObject(mod.moduleInfo)) {
|
||||
return cb(new Error('Module is missing "moduleInfo" section'));
|
||||
}
|
||||
|
||||
if(!_.isFunction(mod.getModule)) {
|
||||
return cb(new Error('Invalid or missing "getModule" method for module!'));
|
||||
}
|
||||
if(!_.isFunction(mod.getModule)) {
|
||||
return cb(new Error('Invalid or missing "getModule" method for module!'));
|
||||
}
|
||||
|
||||
return cb(null, mod);
|
||||
return cb(null, mod);
|
||||
}
|
||||
|
||||
function loadModule(name, category, cb) {
|
||||
const path = Config().paths[category];
|
||||
const path = Config().paths[category];
|
||||
|
||||
if(!_.isString(path)) {
|
||||
return cb(new Error(`Not sure where to look for "${name}" of category "${category}"`));
|
||||
}
|
||||
if(!_.isString(path)) {
|
||||
return cb(new Error(`Not sure where to look for "${name}" of category "${category}"`));
|
||||
}
|
||||
|
||||
loadModuleEx( { name : name, path : path, category : category }, function loaded(err, mod) {
|
||||
return cb(err, mod);
|
||||
});
|
||||
loadModuleEx( { name : name, path : path, category : category }, function loaded(err, mod) {
|
||||
return cb(err, mod);
|
||||
});
|
||||
}
|
||||
|
||||
function loadModulesForCategory(category, iterator, complete) {
|
||||
|
||||
fs.readdir(Config().paths[category], (err, files) => {
|
||||
if(err) {
|
||||
return iterator(err);
|
||||
}
|
||||
fs.readdir(Config().paths[category], (err, files) => {
|
||||
if(err) {
|
||||
return iterator(err);
|
||||
}
|
||||
|
||||
const jsModules = files.filter(file => {
|
||||
return '.js' === paths.extname(file);
|
||||
});
|
||||
const jsModules = files.filter(file => {
|
||||
return '.js' === paths.extname(file);
|
||||
});
|
||||
|
||||
async.each(jsModules, (file, next) => {
|
||||
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
|
||||
iterator(err, mod);
|
||||
return next();
|
||||
});
|
||||
}, err => {
|
||||
if(complete) {
|
||||
return complete(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
async.each(jsModules, (file, next) => {
|
||||
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
|
||||
iterator(err, mod);
|
||||
return next();
|
||||
});
|
||||
}, err => {
|
||||
if(complete) {
|
||||
return complete(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getModulePaths() {
|
||||
const config = Config();
|
||||
return [
|
||||
config.paths.mods,
|
||||
config.paths.loginServers,
|
||||
config.paths.contentServers,
|
||||
config.paths.scannerTossers,
|
||||
];
|
||||
const config = Config();
|
||||
return [
|
||||
config.paths.mods,
|
||||
config.paths.loginServers,
|
||||
config.paths.contentServers,
|
||||
config.paths.scannerTossers,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -15,9 +15,9 @@ const async = require('async');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message Area List',
|
||||
desc : 'Module for listing / choosing message areas',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message Area List',
|
||||
desc : 'Module for listing / choosing message areas',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -35,73 +35,73 @@ exports.moduleInfo = {
|
|||
*/
|
||||
|
||||
const MciViewIds = {
|
||||
AreaList : 1,
|
||||
SelAreaInfo1 : 2,
|
||||
SelAreaInfo2 : 3,
|
||||
AreaList : 1,
|
||||
SelAreaInfo1 : 2,
|
||||
SelAreaInfo2 : 3,
|
||||
};
|
||||
|
||||
exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
|
||||
this.client.user.properties.message_conf_tag,
|
||||
{ client : this.client }
|
||||
);
|
||||
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
|
||||
this.client.user.properties.message_conf_tag,
|
||||
{ client : this.client }
|
||||
);
|
||||
|
||||
const self = this;
|
||||
this.menuMethods = {
|
||||
changeArea : function(formData, extraArgs, cb) {
|
||||
if(1 === formData.submitId) {
|
||||
let area = self.messageAreas[formData.value.area];
|
||||
const areaTag = area.areaTag;
|
||||
area = area.area; // what we want is actually embedded
|
||||
const self = this;
|
||||
this.menuMethods = {
|
||||
changeArea : function(formData, extraArgs, cb) {
|
||||
if(1 === formData.submitId) {
|
||||
let area = self.messageAreas[formData.value.area];
|
||||
const areaTag = area.areaTag;
|
||||
area = area.area; // what we want is actually embedded
|
||||
|
||||
messageArea.changeMessageArea(self.client, areaTag, err => {
|
||||
if(err) {
|
||||
self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`);
|
||||
messageArea.changeMessageArea(self.client, areaTag, err => {
|
||||
if(err) {
|
||||
self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`);
|
||||
|
||||
self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
if(_.isString(area.art)) {
|
||||
const dispOptions = {
|
||||
client : self.client,
|
||||
name : area.art,
|
||||
};
|
||||
self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
if(_.isString(area.art)) {
|
||||
const dispOptions = {
|
||||
client : self.client,
|
||||
name : area.art,
|
||||
};
|
||||
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
|
||||
displayThemeArt(dispOptions, () => {
|
||||
// pause by default, unless explicitly told not to
|
||||
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
||||
return self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
self.pausePrompt( () => {
|
||||
return self.prevMenu(cb);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
displayThemeArt(dispOptions, () => {
|
||||
// pause by default, unless explicitly told not to
|
||||
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
||||
return self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
self.pausePrompt( () => {
|
||||
return self.prevMenu(cb);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
prevMenuOnTimeout(timeout, cb) {
|
||||
setTimeout( () => {
|
||||
return this.prevMenu(cb);
|
||||
}, timeout);
|
||||
}
|
||||
prevMenuOnTimeout(timeout, cb) {
|
||||
setTimeout( () => {
|
||||
return this.prevMenu(cb);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
|
||||
updateGeneralAreaInfoViews(areaIndex) {
|
||||
/*
|
||||
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
|
||||
updateGeneralAreaInfoViews(areaIndex) {
|
||||
/*
|
||||
const areaInfo = self.messageAreas[areaIndex];
|
||||
|
||||
[ MciViewIds.SelAreaInfo1, MciViewIds.SelAreaInfo2 ].forEach(mciId => {
|
||||
|
@ -111,71 +111,71 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
|||
}
|
||||
});
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||
const self = this;
|
||||
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
formId : 0,
|
||||
};
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
formId : 0,
|
||||
};
|
||||
|
||||
vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function populateAreaListView(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||
vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
function populateAreaListView(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||
|
||||
const areaListView = vc.getView(MciViewIds.AreaList);
|
||||
if(!areaListView) {
|
||||
return callback(Errors.MissingMci('A MenuView compatible MCI code is required'));
|
||||
}
|
||||
let i = 1;
|
||||
areaListView.setItems(_.map(self.messageAreas, v => {
|
||||
return stringFormat(listFormat, {
|
||||
index : i++,
|
||||
areaTag : v.area.areaTag,
|
||||
name : v.area.name,
|
||||
desc : v.area.desc,
|
||||
});
|
||||
}));
|
||||
const areaListView = vc.getView(MciViewIds.AreaList);
|
||||
if(!areaListView) {
|
||||
return callback(Errors.MissingMci('A MenuView compatible MCI code is required'));
|
||||
}
|
||||
let i = 1;
|
||||
areaListView.setItems(_.map(self.messageAreas, v => {
|
||||
return stringFormat(listFormat, {
|
||||
index : i++,
|
||||
areaTag : v.area.areaTag,
|
||||
name : v.area.name,
|
||||
desc : v.area.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
i = 1;
|
||||
areaListView.setFocusItems(_.map(self.messageAreas, v => {
|
||||
return stringFormat(focusListFormat, {
|
||||
index : i++,
|
||||
areaTag : v.area.areaTag,
|
||||
name : v.area.name,
|
||||
desc : v.area.desc,
|
||||
});
|
||||
}));
|
||||
i = 1;
|
||||
areaListView.setFocusItems(_.map(self.messageAreas, v => {
|
||||
return stringFormat(focusListFormat, {
|
||||
index : i++,
|
||||
areaTag : v.area.areaTag,
|
||||
name : v.area.name,
|
||||
desc : v.area.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
areaListView.on('index update', areaIndex => {
|
||||
self.updateGeneralAreaInfoViews(areaIndex);
|
||||
});
|
||||
areaListView.on('index update', areaIndex => {
|
||||
self.updateGeneralAreaInfoViews(areaIndex);
|
||||
});
|
||||
|
||||
areaListView.redraw();
|
||||
areaListView.redraw();
|
||||
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -8,60 +8,60 @@ const _ = require('lodash');
|
|||
const async = require('async');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message Area Post',
|
||||
desc : 'Module for posting a new message to an area',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message Area Post',
|
||||
desc : 'Module for posting a new message to an area',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
// we're posting, so always start with 'edit' mode
|
||||
this.editorMode = 'edit';
|
||||
// we're posting, so always start with 'edit' mode
|
||||
this.editorMode = 'edit';
|
||||
|
||||
this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) {
|
||||
this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) {
|
||||
|
||||
var msg;
|
||||
async.series(
|
||||
[
|
||||
function getMessageObject(callback) {
|
||||
self.getMessage(function gotMsg(err, msgObj) {
|
||||
msg = msgObj;
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function saveMessage(callback) {
|
||||
return persistMessage(msg, callback);
|
||||
},
|
||||
function updateStats(callback) {
|
||||
self.updateUserStats(callback);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
// :TODO:... sooooo now what?
|
||||
} else {
|
||||
// note: not logging 'from' here as it's part of client.log.xxxx()
|
||||
self.client.log.info(
|
||||
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
|
||||
'Message persisted'
|
||||
);
|
||||
}
|
||||
var msg;
|
||||
async.series(
|
||||
[
|
||||
function getMessageObject(callback) {
|
||||
self.getMessage(function gotMsg(err, msgObj) {
|
||||
msg = msgObj;
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function saveMessage(callback) {
|
||||
return persistMessage(msg, callback);
|
||||
},
|
||||
function updateStats(callback) {
|
||||
self.updateUserStats(callback);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
if(err) {
|
||||
// :TODO:... sooooo now what?
|
||||
} else {
|
||||
// note: not logging 'from' here as it's part of client.log.xxxx()
|
||||
self.client.log.info(
|
||||
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
|
||||
'Message persisted'
|
||||
);
|
||||
}
|
||||
|
||||
return self.nextMenu(cb);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
return self.nextMenu(cb);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
enter() {
|
||||
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
|
||||
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
}
|
||||
enter() {
|
||||
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
|
||||
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
}
|
||||
|
||||
super.enter();
|
||||
}
|
||||
super.enter();
|
||||
}
|
||||
};
|
|
@ -6,13 +6,13 @@ var FullScreenEditorModule = require('./fse.js').FullScreenEditorModule;
|
|||
exports.getModule = AreaReplyFSEModule;
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message Area Reply',
|
||||
desc : 'Module for replying to an area message',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message Area Reply',
|
||||
desc : 'Module for replying to an area message',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
function AreaReplyFSEModule(options) {
|
||||
FullScreenEditorModule.call(this, options);
|
||||
FullScreenEditorModule.call(this, options);
|
||||
}
|
||||
|
||||
require('util').inherits(AreaReplyFSEModule, FullScreenEditorModule);
|
||||
|
|
|
@ -9,137 +9,137 @@ const Message = require('./message.js');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message Area View',
|
||||
desc : 'Module for viewing an area message',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message Area View',
|
||||
desc : 'Module for viewing an area message',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.editorType = 'area';
|
||||
this.editorMode = 'view';
|
||||
this.editorType = 'area';
|
||||
this.editorMode = 'view';
|
||||
|
||||
if(_.isObject(options.extraArgs)) {
|
||||
this.messageList = options.extraArgs.messageList;
|
||||
this.messageIndex = options.extraArgs.messageIndex;
|
||||
this.lastMessageNextExit = options.extraArgs.lastMessageNextExit;
|
||||
}
|
||||
if(_.isObject(options.extraArgs)) {
|
||||
this.messageList = options.extraArgs.messageList;
|
||||
this.messageIndex = options.extraArgs.messageIndex;
|
||||
this.lastMessageNextExit = options.extraArgs.lastMessageNextExit;
|
||||
}
|
||||
|
||||
this.messageList = this.messageList || [];
|
||||
this.messageIndex = this.messageIndex || 0;
|
||||
this.messageTotal = this.messageList.length;
|
||||
this.messageList = this.messageList || [];
|
||||
this.messageIndex = this.messageIndex || 0;
|
||||
this.messageTotal = this.messageList.length;
|
||||
|
||||
if(this.messageList.length > 0) {
|
||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||
}
|
||||
if(this.messageList.length > 0) {
|
||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
// assign *additional* menuMethods
|
||||
Object.assign(this.menuMethods, {
|
||||
nextMessage : (formData, extraArgs, cb) => {
|
||||
if(self.messageIndex + 1 < self.messageList.length) {
|
||||
self.messageIndex++;
|
||||
// assign *additional* menuMethods
|
||||
Object.assign(this.menuMethods, {
|
||||
nextMessage : (formData, extraArgs, cb) => {
|
||||
if(self.messageIndex + 1 < self.messageList.length) {
|
||||
self.messageIndex++;
|
||||
|
||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with
|
||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with
|
||||
|
||||
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
|
||||
}
|
||||
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
|
||||
}
|
||||
|
||||
// auto-exit if no more to go?
|
||||
if(self.lastMessageNextExit) {
|
||||
self.lastMessageReached = true;
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
// auto-exit if no more to go?
|
||||
if(self.lastMessageNextExit) {
|
||||
self.lastMessageReached = true;
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
prevMessage : (formData, extraArgs, cb) => {
|
||||
if(self.messageIndex > 0) {
|
||||
self.messageIndex--;
|
||||
prevMessage : (formData, extraArgs, cb) => {
|
||||
if(self.messageIndex > 0) {
|
||||
self.messageIndex--;
|
||||
|
||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with
|
||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag, false); // false=don't record prev; we want what we entered the module with
|
||||
|
||||
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
|
||||
}
|
||||
return self.loadMessageByUuid(self.messageList[self.messageIndex].messageUuid, cb);
|
||||
}
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
movementKeyPressed : (formData, extraArgs, cb) => {
|
||||
const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic #
|
||||
movementKeyPressed : (formData, extraArgs, cb) => {
|
||||
const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic #
|
||||
|
||||
// :TODO: Create methods for up/down vs using keyPressXXXXX
|
||||
switch(formData.key.name) {
|
||||
case 'down arrow' : bodyView.scrollDocumentUp(); break;
|
||||
case 'up arrow' : bodyView.scrollDocumentDown(); break;
|
||||
case 'page up' : bodyView.keyPressPageUp(); break;
|
||||
case 'page down' : bodyView.keyPressPageDown(); break;
|
||||
}
|
||||
// :TODO: Create methods for up/down vs using keyPressXXXXX
|
||||
switch(formData.key.name) {
|
||||
case 'down arrow' : bodyView.scrollDocumentUp(); break;
|
||||
case 'up arrow' : bodyView.scrollDocumentDown(); break;
|
||||
case 'page up' : bodyView.keyPressPageUp(); break;
|
||||
case 'page down' : bodyView.keyPressPageDown(); break;
|
||||
}
|
||||
|
||||
// :TODO: need to stop down/page down if doing so would push the last
|
||||
// visible page off the screen at all .... this should be handled by MLTEV though...
|
||||
// :TODO: need to stop down/page down if doing so would push the last
|
||||
// visible page off the screen at all .... this should be handled by MLTEV though...
|
||||
|
||||
return cb(null);
|
||||
},
|
||||
return cb(null);
|
||||
},
|
||||
|
||||
replyMessage : (formData, extraArgs, cb) => {
|
||||
if(_.isString(extraArgs.menu)) {
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
messageAreaTag : self.messageAreaTag,
|
||||
replyToMessage : self.message,
|
||||
}
|
||||
};
|
||||
replyMessage : (formData, extraArgs, cb) => {
|
||||
if(_.isString(extraArgs.menu)) {
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
messageAreaTag : self.messageAreaTag,
|
||||
replyToMessage : self.message,
|
||||
}
|
||||
};
|
||||
|
||||
return self.gotoMenu(extraArgs.menu, modOpts, cb);
|
||||
}
|
||||
return self.gotoMenu(extraArgs.menu, modOpts, cb);
|
||||
}
|
||||
|
||||
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadMessageByUuid(uuid, cb) {
|
||||
const msg = new Message();
|
||||
msg.load( { uuid : uuid, user : this.client.user }, () => {
|
||||
this.setMessage(msg);
|
||||
loadMessageByUuid(uuid, cb) {
|
||||
const msg = new Message();
|
||||
msg.load( { uuid : uuid, user : this.client.user }, () => {
|
||||
this.setMessage(msg);
|
||||
|
||||
if(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
if(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
finishedLoading() {
|
||||
this.loadMessageByUuid(this.messageList[this.messageIndex].messageUuid);
|
||||
}
|
||||
finishedLoading() {
|
||||
this.loadMessageByUuid(this.messageList[this.messageIndex].messageUuid);
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
return {
|
||||
messageList : this.messageList,
|
||||
messageIndex : this.messageIndex,
|
||||
messageTotal : this.messageList.length,
|
||||
};
|
||||
}
|
||||
getSaveState() {
|
||||
return {
|
||||
messageList : this.messageList,
|
||||
messageIndex : this.messageIndex,
|
||||
messageTotal : this.messageList.length,
|
||||
};
|
||||
}
|
||||
|
||||
restoreSavedState(savedState) {
|
||||
this.messageList = savedState.messageList;
|
||||
this.messageIndex = savedState.messageIndex;
|
||||
this.messageTotal = savedState.messageTotal;
|
||||
}
|
||||
restoreSavedState(savedState) {
|
||||
this.messageList = savedState.messageList;
|
||||
this.messageIndex = savedState.messageIndex;
|
||||
this.messageTotal = savedState.messageTotal;
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
return {
|
||||
messageIndex : this.messageIndex,
|
||||
lastMessageReached : this.lastMessageReached,
|
||||
};
|
||||
}
|
||||
getMenuResult() {
|
||||
return {
|
||||
messageIndex : this.messageIndex,
|
||||
lastMessageReached : this.lastMessageReached,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,135 +14,135 @@ const async = require('async');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message Conference List',
|
||||
desc : 'Module for listing / choosing message conferences',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message Conference List',
|
||||
desc : 'Module for listing / choosing message conferences',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
ConfList : 1,
|
||||
ConfList : 1,
|
||||
|
||||
// :TODO:
|
||||
// # areas in conf .... see Obv/2, iNiQ, ...
|
||||
//
|
||||
// :TODO:
|
||||
// # areas in conf .... see Obv/2, iNiQ, ...
|
||||
//
|
||||
};
|
||||
|
||||
exports.getModule = class MessageConfListModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client);
|
||||
const self = this;
|
||||
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client);
|
||||
const self = this;
|
||||
|
||||
this.menuMethods = {
|
||||
changeConference : function(formData, extraArgs, cb) {
|
||||
if(1 === formData.submitId) {
|
||||
let conf = self.messageConfs[formData.value.conf];
|
||||
const confTag = conf.confTag;
|
||||
conf = conf.conf; // what we want is embedded
|
||||
this.menuMethods = {
|
||||
changeConference : function(formData, extraArgs, cb) {
|
||||
if(1 === formData.submitId) {
|
||||
let conf = self.messageConfs[formData.value.conf];
|
||||
const confTag = conf.confTag;
|
||||
conf = conf.conf; // what we want is embedded
|
||||
|
||||
messageArea.changeMessageConference(self.client, confTag, err => {
|
||||
if(err) {
|
||||
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
|
||||
messageArea.changeMessageConference(self.client, confTag, err => {
|
||||
if(err) {
|
||||
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
|
||||
|
||||
setTimeout( () => {
|
||||
return self.prevMenu(cb);
|
||||
}, 1000);
|
||||
} else {
|
||||
if(_.isString(conf.art)) {
|
||||
const dispOptions = {
|
||||
client : self.client,
|
||||
name : conf.art,
|
||||
};
|
||||
setTimeout( () => {
|
||||
return self.prevMenu(cb);
|
||||
}, 1000);
|
||||
} else {
|
||||
if(_.isString(conf.art)) {
|
||||
const dispOptions = {
|
||||
client : self.client,
|
||||
name : conf.art,
|
||||
};
|
||||
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
|
||||
displayThemeArt(dispOptions, () => {
|
||||
// pause by default, unless explicitly told not to
|
||||
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
||||
return self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
self.pausePrompt( () => {
|
||||
return self.prevMenu(cb);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
displayThemeArt(dispOptions, () => {
|
||||
// pause by default, unless explicitly told not to
|
||||
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
||||
return self.prevMenuOnTimeout(1000, cb);
|
||||
} else {
|
||||
self.pausePrompt( () => {
|
||||
return self.prevMenu(cb);
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
prevMenuOnTimeout(timeout, cb) {
|
||||
setTimeout( () => {
|
||||
return this.prevMenu(cb);
|
||||
}, timeout);
|
||||
}
|
||||
prevMenuOnTimeout(timeout, cb) {
|
||||
setTimeout( () => {
|
||||
return this.prevMenu(cb);
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||
const self = this;
|
||||
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
let loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
formId : 0,
|
||||
};
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
let loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
formId : 0,
|
||||
};
|
||||
|
||||
vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function populateConfListView(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||
vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function populateConfListView(callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||
|
||||
const confListView = vc.getView(MciViewIds.ConfList);
|
||||
let i = 1;
|
||||
confListView.setItems(_.map(self.messageConfs, v => {
|
||||
return stringFormat(listFormat, {
|
||||
index : i++,
|
||||
confTag : v.conf.confTag,
|
||||
name : v.conf.name,
|
||||
desc : v.conf.desc,
|
||||
});
|
||||
}));
|
||||
const confListView = vc.getView(MciViewIds.ConfList);
|
||||
let i = 1;
|
||||
confListView.setItems(_.map(self.messageConfs, v => {
|
||||
return stringFormat(listFormat, {
|
||||
index : i++,
|
||||
confTag : v.conf.confTag,
|
||||
name : v.conf.name,
|
||||
desc : v.conf.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
i = 1;
|
||||
confListView.setFocusItems(_.map(self.messageConfs, v => {
|
||||
return stringFormat(focusListFormat, {
|
||||
index : i++,
|
||||
confTag : v.conf.confTag,
|
||||
name : v.conf.name,
|
||||
desc : v.conf.desc,
|
||||
});
|
||||
}));
|
||||
i = 1;
|
||||
confListView.setFocusItems(_.map(self.messageConfs, v => {
|
||||
return stringFormat(focusListFormat, {
|
||||
index : i++,
|
||||
confTag : v.conf.confTag,
|
||||
name : v.conf.name,
|
||||
desc : v.conf.desc,
|
||||
});
|
||||
}));
|
||||
|
||||
confListView.redraw();
|
||||
confListView.redraw();
|
||||
|
||||
callback(null);
|
||||
},
|
||||
function populateTextViews(callback) {
|
||||
// :TODO: populate other avail MCI, e.g. current conf name
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
callback(null);
|
||||
},
|
||||
function populateTextViews(callback) {
|
||||
// :TODO: populate other avail MCI, e.g. current conf name
|
||||
callback(null);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
376
core/msg_list.js
376
core/msg_list.js
|
@ -30,229 +30,229 @@ const moment = require('moment');
|
|||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Message List',
|
||||
desc : 'Module for listing/browsing available messages',
|
||||
author : 'NuSkooler',
|
||||
name : 'Message List',
|
||||
desc : 'Module for listing/browsing available messages',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
msgList : 1, // VM1
|
||||
msgInfo1 : 2, // TL2
|
||||
msgList : 1, // VM1
|
||||
msgInfo1 : 2, // TL2
|
||||
};
|
||||
|
||||
exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
// :TODO: consider this pattern in base MenuModule - clean up code all over
|
||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
||||
// :TODO: consider this pattern in base MenuModule - clean up code all over
|
||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
||||
|
||||
this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false);
|
||||
this.lastMessageReachedExit = _.get(options, 'lastMenuResult.lastMessageReached', false);
|
||||
|
||||
this.menuMethods = {
|
||||
selectMessage : (formData, extraArgs, cb) => {
|
||||
if(MciViewIds.msgList === formData.submitId) {
|
||||
this.initialFocusIndex = formData.value.message;
|
||||
this.menuMethods = {
|
||||
selectMessage : (formData, extraArgs, cb) => {
|
||||
if(MciViewIds.msgList === formData.submitId) {
|
||||
this.initialFocusIndex = formData.value.message;
|
||||
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
messageAreaTag : this.getSelectedAreaTag(formData.value.message),// this.config.messageAreaTag,
|
||||
messageList : this.config.messageList,
|
||||
messageIndex : formData.value.message,
|
||||
lastMessageNextExit : true,
|
||||
}
|
||||
};
|
||||
const modOpts = {
|
||||
extraArgs : {
|
||||
messageAreaTag : this.getSelectedAreaTag(formData.value.message),// this.config.messageAreaTag,
|
||||
messageList : this.config.messageList,
|
||||
messageIndex : formData.value.message,
|
||||
lastMessageNextExit : true,
|
||||
}
|
||||
};
|
||||
|
||||
if(_.isBoolean(this.config.noUpdateLastReadId)) {
|
||||
modOpts.extraArgs.noUpdateLastReadId = this.config.noUpdateLastReadId;
|
||||
}
|
||||
if(_.isBoolean(this.config.noUpdateLastReadId)) {
|
||||
modOpts.extraArgs.noUpdateLastReadId = this.config.noUpdateLastReadId;
|
||||
}
|
||||
|
||||
//
|
||||
// Provide a serializer so we don't dump *huge* bits of information to the log
|
||||
// due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
|
||||
//
|
||||
const self = this;
|
||||
modOpts.extraArgs.toJSON = function() {
|
||||
const logMsgList = (self.config.messageList.length <= 4) ?
|
||||
self.config.messageList :
|
||||
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2));
|
||||
//
|
||||
// Provide a serializer so we don't dump *huge* bits of information to the log
|
||||
// due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
|
||||
//
|
||||
const self = this;
|
||||
modOpts.extraArgs.toJSON = function() {
|
||||
const logMsgList = (self.config.messageList.length <= 4) ?
|
||||
self.config.messageList :
|
||||
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2));
|
||||
|
||||
return {
|
||||
// note |this| is scope of toJSON()!
|
||||
messageAreaTag : this.messageAreaTag,
|
||||
apprevMessageList : logMsgList,
|
||||
messageCount : this.messageList.length,
|
||||
messageIndex : this.messageIndex,
|
||||
};
|
||||
};
|
||||
return {
|
||||
// note |this| is scope of toJSON()!
|
||||
messageAreaTag : this.messageAreaTag,
|
||||
apprevMessageList : logMsgList,
|
||||
messageCount : this.messageList.length,
|
||||
messageIndex : this.messageIndex,
|
||||
};
|
||||
};
|
||||
|
||||
return this.gotoMenu(this.config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
},
|
||||
return this.gotoMenu(this.config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
|
||||
} else {
|
||||
return cb(null);
|
||||
}
|
||||
},
|
||||
|
||||
fullExit : (formData, extraArgs, cb) => {
|
||||
this.menuResult = { fullExit : true };
|
||||
return this.prevMenu(cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
fullExit : (formData, extraArgs, cb) => {
|
||||
this.menuResult = { fullExit : true };
|
||||
return this.prevMenu(cb);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
getSelectedAreaTag(listIndex) {
|
||||
return this.config.messageList[listIndex].areaTag || this.config.messageAreaTag;
|
||||
}
|
||||
getSelectedAreaTag(listIndex) {
|
||||
return this.config.messageList[listIndex].areaTag || this.config.messageAreaTag;
|
||||
}
|
||||
|
||||
enter() {
|
||||
if(this.lastMessageReachedExit) {
|
||||
return this.prevMenu();
|
||||
}
|
||||
enter() {
|
||||
if(this.lastMessageReachedExit) {
|
||||
return this.prevMenu();
|
||||
}
|
||||
|
||||
super.enter();
|
||||
super.enter();
|
||||
|
||||
//
|
||||
// Config can specify |messageAreaTag| else it comes from
|
||||
// the user's current area. If |messageList| is supplied,
|
||||
// each item is expected to contain |areaTag|, so we use that
|
||||
// instead in those cases.
|
||||
//
|
||||
if(!Array.isArray(this.config.messageList)) {
|
||||
if(this.config.messageAreaTag) {
|
||||
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
|
||||
} else {
|
||||
this.config.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
//
|
||||
// Config can specify |messageAreaTag| else it comes from
|
||||
// the user's current area. If |messageList| is supplied,
|
||||
// each item is expected to contain |areaTag|, so we use that
|
||||
// instead in those cases.
|
||||
//
|
||||
if(!Array.isArray(this.config.messageList)) {
|
||||
if(this.config.messageAreaTag) {
|
||||
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
|
||||
} else {
|
||||
this.config.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leave() {
|
||||
this.tempMessageConfAndAreaRestore();
|
||||
super.leave();
|
||||
}
|
||||
leave() {
|
||||
this.tempMessageConfAndAreaRestore();
|
||||
super.leave();
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
let configProvidedMessageList = false;
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
let configProvidedMessageList = false;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu
|
||||
};
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function fetchMessagesInArea(callback) {
|
||||
//
|
||||
// Config can supply messages else we'll need to populate the list now
|
||||
//
|
||||
if(_.isArray(self.config.messageList)) {
|
||||
configProvidedMessageList = true;
|
||||
return callback(0 === self.config.messageList.length ? new Error('No messages in area') : null);
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function fetchMessagesInArea(callback) {
|
||||
//
|
||||
// Config can supply messages else we'll need to populate the list now
|
||||
//
|
||||
if(_.isArray(self.config.messageList)) {
|
||||
configProvidedMessageList = true;
|
||||
return callback(0 === self.config.messageList.length ? new Error('No messages in area') : null);
|
||||
}
|
||||
|
||||
messageArea.getMessageListForArea(self.client, self.config.messageAreaTag, function msgs(err, msgList) {
|
||||
if(!msgList || 0 === msgList.length) {
|
||||
return callback(new Error('No messages in area'));
|
||||
}
|
||||
messageArea.getMessageListForArea(self.client, self.config.messageAreaTag, function msgs(err, msgList) {
|
||||
if(!msgList || 0 === msgList.length) {
|
||||
return callback(new Error('No messages in area'));
|
||||
}
|
||||
|
||||
self.config.messageList = msgList;
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function getLastReadMesageId(callback) {
|
||||
// messageList entries can contain |isNew| if they want to be considered new
|
||||
if(configProvidedMessageList) {
|
||||
self.lastReadId = 0;
|
||||
return callback(null);
|
||||
}
|
||||
self.config.messageList = msgList;
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function getLastReadMesageId(callback) {
|
||||
// messageList entries can contain |isNew| if they want to be considered new
|
||||
if(configProvidedMessageList) {
|
||||
self.lastReadId = 0;
|
||||
return callback(null);
|
||||
}
|
||||
|
||||
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.config.messageAreaTag, function lastRead(err, lastReadId) {
|
||||
self.lastReadId = lastReadId || 0;
|
||||
return callback(null); // ignore any errors, e.g. missing value
|
||||
});
|
||||
},
|
||||
function updateMessageListObjects(callback) {
|
||||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || self.client.currentTheme.helpers.getDateTimeFormat();
|
||||
const newIndicator = self.menuConfig.config.newIndicator || '*';
|
||||
const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues
|
||||
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.config.messageAreaTag, function lastRead(err, lastReadId) {
|
||||
self.lastReadId = lastReadId || 0;
|
||||
return callback(null); // ignore any errors, e.g. missing value
|
||||
});
|
||||
},
|
||||
function updateMessageListObjects(callback) {
|
||||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || self.client.currentTheme.helpers.getDateTimeFormat();
|
||||
const newIndicator = self.menuConfig.config.newIndicator || '*';
|
||||
const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues
|
||||
|
||||
let msgNum = 1;
|
||||
self.config.messageList.forEach( (listItem, index) => {
|
||||
listItem.msgNum = msgNum++;
|
||||
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
|
||||
const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId;
|
||||
listItem.newIndicator = isNew ? newIndicator : regIndicator;
|
||||
let msgNum = 1;
|
||||
self.config.messageList.forEach( (listItem, index) => {
|
||||
listItem.msgNum = msgNum++;
|
||||
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
|
||||
const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId;
|
||||
listItem.newIndicator = isNew ? newIndicator : regIndicator;
|
||||
|
||||
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
|
||||
self.initialFocusIndex = index;
|
||||
}
|
||||
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
|
||||
self.initialFocusIndex = index;
|
||||
}
|
||||
|
||||
listItem.text = `${listItem.msgNum} - ${listItem.subject} from ${listItem.fromUserName}`; // default text
|
||||
});
|
||||
return callback(null);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const msgListView = vc.getView(MciViewIds.msgList);
|
||||
// :TODO: replace with standard custom info MCI - msgNumSelected, msgNumTotal, areaName, areaDesc, confName, confDesc, ...
|
||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
listItem.text = `${listItem.msgNum} - ${listItem.subject} from ${listItem.fromUserName}`; // default text
|
||||
});
|
||||
return callback(null);
|
||||
},
|
||||
function populateList(callback) {
|
||||
const msgListView = vc.getView(MciViewIds.msgList);
|
||||
// :TODO: replace with standard custom info MCI - msgNumSelected, msgNumTotal, areaName, areaDesc, confName, confDesc, ...
|
||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
|
||||
msgListView.setItems(self.config.messageList);
|
||||
msgListView.setItems(self.config.messageList);
|
||||
|
||||
msgListView.on('index update', idx => {
|
||||
self.setViewText(
|
||||
'allViews',
|
||||
MciViewIds.msgInfo1,
|
||||
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.config.messageList.length } ));
|
||||
});
|
||||
msgListView.on('index update', idx => {
|
||||
self.setViewText(
|
||||
'allViews',
|
||||
MciViewIds.msgInfo1,
|
||||
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.config.messageList.length } ));
|
||||
});
|
||||
|
||||
if(self.initialFocusIndex > 0) {
|
||||
// note: causes redraw()
|
||||
msgListView.setFocusItemIndex(self.initialFocusIndex);
|
||||
} else {
|
||||
msgListView.redraw();
|
||||
}
|
||||
if(self.initialFocusIndex > 0) {
|
||||
// note: causes redraw()
|
||||
msgListView.setFocusItemIndex(self.initialFocusIndex);
|
||||
} else {
|
||||
msgListView.redraw();
|
||||
}
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function drawOtherViews(callback) {
|
||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
self.setViewText(
|
||||
'allViews',
|
||||
MciViewIds.msgInfo1,
|
||||
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.config.messageList.length } ));
|
||||
return callback(null);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.message }, 'Error loading message list');
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function drawOtherViews(callback) {
|
||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||
self.setViewText(
|
||||
'allViews',
|
||||
MciViewIds.msgInfo1,
|
||||
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.config.messageList.length } ));
|
||||
return callback(null);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.message }, 'Error loading message list');
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
return { initialFocusIndex : this.initialFocusIndex };
|
||||
}
|
||||
getSaveState() {
|
||||
return { initialFocusIndex : this.initialFocusIndex };
|
||||
}
|
||||
|
||||
restoreSavedState(savedState) {
|
||||
if(savedState) {
|
||||
this.initialFocusIndex = savedState.initialFocusIndex;
|
||||
}
|
||||
}
|
||||
restoreSavedState(savedState) {
|
||||
if(savedState) {
|
||||
this.initialFocusIndex = savedState.initialFocusIndex;
|
||||
}
|
||||
}
|
||||
|
||||
getMenuResult() {
|
||||
return this.menuResult;
|
||||
}
|
||||
getMenuResult() {
|
||||
return this.menuResult;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,53 +14,53 @@ exports.recordMessage = recordMessage;
|
|||
let msgNetworkModules = [];
|
||||
|
||||
function startup(cb) {
|
||||
async.series(
|
||||
[
|
||||
function loadModules(callback) {
|
||||
loadModulesForCategory('scannerTossers', (err, module) => {
|
||||
if(!err) {
|
||||
const modInst = new module.getModule();
|
||||
async.series(
|
||||
[
|
||||
function loadModules(callback) {
|
||||
loadModulesForCategory('scannerTossers', (err, module) => {
|
||||
if(!err) {
|
||||
const modInst = new module.getModule();
|
||||
|
||||
modInst.startup(err => {
|
||||
if(!err) {
|
||||
msgNetworkModules.push(modInst);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, err => {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
cb
|
||||
);
|
||||
modInst.startup(err => {
|
||||
if(!err) {
|
||||
msgNetworkModules.push(modInst);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, err => {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
cb
|
||||
);
|
||||
}
|
||||
|
||||
function shutdown(cb) {
|
||||
async.each(
|
||||
msgNetworkModules,
|
||||
(msgNetModule, next) => {
|
||||
msgNetModule.shutdown( () => {
|
||||
return next();
|
||||
});
|
||||
},
|
||||
() => {
|
||||
msgNetworkModules = [];
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
async.each(
|
||||
msgNetworkModules,
|
||||
(msgNetModule, next) => {
|
||||
msgNetModule.shutdown( () => {
|
||||
return next();
|
||||
});
|
||||
},
|
||||
() => {
|
||||
msgNetworkModules = [];
|
||||
return cb(null);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function recordMessage(message, cb) {
|
||||
//
|
||||
// Give all message network modules (scanner/tossers)
|
||||
// a chance to do something with |message|. Any or all can
|
||||
// choose to ignore it.
|
||||
//
|
||||
async.each(msgNetworkModules, (modInst, next) => {
|
||||
modInst.record(message);
|
||||
next();
|
||||
}, err => {
|
||||
cb(err);
|
||||
});
|
||||
//
|
||||
// Give all message network modules (scanner/tossers)
|
||||
// a chance to do something with |message|. Any or all can
|
||||
// choose to ignore it.
|
||||
//
|
||||
async.each(msgNetworkModules, (modInst, next) => {
|
||||
modInst.record(message);
|
||||
next();
|
||||
}, err => {
|
||||
cb(err);
|
||||
});
|
||||
}
|
|
@ -7,17 +7,17 @@ var PluginModule = require('./plugin_module.js').PluginModule;
|
|||
exports.MessageScanTossModule = MessageScanTossModule;
|
||||
|
||||
function MessageScanTossModule() {
|
||||
PluginModule.call(this);
|
||||
PluginModule.call(this);
|
||||
}
|
||||
|
||||
require('util').inherits(MessageScanTossModule, PluginModule);
|
||||
|
||||
MessageScanTossModule.prototype.startup = function(cb) {
|
||||
return cb(null);
|
||||
return cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.shutdown = function(cb) {
|
||||
return cb(null);
|
||||
return cb(null);
|
||||
};
|
||||
|
||||
MessageScanTossModule.prototype.record = function(/*message*/) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
396
core/new_scan.js
396
core/new_scan.js
|
@ -16,9 +16,9 @@ const _ = require('lodash');
|
|||
const async = require('async');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'New Scan',
|
||||
desc : 'Performs a new scan against various areas of the system',
|
||||
author : 'NuSkooler',
|
||||
name : 'New Scan',
|
||||
desc : 'Performs a new scan against various areas of the system',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -30,239 +30,239 @@ exports.moduleInfo = {
|
|||
*/
|
||||
|
||||
const MciCodeIds = {
|
||||
ScanStatusLabel : 1, // TL1
|
||||
ScanStatusList : 2, // VM2 (appends)
|
||||
ScanStatusLabel : 1, // TL1
|
||||
ScanStatusList : 2, // VM2 (appends)
|
||||
};
|
||||
|
||||
const Steps = {
|
||||
MessageConfs : 'messageConferences',
|
||||
FileBase : 'fileBase',
|
||||
MessageConfs : 'messageConferences',
|
||||
FileBase : 'fileBase',
|
||||
|
||||
Finished : 'finished',
|
||||
Finished : 'finished',
|
||||
};
|
||||
|
||||
exports.getModule = class NewScanModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
|
||||
this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false);
|
||||
this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false);
|
||||
|
||||
this.currentStep = Steps.MessageConfs;
|
||||
this.currentScanAux = {};
|
||||
this.currentStep = Steps.MessageConfs;
|
||||
this.currentScanAux = {};
|
||||
|
||||
// :TODO: Make this conf/area specific:
|
||||
const config = this.menuConfig.config;
|
||||
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
||||
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
||||
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
||||
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
||||
}
|
||||
// :TODO: Make this conf/area specific:
|
||||
const config = this.menuConfig.config;
|
||||
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
||||
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
||||
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
||||
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
||||
}
|
||||
|
||||
updateScanStatus(statusText) {
|
||||
this.setViewText('allViews', MciCodeIds.ScanStatusLabel, statusText);
|
||||
}
|
||||
updateScanStatus(statusText) {
|
||||
this.setViewText('allViews', MciCodeIds.ScanStatusLabel, statusText);
|
||||
}
|
||||
|
||||
newScanMessageConference(cb) {
|
||||
// lazy init
|
||||
if(!this.sortedMessageConfs) {
|
||||
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
||||
newScanMessageConference(cb) {
|
||||
// lazy init
|
||||
if(!this.sortedMessageConfs) {
|
||||
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
||||
|
||||
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => {
|
||||
return {
|
||||
confTag : k,
|
||||
conf : v,
|
||||
};
|
||||
});
|
||||
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => {
|
||||
return {
|
||||
confTag : k,
|
||||
conf : v,
|
||||
};
|
||||
});
|
||||
|
||||
//
|
||||
// Sort conferences by name, other than 'system_internal' which should
|
||||
// always come first such that we display private mails/etc. before
|
||||
// other conferences & areas
|
||||
//
|
||||
this.sortedMessageConfs.sort((a, b) => {
|
||||
if('system_internal' === a.confTag) {
|
||||
return -1;
|
||||
} else {
|
||||
return a.conf.name.localeCompare(b.conf.name, { sensitivity : false, numeric : true } );
|
||||
}
|
||||
});
|
||||
//
|
||||
// Sort conferences by name, other than 'system_internal' which should
|
||||
// always come first such that we display private mails/etc. before
|
||||
// other conferences & areas
|
||||
//
|
||||
this.sortedMessageConfs.sort((a, b) => {
|
||||
if('system_internal' === a.confTag) {
|
||||
return -1;
|
||||
} else {
|
||||
return a.conf.name.localeCompare(b.conf.name, { sensitivity : false, numeric : true } );
|
||||
}
|
||||
});
|
||||
|
||||
this.currentScanAux.conf = this.currentScanAux.conf || 0;
|
||||
this.currentScanAux.area = this.currentScanAux.area || 0;
|
||||
}
|
||||
this.currentScanAux.conf = this.currentScanAux.conf || 0;
|
||||
this.currentScanAux.area = this.currentScanAux.area || 0;
|
||||
}
|
||||
|
||||
const currentConf = this.sortedMessageConfs[this.currentScanAux.conf];
|
||||
const currentConf = this.sortedMessageConfs[this.currentScanAux.conf];
|
||||
|
||||
this.newScanMessageArea(currentConf, () => {
|
||||
if(this.sortedMessageConfs.length > this.currentScanAux.conf + 1) {
|
||||
this.currentScanAux.conf += 1;
|
||||
this.currentScanAux.area = 0;
|
||||
this.newScanMessageArea(currentConf, () => {
|
||||
if(this.sortedMessageConfs.length > this.currentScanAux.conf + 1) {
|
||||
this.currentScanAux.conf += 1;
|
||||
this.currentScanAux.area = 0;
|
||||
|
||||
return this.newScanMessageConference(cb); // recursive to next conf
|
||||
}
|
||||
return this.newScanMessageConference(cb); // recursive to next conf
|
||||
}
|
||||
|
||||
this.updateScanStatus(this.scanCompleteMsg);
|
||||
return cb(Errors.DoesNotExist('No more conferences'));
|
||||
});
|
||||
}
|
||||
this.updateScanStatus(this.scanCompleteMsg);
|
||||
return cb(Errors.DoesNotExist('No more conferences'));
|
||||
});
|
||||
}
|
||||
|
||||
newScanMessageArea(conf, cb) {
|
||||
// :TODO: it would be nice to cache this - must be done by conf!
|
||||
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } );
|
||||
const currentArea = sortedAreas[this.currentScanAux.area];
|
||||
newScanMessageArea(conf, cb) {
|
||||
// :TODO: it would be nice to cache this - must be done by conf!
|
||||
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } );
|
||||
const currentArea = sortedAreas[this.currentScanAux.area];
|
||||
|
||||
//
|
||||
// Scan and update index until we find something. If results are found,
|
||||
// we'll goto the list module & show them.
|
||||
//
|
||||
const self = this;
|
||||
async.waterfall(
|
||||
[
|
||||
function checkAndUpdateIndex(callback) {
|
||||
// Advance to next area if possible
|
||||
if(sortedAreas.length >= self.currentScanAux.area + 1) {
|
||||
self.currentScanAux.area += 1;
|
||||
return callback(null);
|
||||
} else {
|
||||
self.updateScanStatus(self.scanCompleteMsg);
|
||||
return callback(Errors.DoesNotExist('No more areas')); // this will stop our scan
|
||||
}
|
||||
},
|
||||
function updateStatusScanStarted(callback) {
|
||||
self.updateScanStatus(stringFormat(self.scanStartFmt, {
|
||||
confName : conf.conf.name,
|
||||
confDesc : conf.conf.desc,
|
||||
areaName : currentArea.area.name,
|
||||
areaDesc : currentArea.area.desc
|
||||
}));
|
||||
return callback(null);
|
||||
},
|
||||
function getNewMessagesCountInArea(callback) {
|
||||
msgArea.getNewMessageCountInAreaForUser(
|
||||
self.client.user.userId, currentArea.areaTag, (err, newMessageCount) => {
|
||||
callback(err, newMessageCount);
|
||||
}
|
||||
);
|
||||
},
|
||||
function displayMessageList(newMessageCount) {
|
||||
if(newMessageCount <= 0) {
|
||||
return self.newScanMessageArea(conf, cb); // next area, if any
|
||||
}
|
||||
//
|
||||
// Scan and update index until we find something. If results are found,
|
||||
// we'll goto the list module & show them.
|
||||
//
|
||||
const self = this;
|
||||
async.waterfall(
|
||||
[
|
||||
function checkAndUpdateIndex(callback) {
|
||||
// Advance to next area if possible
|
||||
if(sortedAreas.length >= self.currentScanAux.area + 1) {
|
||||
self.currentScanAux.area += 1;
|
||||
return callback(null);
|
||||
} else {
|
||||
self.updateScanStatus(self.scanCompleteMsg);
|
||||
return callback(Errors.DoesNotExist('No more areas')); // this will stop our scan
|
||||
}
|
||||
},
|
||||
function updateStatusScanStarted(callback) {
|
||||
self.updateScanStatus(stringFormat(self.scanStartFmt, {
|
||||
confName : conf.conf.name,
|
||||
confDesc : conf.conf.desc,
|
||||
areaName : currentArea.area.name,
|
||||
areaDesc : currentArea.area.desc
|
||||
}));
|
||||
return callback(null);
|
||||
},
|
||||
function getNewMessagesCountInArea(callback) {
|
||||
msgArea.getNewMessageCountInAreaForUser(
|
||||
self.client.user.userId, currentArea.areaTag, (err, newMessageCount) => {
|
||||
callback(err, newMessageCount);
|
||||
}
|
||||
);
|
||||
},
|
||||
function displayMessageList(newMessageCount) {
|
||||
if(newMessageCount <= 0) {
|
||||
return self.newScanMessageArea(conf, cb); // next area, if any
|
||||
}
|
||||
|
||||
const nextModuleOpts = {
|
||||
extraArgs: {
|
||||
messageAreaTag : currentArea.areaTag,
|
||||
}
|
||||
};
|
||||
const nextModuleOpts = {
|
||||
extraArgs: {
|
||||
messageAreaTag : currentArea.areaTag,
|
||||
}
|
||||
};
|
||||
|
||||
return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
newScanFileBase(cb) {
|
||||
// :TODO: add in steps
|
||||
const filterCriteria = {
|
||||
newerThanFileId : FileBaseFilters.getFileBaseLastViewedFileIdByUser(this.client.user),
|
||||
areaTag : getAvailableFileAreaTags(this.client),
|
||||
order : 'ascending', // oldest first
|
||||
};
|
||||
newScanFileBase(cb) {
|
||||
// :TODO: add in steps
|
||||
const filterCriteria = {
|
||||
newerThanFileId : FileBaseFilters.getFileBaseLastViewedFileIdByUser(this.client.user),
|
||||
areaTag : getAvailableFileAreaTags(this.client),
|
||||
order : 'ascending', // oldest first
|
||||
};
|
||||
|
||||
FileEntry.findFiles(
|
||||
filterCriteria,
|
||||
(err, fileIds) => {
|
||||
if(err || 0 === fileIds.length) {
|
||||
return cb(err ? err : Errors.DoesNotExist('No more new files'));
|
||||
}
|
||||
FileEntry.findFiles(
|
||||
filterCriteria,
|
||||
(err, fileIds) => {
|
||||
if(err || 0 === fileIds.length) {
|
||||
return cb(err ? err : Errors.DoesNotExist('No more new files'));
|
||||
}
|
||||
|
||||
FileBaseFilters.setFileBaseLastViewedFileIdForUser( this.client.user, fileIds[fileIds.length - 1] );
|
||||
FileBaseFilters.setFileBaseLastViewedFileIdForUser( this.client.user, fileIds[fileIds.length - 1] );
|
||||
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
fileList : fileIds,
|
||||
},
|
||||
};
|
||||
const menuOpts = {
|
||||
extraArgs : {
|
||||
fileList : fileIds,
|
||||
},
|
||||
};
|
||||
|
||||
return this.gotoMenu(this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList', menuOpts);
|
||||
}
|
||||
);
|
||||
}
|
||||
return this.gotoMenu(this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList', menuOpts);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getSaveState() {
|
||||
return {
|
||||
currentStep : this.currentStep,
|
||||
currentScanAux : this.currentScanAux,
|
||||
};
|
||||
}
|
||||
getSaveState() {
|
||||
return {
|
||||
currentStep : this.currentStep,
|
||||
currentScanAux : this.currentScanAux,
|
||||
};
|
||||
}
|
||||
|
||||
restoreSavedState(savedState) {
|
||||
this.currentStep = savedState.currentStep;
|
||||
this.currentScanAux = savedState.currentScanAux;
|
||||
}
|
||||
restoreSavedState(savedState) {
|
||||
this.currentStep = savedState.currentStep;
|
||||
this.currentScanAux = savedState.currentScanAux;
|
||||
}
|
||||
|
||||
performScanCurrentStep(cb) {
|
||||
switch(this.currentStep) {
|
||||
case Steps.MessageConfs :
|
||||
this.newScanMessageConference( () => {
|
||||
this.currentStep = Steps.FileBase;
|
||||
return this.performScanCurrentStep(cb);
|
||||
});
|
||||
break;
|
||||
performScanCurrentStep(cb) {
|
||||
switch(this.currentStep) {
|
||||
case Steps.MessageConfs :
|
||||
this.newScanMessageConference( () => {
|
||||
this.currentStep = Steps.FileBase;
|
||||
return this.performScanCurrentStep(cb);
|
||||
});
|
||||
break;
|
||||
|
||||
case Steps.FileBase :
|
||||
this.newScanFileBase( () => {
|
||||
this.currentStep = Steps.Finished;
|
||||
return this.performScanCurrentStep(cb);
|
||||
});
|
||||
break;
|
||||
case Steps.FileBase :
|
||||
this.newScanFileBase( () => {
|
||||
this.currentStep = Steps.Finished;
|
||||
return this.performScanCurrentStep(cb);
|
||||
});
|
||||
break;
|
||||
|
||||
default : return cb(null);
|
||||
}
|
||||
}
|
||||
default : return cb(null);
|
||||
}
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
if(this.newScanFullExit) {
|
||||
// user has canceled the entire scan @ message list view
|
||||
return cb(null);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
if(this.newScanFullExit) {
|
||||
// user has canceled the entire scan @ message list view
|
||||
return cb(null);
|
||||
}
|
||||
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
super.mciReady(mciData, err => {
|
||||
if(err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
const self = this;
|
||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||
|
||||
// :TODO: display scan step/etc.
|
||||
// :TODO: display scan step/etc.
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
noInput : true,
|
||||
};
|
||||
async.series(
|
||||
[
|
||||
function loadFromConfig(callback) {
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : mciData.menu,
|
||||
noInput : true,
|
||||
};
|
||||
|
||||
vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function performCurrentStepScan(callback) {
|
||||
return self.performScanCurrentStep(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.toString() }, 'Error during new scan');
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
vc.loadFromMenuConfig(loadOpts, callback);
|
||||
},
|
||||
function performCurrentStepScan(callback) {
|
||||
return self.performScanCurrentStep(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
self.client.log.error( { error : err.toString() }, 'Error during new scan');
|
||||
}
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
208
core/nua.js
208
core/nua.js
|
@ -10,136 +10,136 @@ const Config = require('./config.js').get;
|
|||
const messageArea = require('./message_area.js');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'NUA',
|
||||
desc : 'New User Application',
|
||||
name : 'NUA',
|
||||
desc : 'New User Application',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
userName : 1,
|
||||
password : 9,
|
||||
confirm : 10,
|
||||
errMsg : 11,
|
||||
userName : 1,
|
||||
password : 9,
|
||||
confirm : 10,
|
||||
errMsg : 11,
|
||||
};
|
||||
|
||||
exports.getModule = class NewUserAppModule extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
this.menuMethods = {
|
||||
//
|
||||
// Validation stuff
|
||||
//
|
||||
validatePassConfirmMatch : function(data, cb) {
|
||||
const passwordView = self.viewControllers.menu.getView(MciViewIds.password);
|
||||
return cb(passwordView.getData() === data ? null : new Error('Passwords do not match'));
|
||||
},
|
||||
this.menuMethods = {
|
||||
//
|
||||
// Validation stuff
|
||||
//
|
||||
validatePassConfirmMatch : function(data, cb) {
|
||||
const passwordView = self.viewControllers.menu.getView(MciViewIds.password);
|
||||
return cb(passwordView.getData() === data ? null : new Error('Passwords do not match'));
|
||||
},
|
||||
|
||||
viewValidationListener : function(err, cb) {
|
||||
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
||||
let newFocusId;
|
||||
viewValidationListener : function(err, cb) {
|
||||
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
||||
let newFocusId;
|
||||
|
||||
if(err) {
|
||||
errMsgView.setText(err.message);
|
||||
err.view.clearText();
|
||||
if(err) {
|
||||
errMsgView.setText(err.message);
|
||||
err.view.clearText();
|
||||
|
||||
if(err.view.getId() === MciViewIds.confirm) {
|
||||
newFocusId = MciViewIds.password;
|
||||
self.viewControllers.menu.getView(MciViewIds.password).clearText();
|
||||
}
|
||||
} else {
|
||||
errMsgView.clearText();
|
||||
}
|
||||
if(err.view.getId() === MciViewIds.confirm) {
|
||||
newFocusId = MciViewIds.password;
|
||||
self.viewControllers.menu.getView(MciViewIds.password).clearText();
|
||||
}
|
||||
} else {
|
||||
errMsgView.clearText();
|
||||
}
|
||||
|
||||
return cb(newFocusId);
|
||||
},
|
||||
return cb(newFocusId);
|
||||
},
|
||||
|
||||
|
||||
//
|
||||
// Submit handlers
|
||||
//
|
||||
submitApplication : function(formData, extraArgs, cb) {
|
||||
const newUser = new User();
|
||||
const config = Config();
|
||||
//
|
||||
// Submit handlers
|
||||
//
|
||||
submitApplication : function(formData, extraArgs, cb) {
|
||||
const newUser = new User();
|
||||
const config = Config();
|
||||
|
||||
newUser.username = formData.value.username;
|
||||
newUser.username = formData.value.username;
|
||||
|
||||
//
|
||||
// We have to disable ACS checks for initial default areas as the user is not yet ready
|
||||
//
|
||||
let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck
|
||||
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
||||
//
|
||||
// We have to disable ACS checks for initial default areas as the user is not yet ready
|
||||
//
|
||||
let confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck
|
||||
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
||||
|
||||
// can't store undefined!
|
||||
confTag = confTag || '';
|
||||
areaTag = areaTag || '';
|
||||
// can't store undefined!
|
||||
confTag = confTag || '';
|
||||
areaTag = areaTag || '';
|
||||
|
||||
newUser.properties = {
|
||||
real_name : formData.value.realName,
|
||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
sex : formData.value.sex,
|
||||
location : formData.value.location,
|
||||
affiliation : formData.value.affils,
|
||||
email_address : formData.value.email,
|
||||
web_address : formData.value.web,
|
||||
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
newUser.properties = {
|
||||
real_name : formData.value.realName,
|
||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
sex : formData.value.sex,
|
||||
location : formData.value.location,
|
||||
affiliation : formData.value.affils,
|
||||
email_address : formData.value.email,
|
||||
web_address : formData.value.web,
|
||||
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||
|
||||
message_conf_tag : confTag,
|
||||
message_area_tag : areaTag,
|
||||
message_conf_tag : confTag,
|
||||
message_area_tag : areaTag,
|
||||
|
||||
term_height : self.client.term.termHeight,
|
||||
term_width : self.client.term.termWidth,
|
||||
term_height : self.client.term.termHeight,
|
||||
term_width : self.client.term.termWidth,
|
||||
|
||||
// :TODO: Other defaults
|
||||
// :TODO: should probably have a place to create defaults/etc.
|
||||
};
|
||||
// :TODO: Other defaults
|
||||
// :TODO: should probably have a place to create defaults/etc.
|
||||
};
|
||||
|
||||
if('*' === config.defaults.theme) {
|
||||
newUser.properties.theme_id = theme.getRandomTheme();
|
||||
} else {
|
||||
newUser.properties.theme_id = config.defaults.theme;
|
||||
}
|
||||
if('*' === config.defaults.theme) {
|
||||
newUser.properties.theme_id = theme.getRandomTheme();
|
||||
} else {
|
||||
newUser.properties.theme_id = config.defaults.theme;
|
||||
}
|
||||
|
||||
// :TODO: User.create() should validate email uniqueness!
|
||||
newUser.create(formData.value.password, err => {
|
||||
if(err) {
|
||||
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
||||
// :TODO: User.create() should validate email uniqueness!
|
||||
newUser.create(formData.value.password, err => {
|
||||
if(err) {
|
||||
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
||||
|
||||
self.gotoMenu(extraArgs.error, err => {
|
||||
if(err) {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
} else {
|
||||
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created');
|
||||
self.gotoMenu(extraArgs.error, err => {
|
||||
if(err) {
|
||||
return self.prevMenu(cb);
|
||||
}
|
||||
return cb(null);
|
||||
});
|
||||
} else {
|
||||
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created');
|
||||
|
||||
// Cache SysOp information now
|
||||
// :TODO: Similar to bbs.js. DRY
|
||||
if(newUser.isSysOp()) {
|
||||
config.general.sysOp = {
|
||||
username : formData.value.username,
|
||||
properties : newUser.properties,
|
||||
};
|
||||
}
|
||||
// Cache SysOp information now
|
||||
// :TODO: Similar to bbs.js. DRY
|
||||
if(newUser.isSysOp()) {
|
||||
config.general.sysOp = {
|
||||
username : formData.value.username,
|
||||
properties : newUser.properties,
|
||||
};
|
||||
}
|
||||
|
||||
if(User.AccountStatus.inactive === self.client.user.properties.account_status) {
|
||||
return self.gotoMenu(extraArgs.inactive, cb);
|
||||
} else {
|
||||
//
|
||||
// If active now, we need to call login() to authenticate
|
||||
//
|
||||
return login(self, formData, extraArgs, cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
if(User.AccountStatus.inactive === self.client.user.properties.account_status) {
|
||||
return self.gotoMenu(extraArgs.inactive, cb);
|
||||
} else {
|
||||
//
|
||||
// If active now, we need to call login() to authenticate
|
||||
//
|
||||
return login(self, formData, extraArgs, cb);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
mciReady(mciData, cb) {
|
||||
return this.standardMCIReadyHandler(mciData, cb);
|
||||
}
|
||||
mciReady(mciData, cb) {
|
||||
return this.standardMCIReadyHandler(mciData, cb);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
|
||||
const {
|
||||
getModDatabasePath,
|
||||
getTransactionDatabase
|
||||
getModDatabasePath,
|
||||
getTransactionDatabase
|
||||
} = require('./database.js');
|
||||
|
||||
const ViewController = require('./view_controller.js').ViewController;
|
||||
|
@ -30,136 +30,136 @@ const moment = require('moment');
|
|||
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Onelinerz',
|
||||
desc : 'Standard local onelinerz',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.onelinerz',
|
||||
name : 'Onelinerz',
|
||||
desc : 'Standard local onelinerz',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.onelinerz',
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
ViewForm : {
|
||||
Entries : 1,
|
||||
AddPrompt : 2,
|
||||
},
|
||||
AddForm : {
|
||||
NewEntry : 1,
|
||||
EntryPreview : 2,
|
||||
AddPrompt : 3,
|
||||
}
|
||||
ViewForm : {
|
||||
Entries : 1,
|
||||
AddPrompt : 2,
|
||||
},
|
||||
AddForm : {
|
||||
NewEntry : 1,
|
||||
EntryPreview : 2,
|
||||
AddPrompt : 3,
|
||||
}
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
View : 0,
|
||||
Add : 1,
|
||||
View : 0,
|
||||
Add : 1,
|
||||
};
|
||||
|
||||
exports.getModule = class OnelinerzModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const self = this;
|
||||
const self = this;
|
||||
|
||||
this.menuMethods = {
|
||||
viewAddScreen : function(formData, extraArgs, cb) {
|
||||
return self.displayAddScreen(cb);
|
||||
},
|
||||
this.menuMethods = {
|
||||
viewAddScreen : function(formData, extraArgs, cb) {
|
||||
return self.displayAddScreen(cb);
|
||||
},
|
||||
|
||||
addEntry : function(formData, extraArgs, cb) {
|
||||
if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) {
|
||||
const oneliner = formData.value.oneliner.trim(); // remove any trailing ws
|
||||
addEntry : function(formData, extraArgs, cb) {
|
||||
if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) {
|
||||
const oneliner = formData.value.oneliner.trim(); // remove any trailing ws
|
||||
|
||||
self.storeNewOneliner(oneliner, err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'Failed saving oneliner');
|
||||
}
|
||||
self.storeNewOneliner(oneliner, err => {
|
||||
if(err) {
|
||||
self.client.log.warn( { error : err.message }, 'Failed saving oneliner');
|
||||
}
|
||||
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
|
||||
} else {
|
||||
// empty message - treat as if cancel was hit
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
},
|
||||
} else {
|
||||
// empty message - treat as if cancel was hit
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
},
|
||||
|
||||
cancelAdd : function(formData, extraArgs, cb) {
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
};
|
||||
}
|
||||
cancelAdd : function(formData, extraArgs, cb) {
|
||||
self.clearAddForm();
|
||||
return self.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayViewScreen(false, callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||
}
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
initSequence() {
|
||||
const self = this;
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayViewScreen(false, callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||
}
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayViewScreen(clearScreen, cb) {
|
||||
const self = this;
|
||||
displayViewScreen(clearScreen, cb) {
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if(self.viewControllers.add) {
|
||||
self.viewControllers.add.setFocus(false);
|
||||
}
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if(self.viewControllers.add) {
|
||||
self.viewControllers.add.setFocus(false);
|
||||
}
|
||||
|
||||
if(clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
if(clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.entries,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.entries,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.View,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.View,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.ViewForm.Entries);
|
||||
const limit = entriesView.dimens.height;
|
||||
let entries = [];
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciViewIds.ViewForm.Entries);
|
||||
const limit = entriesView.dimens.height;
|
||||
let entries = [];
|
||||
|
||||
self.db.each(
|
||||
`SELECT *
|
||||
self.db.each(
|
||||
`SELECT *
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM onelinerz
|
||||
|
@ -167,172 +167,172 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
|||
LIMIT ${limit}
|
||||
)
|
||||
ORDER BY timestamp ASC;`,
|
||||
(err, row) => {
|
||||
if(!err) {
|
||||
row.timestamp = moment(row.timestamp); // convert -> moment
|
||||
entries.push(row);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return callback(err, entriesView, entries);
|
||||
}
|
||||
);
|
||||
},
|
||||
function populateEntries(entriesView, entries, callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent
|
||||
const tsFormat = self.menuConfig.config.timestampFormat || 'ddd h:mma';
|
||||
(err, row) => {
|
||||
if(!err) {
|
||||
row.timestamp = moment(row.timestamp); // convert -> moment
|
||||
entries.push(row);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return callback(err, entriesView, entries);
|
||||
}
|
||||
);
|
||||
},
|
||||
function populateEntries(entriesView, entries, callback) {
|
||||
const listFormat = self.menuConfig.config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent
|
||||
const tsFormat = self.menuConfig.config.timestampFormat || 'ddd h:mma';
|
||||
|
||||
entriesView.setItems(entries.map( e => {
|
||||
return stringFormat(listFormat, {
|
||||
userId : e.user_id,
|
||||
username : e.user_name,
|
||||
oneliner : e.oneliner,
|
||||
ts : e.timestamp.format(tsFormat),
|
||||
} );
|
||||
}));
|
||||
entriesView.setItems(entries.map( e => {
|
||||
return stringFormat(listFormat, {
|
||||
userId : e.user_id,
|
||||
username : e.user_name,
|
||||
oneliner : e.oneliner,
|
||||
ts : e.timestamp.format(tsFormat),
|
||||
} );
|
||||
}));
|
||||
|
||||
entriesView.redraw();
|
||||
entriesView.redraw();
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function finalPrep(callback) {
|
||||
const promptView = self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt);
|
||||
promptView.setFocusItemIndex(1); // default to NO
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function finalPrep(callback) {
|
||||
const promptView = self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt);
|
||||
promptView.setFocusItemIndex(1); // default to NO
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayAddScreen(cb) {
|
||||
const self = this;
|
||||
displayAddScreen(cb) {
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.add,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
theme.displayThemedAsset(
|
||||
self.menuConfig.config.art.add,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.Add,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.Add,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciViewIds.AddForm.NewEntry);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciViewIds.AddForm.NewEntry);
|
||||
return callback(null);
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
clearAddForm() {
|
||||
this.setViewText('add', MciViewIds.AddForm.NewEntry, '');
|
||||
this.setViewText('add', MciViewIds.AddForm.EntryPreview, '');
|
||||
}
|
||||
clearAddForm() {
|
||||
this.setViewText('add', MciViewIds.AddForm.NewEntry, '');
|
||||
this.setViewText('add', MciViewIds.AddForm.EntryPreview, '');
|
||||
}
|
||||
|
||||
initDatabase(cb) {
|
||||
const self = this;
|
||||
initDatabase(cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function openDatabase(callback) {
|
||||
self.db = getTransactionDatabase(new sqlite3.Database(
|
||||
getModDatabasePath(exports.moduleInfo),
|
||||
err => {
|
||||
return callback(err);
|
||||
}
|
||||
));
|
||||
},
|
||||
function createTables(callback) {
|
||||
self.db.run(
|
||||
`CREATE TABLE IF NOT EXISTS onelinerz (
|
||||
async.series(
|
||||
[
|
||||
function openDatabase(callback) {
|
||||
self.db = getTransactionDatabase(new sqlite3.Database(
|
||||
getModDatabasePath(exports.moduleInfo),
|
||||
err => {
|
||||
return callback(err);
|
||||
}
|
||||
));
|
||||
},
|
||||
function createTables(callback) {
|
||||
self.db.run(
|
||||
`CREATE TABLE IF NOT EXISTS onelinerz (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER_NOT NULL,
|
||||
user_name VARCHAR NOT NULL,
|
||||
oneliner VARCHAR NOT NULL,
|
||||
timestamp DATETIME NOT NULL
|
||||
);`
|
||||
,
|
||||
err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
,
|
||||
err => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
storeNewOneliner(oneliner, cb) {
|
||||
const self = this;
|
||||
const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||
storeNewOneliner(oneliner, cb) {
|
||||
const self = this;
|
||||
const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||
|
||||
async.series(
|
||||
[
|
||||
function addRec(callback) {
|
||||
self.db.run(
|
||||
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
|
||||
async.series(
|
||||
[
|
||||
function addRec(callback) {
|
||||
self.db.run(
|
||||
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
|
||||
VALUES (?, ?, ?, ?);`,
|
||||
[ self.client.user.userId, self.client.user.username, oneliner, ts ],
|
||||
callback
|
||||
);
|
||||
},
|
||||
function removeOld(callback) {
|
||||
// keep 25 max most recent items - remove the older ones
|
||||
self.db.run(
|
||||
`DELETE FROM onelinerz
|
||||
[ self.client.user.userId, self.client.user.username, oneliner, ts ],
|
||||
callback
|
||||
);
|
||||
},
|
||||
function removeOld(callback) {
|
||||
// keep 25 max most recent items - remove the older ones
|
||||
self.db.run(
|
||||
`DELETE FROM onelinerz
|
||||
WHERE id IN (
|
||||
SELECT id
|
||||
FROM onelinerz
|
||||
ORDER BY id DESC
|
||||
LIMIT -1 OFFSET 25
|
||||
);`,
|
||||
callback
|
||||
);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
callback
|
||||
);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
beforeArt(cb) {
|
||||
super.beforeArt(err => {
|
||||
return err ? cb(err) : this.initDatabase(cb);
|
||||
});
|
||||
}
|
||||
beforeArt(cb) {
|
||||
super.beforeArt(err => {
|
||||
return err ? cb(err) : this.initDatabase(cb);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,83 +16,83 @@ exports.getAreaAndStorage = getAreaAndStorage;
|
|||
exports.looksLikePattern = looksLikePattern;
|
||||
|
||||
const exitCodes = exports.ExitCodes = {
|
||||
SUCCESS : 0,
|
||||
ERROR : -1,
|
||||
BAD_COMMAND : -2,
|
||||
BAD_ARGS : -3,
|
||||
SUCCESS : 0,
|
||||
ERROR : -1,
|
||||
BAD_COMMAND : -2,
|
||||
BAD_ARGS : -3,
|
||||
};
|
||||
|
||||
const argv = exports.argv = require('minimist')(process.argv.slice(2), {
|
||||
alias : {
|
||||
h : 'help',
|
||||
v : 'version',
|
||||
c : 'config',
|
||||
n : 'no-prompt',
|
||||
}
|
||||
alias : {
|
||||
h : 'help',
|
||||
v : 'version',
|
||||
c : 'config',
|
||||
n : 'no-prompt',
|
||||
}
|
||||
});
|
||||
|
||||
function printUsageAndSetExitCode(errMsg, exitCode) {
|
||||
if(_.isUndefined(exitCode)) {
|
||||
exitCode = exitCodes.ERROR;
|
||||
}
|
||||
if(_.isUndefined(exitCode)) {
|
||||
exitCode = exitCodes.ERROR;
|
||||
}
|
||||
|
||||
process.exitCode = exitCode;
|
||||
process.exitCode = exitCode;
|
||||
|
||||
if(errMsg) {
|
||||
console.error(errMsg);
|
||||
}
|
||||
if(errMsg) {
|
||||
console.error(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
function getDefaultConfigPath() {
|
||||
return './config/';
|
||||
return './config/';
|
||||
}
|
||||
|
||||
function getConfigPath() {
|
||||
const baseConfigPath = argv.config ? argv.config : config.getDefaultPath();
|
||||
return baseConfigPath + 'config.hjson';
|
||||
const baseConfigPath = argv.config ? argv.config : config.getDefaultPath();
|
||||
return baseConfigPath + 'config.hjson';
|
||||
}
|
||||
|
||||
function initConfig(cb) {
|
||||
const configPath = getConfigPath();
|
||||
const configPath = getConfigPath();
|
||||
|
||||
config.init(configPath, { keepWsc : true, noWatch : true }, cb);
|
||||
config.init(configPath, { keepWsc : true, noWatch : true }, cb);
|
||||
}
|
||||
|
||||
function initConfigAndDatabases(cb) {
|
||||
async.series(
|
||||
[
|
||||
function init(callback) {
|
||||
initConfig(callback);
|
||||
},
|
||||
function initDb(callback) {
|
||||
db.initializeDatabases(callback);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
async.series(
|
||||
[
|
||||
function init(callback) {
|
||||
initConfig(callback);
|
||||
},
|
||||
function initDb(callback) {
|
||||
db.initializeDatabases(callback);
|
||||
},
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function getAreaAndStorage(tags) {
|
||||
return tags.map(tag => {
|
||||
const parts = tag.toString().split('@');
|
||||
const entry = {
|
||||
areaTag : parts[0],
|
||||
};
|
||||
entry.pattern = entry.areaTag; // handy
|
||||
if(parts[1]) {
|
||||
entry.storageTag = parts[1];
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
return tags.map(tag => {
|
||||
const parts = tag.toString().split('@');
|
||||
const entry = {
|
||||
areaTag : parts[0],
|
||||
};
|
||||
entry.pattern = entry.areaTag; // handy
|
||||
if(parts[1]) {
|
||||
entry.storageTag = parts[1];
|
||||
}
|
||||
return entry;
|
||||
});
|
||||
}
|
||||
|
||||
function looksLikePattern(tag) {
|
||||
// globs can start with @
|
||||
if(tag.indexOf('@') > 0) {
|
||||
return false;
|
||||
}
|
||||
// globs can start with @
|
||||
if(tag.indexOf('@') > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /[*?[\]!()+|^]/.test(tag);
|
||||
return /[*?[\]!()+|^]/.test(tag);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -7,7 +7,7 @@ const getDefaultConfigPath = require('./oputil_common.js').getDefaultConfigPat
|
|||
exports.getHelpFor = getHelpFor;
|
||||
|
||||
const usageHelp = exports.USAGE_HELP = {
|
||||
General :
|
||||
General :
|
||||
`usage: optutil.js [--version] [--help]
|
||||
<command> [<args>]
|
||||
|
||||
|
@ -21,7 +21,7 @@ commands:
|
|||
fb file base management
|
||||
mb message base management
|
||||
`,
|
||||
User :
|
||||
User :
|
||||
`usage: optutil.js user <action> [<args>]
|
||||
|
||||
actions:
|
||||
|
@ -33,7 +33,7 @@ actions:
|
|||
group USERNAME [+|-]GROUP adds (+) or removes (-) user from GROUP
|
||||
`,
|
||||
|
||||
Config :
|
||||
Config :
|
||||
`usage: optutil.js config <action> [<args>]
|
||||
|
||||
actions:
|
||||
|
@ -46,7 +46,7 @@ import-areas args:
|
|||
--uplinks UL1,UL2,... specify one or more comma separated uplinks
|
||||
--type TYPE specifies area import type. valid options are "bbs" and "na"
|
||||
`,
|
||||
FileBase :
|
||||
FileBase :
|
||||
`usage: oputil.js fb <action> [<args>]
|
||||
|
||||
actions:
|
||||
|
@ -80,7 +80,7 @@ info args:
|
|||
remove args:
|
||||
--phys-file also remove underlying physical file
|
||||
`,
|
||||
FileOpsInfo :
|
||||
FileOpsInfo :
|
||||
`
|
||||
general information:
|
||||
AREA_TAG[@STORAGE_TAG] can specify an area tag and optionally, a storage specific tag
|
||||
|
@ -90,7 +90,7 @@ general information:
|
|||
SHA full or partial SHA-256
|
||||
FILE_ID a file identifier. see file.sqlite3
|
||||
`,
|
||||
MessageBase :
|
||||
MessageBase :
|
||||
`usage: oputil.js mb <action> [<args>]
|
||||
|
||||
actions:
|
||||
|
@ -101,5 +101,5 @@ general information:
|
|||
};
|
||||
|
||||
function getHelpFor(command) {
|
||||
return usageHelp[command];
|
||||
return usageHelp[command];
|
||||
}
|
||||
|
|
|
@ -14,23 +14,23 @@ const getHelpFor = require('./oputil_help.js').getHelpFor;
|
|||
|
||||
module.exports = function() {
|
||||
|
||||
process.exitCode = ExitCodes.SUCCESS;
|
||||
process.exitCode = ExitCodes.SUCCESS;
|
||||
|
||||
if(true === argv.version) {
|
||||
return console.info(require('../package.json').version);
|
||||
}
|
||||
if(true === argv.version) {
|
||||
return console.info(require('../package.json').version);
|
||||
}
|
||||
|
||||
if(0 === argv._.length ||
|
||||
if(0 === argv._.length ||
|
||||
'help' === argv._[0])
|
||||
{
|
||||
return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.SUCCESS);
|
||||
}
|
||||
{
|
||||
return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.SUCCESS);
|
||||
}
|
||||
|
||||
switch(argv._[0]) {
|
||||
case 'user' : return handleUserCommand();
|
||||
case 'config' : return handleConfigCommand();
|
||||
case 'fb' : return handleFileBaseCommand();
|
||||
case 'mb' : return handleMessageBaseCommand();
|
||||
default : return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.BAD_COMMAND);
|
||||
}
|
||||
switch(argv._[0]) {
|
||||
case 'user' : return handleUserCommand();
|
||||
case 'config' : return handleConfigCommand();
|
||||
case 'fb' : return handleFileBaseCommand();
|
||||
case 'mb' : return handleMessageBaseCommand();
|
||||
default : return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.BAD_COMMAND);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -16,127 +16,127 @@ const async = require('async');
|
|||
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
||||
|
||||
function areaFix() {
|
||||
//
|
||||
// oputil mb areafix CMD1 CMD2 ... ADDR [--password PASS]
|
||||
//
|
||||
if(argv._.length < 3) {
|
||||
return printUsageAndSetExitCode(
|
||||
getHelpFor('MessageBase'),
|
||||
ExitCodes.ERROR
|
||||
);
|
||||
}
|
||||
//
|
||||
// oputil mb areafix CMD1 CMD2 ... ADDR [--password PASS]
|
||||
//
|
||||
if(argv._.length < 3) {
|
||||
return printUsageAndSetExitCode(
|
||||
getHelpFor('MessageBase'),
|
||||
ExitCodes.ERROR
|
||||
);
|
||||
}
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function init(callback) {
|
||||
return initConfigAndDatabases(callback);
|
||||
},
|
||||
function validateAddress(callback) {
|
||||
const addrArg = argv._.slice(-1)[0];
|
||||
const ftnAddr = Address.fromString(addrArg);
|
||||
async.waterfall(
|
||||
[
|
||||
function init(callback) {
|
||||
return initConfigAndDatabases(callback);
|
||||
},
|
||||
function validateAddress(callback) {
|
||||
const addrArg = argv._.slice(-1)[0];
|
||||
const ftnAddr = Address.fromString(addrArg);
|
||||
|
||||
if(!ftnAddr) {
|
||||
return callback(Errors.Invalid(`"${addrArg}" is not a valid FTN address`));
|
||||
}
|
||||
if(!ftnAddr) {
|
||||
return callback(Errors.Invalid(`"${addrArg}" is not a valid FTN address`));
|
||||
}
|
||||
|
||||
//
|
||||
// We need to validate the address targets a system we know unless
|
||||
// the --force option is used
|
||||
//
|
||||
// :TODO:
|
||||
return callback(null, ftnAddr);
|
||||
},
|
||||
function fetchFromUser(ftnAddr, callback) {
|
||||
//
|
||||
// --from USER || +op from system
|
||||
//
|
||||
// If possible, we want the user ID of the supplied user as well
|
||||
//
|
||||
const User = require('../user.js');
|
||||
//
|
||||
// We need to validate the address targets a system we know unless
|
||||
// the --force option is used
|
||||
//
|
||||
// :TODO:
|
||||
return callback(null, ftnAddr);
|
||||
},
|
||||
function fetchFromUser(ftnAddr, callback) {
|
||||
//
|
||||
// --from USER || +op from system
|
||||
//
|
||||
// If possible, we want the user ID of the supplied user as well
|
||||
//
|
||||
const User = require('../user.js');
|
||||
|
||||
if(argv.from) {
|
||||
User.getUserIdAndNameByLookup(argv.from, (err, userId, fromName) => {
|
||||
if(err) {
|
||||
return callback(null, ftnAddr, argv.from, 0);
|
||||
}
|
||||
if(argv.from) {
|
||||
User.getUserIdAndNameByLookup(argv.from, (err, userId, fromName) => {
|
||||
if(err) {
|
||||
return callback(null, ftnAddr, argv.from, 0);
|
||||
}
|
||||
|
||||
// fromName is the same as argv.from, but case may be differnet (yet correct)
|
||||
return callback(null, ftnAddr, fromName, userId);
|
||||
});
|
||||
} else {
|
||||
User.getUserName(User.RootUserID, (err, fromName) => {
|
||||
return callback(null, ftnAddr, fromName || 'SysOp', err ? 0 : User.RootUserID);
|
||||
});
|
||||
}
|
||||
},
|
||||
function createMessage(ftnAddr, fromName, fromUserId, callback) {
|
||||
//
|
||||
// Build message as commands separated by line feed
|
||||
//
|
||||
// We need to remove quotes from arguments. These are required
|
||||
// in the case of e.g. removing an area: "-SOME_AREA" would end
|
||||
// up confusing minimist, therefor they must be quoted: "'-SOME_AREA'"
|
||||
//
|
||||
const messageBody = argv._.slice(2, -1).map(arg => {
|
||||
return arg.replace(/["']/g, '');
|
||||
}).join('\r\n') + '\n';
|
||||
// fromName is the same as argv.from, but case may be differnet (yet correct)
|
||||
return callback(null, ftnAddr, fromName, userId);
|
||||
});
|
||||
} else {
|
||||
User.getUserName(User.RootUserID, (err, fromName) => {
|
||||
return callback(null, ftnAddr, fromName || 'SysOp', err ? 0 : User.RootUserID);
|
||||
});
|
||||
}
|
||||
},
|
||||
function createMessage(ftnAddr, fromName, fromUserId, callback) {
|
||||
//
|
||||
// Build message as commands separated by line feed
|
||||
//
|
||||
// We need to remove quotes from arguments. These are required
|
||||
// in the case of e.g. removing an area: "-SOME_AREA" would end
|
||||
// up confusing minimist, therefor they must be quoted: "'-SOME_AREA'"
|
||||
//
|
||||
const messageBody = argv._.slice(2, -1).map(arg => {
|
||||
return arg.replace(/["']/g, '');
|
||||
}).join('\r\n') + '\n';
|
||||
|
||||
const Message = require('../message.js');
|
||||
const Message = require('../message.js');
|
||||
|
||||
const message = new Message({
|
||||
toUserName : argv.to || 'AreaFix',
|
||||
fromUserName : fromName,
|
||||
subject : argv.password || '',
|
||||
message : messageBody,
|
||||
areaTag : Message.WellKnownAreaTags.Private, // mark private
|
||||
meta : {
|
||||
System : {
|
||||
[ Message.SystemMetaNames.RemoteToUser ] : ftnAddr.toString(), // where to send it
|
||||
[ Message.SystemMetaNames.ExternalFlavor ] : Message.AddressFlavor.FTN, // on FTN-style network
|
||||
}
|
||||
}
|
||||
});
|
||||
const message = new Message({
|
||||
toUserName : argv.to || 'AreaFix',
|
||||
fromUserName : fromName,
|
||||
subject : argv.password || '',
|
||||
message : messageBody,
|
||||
areaTag : Message.WellKnownAreaTags.Private, // mark private
|
||||
meta : {
|
||||
System : {
|
||||
[ Message.SystemMetaNames.RemoteToUser ] : ftnAddr.toString(), // where to send it
|
||||
[ Message.SystemMetaNames.ExternalFlavor ] : Message.AddressFlavor.FTN, // on FTN-style network
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if(0 !== fromUserId) {
|
||||
message.setLocalFromUserId(fromUserId);
|
||||
}
|
||||
if(0 !== fromUserId) {
|
||||
message.setLocalFromUserId(fromUserId);
|
||||
}
|
||||
|
||||
return callback(null, message);
|
||||
},
|
||||
function persistMessage(message, callback) {
|
||||
message.persist(err => {
|
||||
if(!err) {
|
||||
console.log('AreaFix message persisted and will be exported at next scheduled scan');
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
console.error(`${err.message}${err.reason ? ': ' + err.reason : ''}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
return callback(null, message);
|
||||
},
|
||||
function persistMessage(message, callback) {
|
||||
message.persist(err => {
|
||||
if(!err) {
|
||||
console.log('AreaFix message persisted and will be exported at next scheduled scan');
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
console.error(`${err.message}${err.reason ? ': ' + err.reason : ''}`);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function handleMessageBaseCommand() {
|
||||
|
||||
function errUsage() {
|
||||
return printUsageAndSetExitCode(
|
||||
getHelpFor('MessageBase'),
|
||||
ExitCodes.ERROR
|
||||
);
|
||||
}
|
||||
function errUsage() {
|
||||
return printUsageAndSetExitCode(
|
||||
getHelpFor('MessageBase'),
|
||||
ExitCodes.ERROR
|
||||
);
|
||||
}
|
||||
|
||||
if(true === argv.help) {
|
||||
return errUsage();
|
||||
}
|
||||
if(true === argv.help) {
|
||||
return errUsage();
|
||||
}
|
||||
|
||||
const action = argv._[1];
|
||||
const action = argv._[1];
|
||||
|
||||
return({
|
||||
areafix : areaFix,
|
||||
}[action] || errUsage)();
|
||||
return({
|
||||
areafix : areaFix,
|
||||
}[action] || errUsage)();
|
||||
}
|
|
@ -15,191 +15,191 @@ const _ = require('lodash');
|
|||
exports.handleUserCommand = handleUserCommand;
|
||||
|
||||
function getUser(userName, cb) {
|
||||
const User = require('../../core/user.js');
|
||||
User.getUserIdAndName(userName, (err, userId) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return cb(err);
|
||||
}
|
||||
const u = new User();
|
||||
u.userId = userId;
|
||||
return cb(null, u);
|
||||
});
|
||||
const User = require('../../core/user.js');
|
||||
User.getUserIdAndName(userName, (err, userId) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return cb(err);
|
||||
}
|
||||
const u = new User();
|
||||
u.userId = userId;
|
||||
return cb(null, u);
|
||||
});
|
||||
}
|
||||
|
||||
function initAndGetUser(userName, cb) {
|
||||
async.waterfall(
|
||||
[
|
||||
function init(callback) {
|
||||
initConfigAndDatabases(callback);
|
||||
},
|
||||
function getUserObject(callback) {
|
||||
getUser(userName, (err, user) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, user);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, user) => {
|
||||
return cb(err, user);
|
||||
}
|
||||
);
|
||||
async.waterfall(
|
||||
[
|
||||
function init(callback) {
|
||||
initConfigAndDatabases(callback);
|
||||
},
|
||||
function getUserObject(callback) {
|
||||
getUser(userName, (err, user) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return callback(err);
|
||||
}
|
||||
return callback(null, user);
|
||||
});
|
||||
}
|
||||
],
|
||||
(err, user) => {
|
||||
return cb(err, user);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function setAccountStatus(user, status) {
|
||||
if(argv._.length < 3) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
if(argv._.length < 3) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
const statusDesc = _.invert(AccountStatus)[status];
|
||||
user.persistProperty('account_status', status, err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info(`User status set to ${statusDesc}`);
|
||||
}
|
||||
});
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
const statusDesc = _.invert(AccountStatus)[status];
|
||||
user.persistProperty('account_status', status, err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info(`User status set to ${statusDesc}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setUserPassword(user) {
|
||||
if(argv._.length < 4) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
if(argv._.length < 4) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function validate(callback) {
|
||||
// :TODO: prompt if no password provided (more secure, no history, etc.)
|
||||
const password = argv._[argv._.length - 1];
|
||||
if(0 === password.length) {
|
||||
return callback(Errors.Invalid('Invalid password'));
|
||||
}
|
||||
return callback(null, password);
|
||||
},
|
||||
function set(password, callback) {
|
||||
user.setNewAuthCredentials(password, err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info('New password set');
|
||||
}
|
||||
}
|
||||
);
|
||||
async.waterfall(
|
||||
[
|
||||
function validate(callback) {
|
||||
// :TODO: prompt if no password provided (more secure, no history, etc.)
|
||||
const password = argv._[argv._.length - 1];
|
||||
if(0 === password.length) {
|
||||
return callback(Errors.Invalid('Invalid password'));
|
||||
}
|
||||
return callback(null, password);
|
||||
},
|
||||
function set(password, callback) {
|
||||
user.setNewAuthCredentials(password, err => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
}
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info('New password set');
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function removeUser(user) {
|
||||
console.error('NOT YET IMPLEMENTED');
|
||||
function removeUser() {
|
||||
console.error('NOT YET IMPLEMENTED');
|
||||
}
|
||||
|
||||
function modUserGroups(user) {
|
||||
if(argv._.length < 3) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
if(argv._.length < 3) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
|
||||
let groupName = argv._[argv._.length - 1].toString().replace(/["']/g, ''); // remove any quotes - necessary to allow "-foo"
|
||||
let action = groupName[0]; // + or -
|
||||
let groupName = argv._[argv._.length - 1].toString().replace(/["']/g, ''); // remove any quotes - necessary to allow "-foo"
|
||||
let action = groupName[0]; // + or -
|
||||
|
||||
if('-' === action || '+' === action) {
|
||||
groupName = groupName.substr(1);
|
||||
}
|
||||
if('-' === action || '+' === action) {
|
||||
groupName = groupName.substr(1);
|
||||
}
|
||||
|
||||
action = action || '+';
|
||||
action = action || '+';
|
||||
|
||||
if(0 === groupName.length) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
if(0 === groupName.length) {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
|
||||
//
|
||||
// Groups are currently arbritary, so do a slight validation
|
||||
//
|
||||
if(!/[A-Za-z0-9]+/.test(groupName)) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return console.error('Bad group name');
|
||||
}
|
||||
//
|
||||
// Groups are currently arbritary, so do a slight validation
|
||||
//
|
||||
if(!/[A-Za-z0-9]+/.test(groupName)) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
return console.error('Bad group name');
|
||||
}
|
||||
|
||||
function done(err) {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info('User groups modified');
|
||||
}
|
||||
}
|
||||
function done(err) {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.BAD_ARGS;
|
||||
console.error(err.message);
|
||||
} else {
|
||||
console.info('User groups modified');
|
||||
}
|
||||
}
|
||||
|
||||
const UserGroup = require('../../core/user_group.js');
|
||||
if('-' === action) {
|
||||
UserGroup.removeUserFromGroup(user.userId, groupName, done);
|
||||
} else {
|
||||
UserGroup.addUserToGroup(user.userId, groupName, done);
|
||||
}
|
||||
const UserGroup = require('../../core/user_group.js');
|
||||
if('-' === action) {
|
||||
UserGroup.removeUserFromGroup(user.userId, groupName, done);
|
||||
} else {
|
||||
UserGroup.addUserToGroup(user.userId, groupName, done);
|
||||
}
|
||||
}
|
||||
|
||||
function activateUser(user) {
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.active);
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.active);
|
||||
}
|
||||
|
||||
function deactivateUser(user) {
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.inactive);
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.inactive);
|
||||
}
|
||||
|
||||
function disableUser(user) {
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.disabled);
|
||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||
return setAccountStatus(user, AccountStatus.disabled);
|
||||
}
|
||||
|
||||
function handleUserCommand() {
|
||||
function errUsage() {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
function errUsage() {
|
||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||
}
|
||||
|
||||
if(true === argv.help) {
|
||||
return errUsage();
|
||||
}
|
||||
if(true === argv.help) {
|
||||
return errUsage();
|
||||
}
|
||||
|
||||
const action = argv._[1];
|
||||
const usernameIdx = [ 'pass', 'passwd', 'password', 'group' ].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
||||
const userName = argv._[usernameIdx];
|
||||
const action = argv._[1];
|
||||
const usernameIdx = [ 'pass', 'passwd', 'password', 'group' ].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
||||
const userName = argv._[usernameIdx];
|
||||
|
||||
if(!userName) {
|
||||
return errUsage();
|
||||
}
|
||||
if(!userName) {
|
||||
return errUsage();
|
||||
}
|
||||
|
||||
initAndGetUser(userName, (err, user) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
return console.error(err.message);
|
||||
}
|
||||
initAndGetUser(userName, (err, user) => {
|
||||
if(err) {
|
||||
process.exitCode = ExitCodes.ERROR;
|
||||
return console.error(err.message);
|
||||
}
|
||||
|
||||
return ({
|
||||
pass : setUserPassword,
|
||||
passwd : setUserPassword,
|
||||
password : setUserPassword,
|
||||
return ({
|
||||
pass : setUserPassword,
|
||||
passwd : setUserPassword,
|
||||
password : setUserPassword,
|
||||
|
||||
rm : removeUser,
|
||||
remove : removeUser,
|
||||
del : removeUser,
|
||||
delete : removeUser,
|
||||
rm : removeUser,
|
||||
remove : removeUser,
|
||||
del : removeUser,
|
||||
delete : removeUser,
|
||||
|
||||
activate : activateUser,
|
||||
deactivate : deactivateUser,
|
||||
disable : disableUser,
|
||||
activate : activateUser,
|
||||
deactivate : deactivateUser,
|
||||
disable : disableUser,
|
||||
|
||||
group : modUserGroups,
|
||||
}[action] || errUsage)(user);
|
||||
});
|
||||
group : modUserGroups,
|
||||
}[action] || errUsage)(user);
|
||||
});
|
||||
}
|
|
@ -21,230 +21,230 @@ exports.getPredefinedMCIValue = getPredefinedMCIValue;
|
|||
exports.init = init;
|
||||
|
||||
function init(cb) {
|
||||
setNextRandomRumor(cb);
|
||||
setNextRandomRumor(cb);
|
||||
}
|
||||
|
||||
function setNextRandomRumor(cb) {
|
||||
StatLog.getSystemLogEntries('system_rumorz', StatLog.Order.Random, 1, (err, entry) => {
|
||||
if(entry) {
|
||||
entry = entry[0];
|
||||
}
|
||||
const randRumor = entry && entry.log_value ? entry.log_value : '';
|
||||
StatLog.setNonPeristentSystemStat('random_rumor', randRumor);
|
||||
if(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
StatLog.getSystemLogEntries('system_rumorz', StatLog.Order.Random, 1, (err, entry) => {
|
||||
if(entry) {
|
||||
entry = entry[0];
|
||||
}
|
||||
const randRumor = entry && entry.log_value ? entry.log_value : '';
|
||||
StatLog.setNonPeristentSystemStat('random_rumor', randRumor);
|
||||
if(cb) {
|
||||
return cb(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getUserRatio(client, propA, propB) {
|
||||
const a = StatLog.getUserStatNum(client.user, propA);
|
||||
const b = StatLog.getUserStatNum(client.user, propB);
|
||||
const ratio = ~~((a / b) * 100);
|
||||
return `${ratio}%`;
|
||||
const a = StatLog.getUserStatNum(client.user, propA);
|
||||
const b = StatLog.getUserStatNum(client.user, propB);
|
||||
const ratio = ~~((a / b) * 100);
|
||||
return `${ratio}%`;
|
||||
}
|
||||
|
||||
function userStatAsString(client, statName, defaultValue) {
|
||||
return (StatLog.getUserStat(client.user, statName) || defaultValue).toLocaleString();
|
||||
return (StatLog.getUserStat(client.user, statName) || defaultValue).toLocaleString();
|
||||
}
|
||||
|
||||
function sysStatAsString(statName, defaultValue) {
|
||||
return (StatLog.getSystemStat(statName) || defaultValue).toLocaleString();
|
||||
return (StatLog.getSystemStat(statName) || defaultValue).toLocaleString();
|
||||
}
|
||||
|
||||
const PREDEFINED_MCI_GENERATORS = {
|
||||
//
|
||||
// Board
|
||||
//
|
||||
BN : function boardName() { return Config().general.boardName; },
|
||||
//
|
||||
// Board
|
||||
//
|
||||
BN : function boardName() { return Config().general.boardName; },
|
||||
|
||||
// ENiGMA
|
||||
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
|
||||
VN : function version() { return packageJson.version; },
|
||||
// ENiGMA
|
||||
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
|
||||
VN : function version() { return packageJson.version; },
|
||||
|
||||
// +op info
|
||||
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
|
||||
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
|
||||
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
|
||||
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
|
||||
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
|
||||
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
|
||||
// :TODO: op age, web, ?????
|
||||
// +op info
|
||||
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
|
||||
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
|
||||
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
|
||||
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
|
||||
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
|
||||
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
|
||||
// :TODO: op age, web, ?????
|
||||
|
||||
//
|
||||
// Current user / session
|
||||
//
|
||||
UN : function userName(client) { return client.user.username; },
|
||||
UI : function userId(client) { return client.user.userId.toString(); },
|
||||
UG : function groups(client) { return _.values(client.user.groups).join(', '); },
|
||||
UR : function realName(client) { return userStatAsString(client, 'real_name', ''); },
|
||||
LO : function location(client) { return userStatAsString(client, 'location', ''); },
|
||||
UA : function age(client) { return client.user.getAge().toString(); },
|
||||
BD : function birthdate(client) { return moment(client.user.properties.birthdate).format(client.currentTheme.helpers.getDateFormat()); }, // iNiQUiTY
|
||||
US : function sex(client) { return userStatAsString(client, 'sex', ''); },
|
||||
UE : function emailAddres(client) { return userStatAsString(client, 'email_address', ''); },
|
||||
UW : function webAddress(client) { return userStatAsString(client, 'web_address', ''); },
|
||||
UF : function affils(client) { return userStatAsString(client, 'affiliation', ''); },
|
||||
UT : function themeId(client) { return userStatAsString(client, 'theme_id', ''); },
|
||||
UC : function loginCount(client) { return userStatAsString(client, 'login_count', 0); },
|
||||
ND : function connectedNode(client) { return client.node.toString(); },
|
||||
IP : function clientIpAddress(client) { return client.remoteAddress.replace(/^::ffff:/, ''); }, // convert any :ffff: IPv4's to 32bit version
|
||||
ST : function serverName(client) { return client.session.serverName; },
|
||||
FN : function activeFileBaseFilterName(client) {
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
||||
return activeFilter ? activeFilter.name : '';
|
||||
},
|
||||
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
||||
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
||||
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
UP : function userNumUploads(client) { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
||||
UK : function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes
|
||||
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
NR : function userUpDownRatio(client) { // Obv/2
|
||||
return getUserRatio(client, 'ul_total_count', 'dl_total_count');
|
||||
},
|
||||
KR : function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio
|
||||
return getUserRatio(client, 'ul_total_bytes', 'dl_total_bytes');
|
||||
},
|
||||
//
|
||||
// Current user / session
|
||||
//
|
||||
UN : function userName(client) { return client.user.username; },
|
||||
UI : function userId(client) { return client.user.userId.toString(); },
|
||||
UG : function groups(client) { return _.values(client.user.groups).join(', '); },
|
||||
UR : function realName(client) { return userStatAsString(client, 'real_name', ''); },
|
||||
LO : function location(client) { return userStatAsString(client, 'location', ''); },
|
||||
UA : function age(client) { return client.user.getAge().toString(); },
|
||||
BD : function birthdate(client) { return moment(client.user.properties.birthdate).format(client.currentTheme.helpers.getDateFormat()); }, // iNiQUiTY
|
||||
US : function sex(client) { return userStatAsString(client, 'sex', ''); },
|
||||
UE : function emailAddres(client) { return userStatAsString(client, 'email_address', ''); },
|
||||
UW : function webAddress(client) { return userStatAsString(client, 'web_address', ''); },
|
||||
UF : function affils(client) { return userStatAsString(client, 'affiliation', ''); },
|
||||
UT : function themeId(client) { return userStatAsString(client, 'theme_id', ''); },
|
||||
UC : function loginCount(client) { return userStatAsString(client, 'login_count', 0); },
|
||||
ND : function connectedNode(client) { return client.node.toString(); },
|
||||
IP : function clientIpAddress(client) { return client.remoteAddress.replace(/^::ffff:/, ''); }, // convert any :ffff: IPv4's to 32bit version
|
||||
ST : function serverName(client) { return client.session.serverName; },
|
||||
FN : function activeFileBaseFilterName(client) {
|
||||
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
||||
return activeFilter ? activeFilter.name : '';
|
||||
},
|
||||
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
||||
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
||||
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
UP : function userNumUploads(client) { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
||||
UK : function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes
|
||||
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
NR : function userUpDownRatio(client) { // Obv/2
|
||||
return getUserRatio(client, 'ul_total_count', 'dl_total_count');
|
||||
},
|
||||
KR : function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio
|
||||
return getUserRatio(client, 'ul_total_bytes', 'dl_total_bytes');
|
||||
},
|
||||
|
||||
MS : function accountCreatedclient(client) { return moment(client.user.properties.account_created).format(client.currentTheme.helpers.getDateFormat()); },
|
||||
PS : function userPostCount(client) { return userStatAsString(client, 'post_count', 0); },
|
||||
PC : function userPostCallRatio(client) { return getUserRatio(client, 'post_count', 'login_count'); },
|
||||
MS : function accountCreatedclient(client) { return moment(client.user.properties.account_created).format(client.currentTheme.helpers.getDateFormat()); },
|
||||
PS : function userPostCount(client) { return userStatAsString(client, 'post_count', 0); },
|
||||
PC : function userPostCallRatio(client) { return getUserRatio(client, 'post_count', 'login_count'); },
|
||||
|
||||
MD : function currentMenuDescription(client) {
|
||||
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
||||
},
|
||||
MD : function currentMenuDescription(client) {
|
||||
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
||||
},
|
||||
|
||||
MA : function messageAreaName(client) {
|
||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||
return area ? area.name : '';
|
||||
},
|
||||
MC : function messageConfName(client) {
|
||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||
return conf ? conf.name : '';
|
||||
},
|
||||
ML : function messageAreaDescription(client) {
|
||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||
return area ? area.desc : '';
|
||||
},
|
||||
CM : function messageConfDescription(client) {
|
||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||
return conf ? conf.desc : '';
|
||||
},
|
||||
MA : function messageAreaName(client) {
|
||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||
return area ? area.name : '';
|
||||
},
|
||||
MC : function messageConfName(client) {
|
||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||
return conf ? conf.name : '';
|
||||
},
|
||||
ML : function messageAreaDescription(client) {
|
||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||
return area ? area.desc : '';
|
||||
},
|
||||
CM : function messageConfDescription(client) {
|
||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||
return conf ? conf.desc : '';
|
||||
},
|
||||
|
||||
SH : function termHeight(client) { return client.term.termHeight.toString(); },
|
||||
SW : function termWidth(client) { return client.term.termWidth.toString(); },
|
||||
SH : function termHeight(client) { return client.term.termHeight.toString(); },
|
||||
SW : function termWidth(client) { return client.term.termWidth.toString(); },
|
||||
|
||||
//
|
||||
// Date/Time
|
||||
//
|
||||
// :TODO: change to CD for 'Current Date'
|
||||
DT : function date(client) { return moment().format(client.currentTheme.helpers.getDateFormat()); },
|
||||
CT : function time(client) { return moment().format(client.currentTheme.helpers.getTimeFormat()) ;},
|
||||
//
|
||||
// Date/Time
|
||||
//
|
||||
// :TODO: change to CD for 'Current Date'
|
||||
DT : function date(client) { return moment().format(client.currentTheme.helpers.getDateFormat()); },
|
||||
CT : function time(client) { return moment().format(client.currentTheme.helpers.getTimeFormat()) ;},
|
||||
|
||||
//
|
||||
// OS/System Info
|
||||
//
|
||||
OS : function operatingSystem() {
|
||||
return {
|
||||
linux : 'Linux',
|
||||
darwin : 'Mac OS X',
|
||||
win32 : 'Windows',
|
||||
sunos : 'SunOS',
|
||||
freebsd : 'FreeBSD',
|
||||
}[os.platform()] || os.type();
|
||||
},
|
||||
//
|
||||
// OS/System Info
|
||||
//
|
||||
OS : function operatingSystem() {
|
||||
return {
|
||||
linux : 'Linux',
|
||||
darwin : 'Mac OS X',
|
||||
win32 : 'Windows',
|
||||
sunos : 'SunOS',
|
||||
freebsd : 'FreeBSD',
|
||||
}[os.platform()] || os.type();
|
||||
},
|
||||
|
||||
OA : function systemArchitecture() { return os.arch(); },
|
||||
OA : function systemArchitecture() { return os.arch(); },
|
||||
|
||||
SC : function systemCpuModel() {
|
||||
//
|
||||
// Clean up CPU strings a bit for better display
|
||||
//
|
||||
return os.cpus()[0].model
|
||||
.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
||||
.replace(/\s+(?= )/g, '');
|
||||
},
|
||||
SC : function systemCpuModel() {
|
||||
//
|
||||
// Clean up CPU strings a bit for better display
|
||||
//
|
||||
return os.cpus()[0].model
|
||||
.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
||||
.replace(/\s+(?= )/g, '');
|
||||
},
|
||||
|
||||
// :TODO: MCI for core count, e.g. os.cpus().length
|
||||
// :TODO: MCI for core count, e.g. os.cpus().length
|
||||
|
||||
// :TODO: cpu load average (over N seconds): http://stackoverflow.com/questions/9565912/convert-the-output-of-os-cpus-in-node-js-to-percentage
|
||||
NV : function nodeVersion() { return process.version; },
|
||||
// :TODO: cpu load average (over N seconds): http://stackoverflow.com/questions/9565912/convert-the-output-of-os-cpus-in-node-js-to-percentage
|
||||
NV : function nodeVersion() { return process.version; },
|
||||
|
||||
AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); },
|
||||
AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); },
|
||||
|
||||
TC : function totalCalls() { return StatLog.getSystemStat('login_count').toLocaleString(); },
|
||||
TC : function totalCalls() { return StatLog.getSystemStat('login_count').toLocaleString(); },
|
||||
|
||||
RR : function randomRumor() {
|
||||
// start the process of picking another random one
|
||||
setNextRandomRumor();
|
||||
RR : function randomRumor() {
|
||||
// start the process of picking another random one
|
||||
setNextRandomRumor();
|
||||
|
||||
return StatLog.getSystemStat('random_rumor');
|
||||
},
|
||||
return StatLog.getSystemStat('random_rumor');
|
||||
},
|
||||
|
||||
//
|
||||
// System File Base, Up/Download Info
|
||||
//
|
||||
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
||||
//
|
||||
SD : function systemNumDownloads() { return sysStatAsString('dl_total_count', 0); },
|
||||
SO : function systemByteDownload() {
|
||||
const byteSize = StatLog.getSystemStatNum('dl_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
SU : function systemNumUploads() { return sysStatAsString('ul_total_count', 0); },
|
||||
SP : function systemByteUpload() {
|
||||
const byteSize = StatLog.getSystemStatNum('ul_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
TF : function totalFilesOnSystem() {
|
||||
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
||||
return _.get(areaStats, 'totalFiles', 0).toLocaleString();
|
||||
},
|
||||
TB : function totalBytesOnSystem() {
|
||||
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
||||
const totalBytes = parseInt(_.get(areaStats, 'totalBytes', 0));
|
||||
return formatByteSize(totalBytes, true); // true=withAbbr
|
||||
},
|
||||
//
|
||||
// System File Base, Up/Download Info
|
||||
//
|
||||
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
||||
//
|
||||
SD : function systemNumDownloads() { return sysStatAsString('dl_total_count', 0); },
|
||||
SO : function systemByteDownload() {
|
||||
const byteSize = StatLog.getSystemStatNum('dl_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
SU : function systemNumUploads() { return sysStatAsString('ul_total_count', 0); },
|
||||
SP : function systemByteUpload() {
|
||||
const byteSize = StatLog.getSystemStatNum('ul_total_bytes');
|
||||
return formatByteSize(byteSize, true); // true=withAbbr
|
||||
},
|
||||
TF : function totalFilesOnSystem() {
|
||||
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
||||
return _.get(areaStats, 'totalFiles', 0).toLocaleString();
|
||||
},
|
||||
TB : function totalBytesOnSystem() {
|
||||
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
||||
const totalBytes = parseInt(_.get(areaStats, 'totalBytes', 0));
|
||||
return formatByteSize(totalBytes, true); // true=withAbbr
|
||||
},
|
||||
|
||||
// :TODO: PT - Messages posted *today* (Obv/2)
|
||||
// -> Include FTN/etc.
|
||||
// :TODO: NT - New users today (Obv/2)
|
||||
// :TODO: CT - Calls *today* (Obv/2)
|
||||
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
||||
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
||||
// :TODO: TP - total message/posts on the system (Obv/2)
|
||||
// -> Include FTN/etc.
|
||||
// :TODO: LC - name of last caller to system (Obv/2)
|
||||
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
||||
// :TODO: PT - Messages posted *today* (Obv/2)
|
||||
// -> Include FTN/etc.
|
||||
// :TODO: NT - New users today (Obv/2)
|
||||
// :TODO: CT - Calls *today* (Obv/2)
|
||||
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
||||
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
||||
// :TODO: TP - total message/posts on the system (Obv/2)
|
||||
// -> Include FTN/etc.
|
||||
// :TODO: LC - name of last caller to system (Obv/2)
|
||||
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
||||
|
||||
|
||||
//
|
||||
// Special handling for XY
|
||||
//
|
||||
XY : function xyHack() { return; /* nothing */ },
|
||||
//
|
||||
// Special handling for XY
|
||||
//
|
||||
XY : function xyHack() { return; /* nothing */ },
|
||||
};
|
||||
|
||||
function getPredefinedMCIValue(client, code) {
|
||||
|
||||
if(!client || !code) {
|
||||
return;
|
||||
}
|
||||
if(!client || !code) {
|
||||
return;
|
||||
}
|
||||
|
||||
const generator = PREDEFINED_MCI_GENERATORS[code];
|
||||
const generator = PREDEFINED_MCI_GENERATORS[code];
|
||||
|
||||
if(generator) {
|
||||
let value;
|
||||
try {
|
||||
value = generator(client);
|
||||
} catch(e) {
|
||||
Log.error( { code : code, exception : e.message }, 'Exception caught generating predefined MCI value' );
|
||||
}
|
||||
if(generator) {
|
||||
let value;
|
||||
try {
|
||||
value = generator(client);
|
||||
} catch(e) {
|
||||
Log.error( { code : code, exception : e.message }, 'Exception caught generating predefined MCI value' );
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
|
390
core/rumorz.js
390
core/rumorz.js
|
@ -15,233 +15,233 @@ const async = require('async');
|
|||
const _ = require('lodash');
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Rumorz',
|
||||
desc : 'Standard local rumorz',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.rumorz',
|
||||
name : 'Rumorz',
|
||||
desc : 'Standard local rumorz',
|
||||
author : 'NuSkooler',
|
||||
packageName : 'codes.l33t.enigma.rumorz',
|
||||
};
|
||||
|
||||
const STATLOG_KEY_RUMORZ = 'system_rumorz';
|
||||
|
||||
const FormIds = {
|
||||
View : 0,
|
||||
Add : 1,
|
||||
View : 0,
|
||||
Add : 1,
|
||||
};
|
||||
|
||||
const MciCodeIds = {
|
||||
ViewForm : {
|
||||
Entries : 1,
|
||||
AddPrompt : 2,
|
||||
},
|
||||
AddForm : {
|
||||
NewEntry : 1,
|
||||
EntryPreview : 2,
|
||||
AddPrompt : 3,
|
||||
}
|
||||
ViewForm : {
|
||||
Entries : 1,
|
||||
AddPrompt : 2,
|
||||
},
|
||||
AddForm : {
|
||||
NewEntry : 1,
|
||||
EntryPreview : 2,
|
||||
AddPrompt : 3,
|
||||
}
|
||||
};
|
||||
|
||||
exports.getModule = class RumorzModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.menuMethods = {
|
||||
viewAddScreen : (formData, extraArgs, cb) => {
|
||||
return this.displayAddScreen(cb);
|
||||
},
|
||||
this.menuMethods = {
|
||||
viewAddScreen : (formData, extraArgs, cb) => {
|
||||
return this.displayAddScreen(cb);
|
||||
},
|
||||
|
||||
addEntry : (formData, extraArgs, cb) => {
|
||||
if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) {
|
||||
const rumor = formData.value.rumor.trim(); // remove any trailing ws
|
||||
addEntry : (formData, extraArgs, cb) => {
|
||||
if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) {
|
||||
const rumor = formData.value.rumor.trim(); // remove any trailing ws
|
||||
|
||||
StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, StatLog.KeepType.Forever, () => {
|
||||
this.clearAddForm();
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
} else {
|
||||
// empty message - treat as if cancel was hit
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
},
|
||||
StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, StatLog.KeepType.Forever, () => {
|
||||
this.clearAddForm();
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
});
|
||||
} else {
|
||||
// empty message - treat as if cancel was hit
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
},
|
||||
|
||||
cancelAdd : (formData, extraArgs, cb) => {
|
||||
this.clearAddForm();
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
};
|
||||
}
|
||||
cancelAdd : (formData, extraArgs, cb) => {
|
||||
this.clearAddForm();
|
||||
return this.displayViewScreen(true, cb); // true=cls
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get config() { return this.menuConfig.config; }
|
||||
get config() { return this.menuConfig.config; }
|
||||
|
||||
clearAddForm() {
|
||||
const newEntryView = this.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||
const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||
clearAddForm() {
|
||||
const newEntryView = this.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||
const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||
|
||||
newEntryView.setText('');
|
||||
newEntryView.setText('');
|
||||
|
||||
// preview is optional
|
||||
if(previewView) {
|
||||
previewView.setText('');
|
||||
}
|
||||
}
|
||||
// preview is optional
|
||||
if(previewView) {
|
||||
previewView.setText('');
|
||||
}
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
self.displayViewScreen(false, callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||
}
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
async.series(
|
||||
[
|
||||
function beforeDisplayArt(callback) {
|
||||
self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
self.displayViewScreen(false, callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(err) {
|
||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||
}
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayViewScreen(clearScreen, cb) {
|
||||
const self = this;
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if(self.viewControllers.add) {
|
||||
self.viewControllers.add.setFocus(false);
|
||||
}
|
||||
displayViewScreen(clearScreen, cb) {
|
||||
const self = this;
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if(self.viewControllers.add) {
|
||||
self.viewControllers.add.setFocus(false);
|
||||
}
|
||||
|
||||
if(clearScreen) {
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
}
|
||||
if(clearScreen) {
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
}
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.config.art.entries,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
theme.displayThemedAsset(
|
||||
self.config.art.entries,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'view',
|
||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.View,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.View,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries);
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function fetchEntries(callback) {
|
||||
const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries);
|
||||
|
||||
StatLog.getSystemLogEntries(STATLOG_KEY_RUMORZ, StatLog.Order.Timestamp, (err, entries) => {
|
||||
return callback(err, entriesView, entries);
|
||||
});
|
||||
},
|
||||
function populateEntries(entriesView, entries, callback) {
|
||||
const config = self.config;
|
||||
const listFormat = config.listFormat || '{rumor}';
|
||||
const focusListFormat = config.focusListFormat || listFormat;
|
||||
StatLog.getSystemLogEntries(STATLOG_KEY_RUMORZ, StatLog.Order.Timestamp, (err, entries) => {
|
||||
return callback(err, entriesView, entries);
|
||||
});
|
||||
},
|
||||
function populateEntries(entriesView, entries, callback) {
|
||||
const config = self.config;
|
||||
const listFormat = config.listFormat || '{rumor}';
|
||||
const focusListFormat = config.focusListFormat || listFormat;
|
||||
|
||||
entriesView.setItems(entries.map( e => stringFormat(listFormat, { rumor : e.log_value } ) ) );
|
||||
entriesView.setFocusItems(entries.map(e => stringFormat(focusListFormat, { rumor : e.log_value } ) ) );
|
||||
entriesView.redraw();
|
||||
entriesView.setItems(entries.map( e => stringFormat(listFormat, { rumor : e.log_value } ) ) );
|
||||
entriesView.setFocusItems(entries.map(e => stringFormat(focusListFormat, { rumor : e.log_value } ) ) );
|
||||
entriesView.redraw();
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function finalPrep(callback) {
|
||||
const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt);
|
||||
promptView.setFocusItemIndex(1); // default to NO
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return callback(null);
|
||||
},
|
||||
function finalPrep(callback) {
|
||||
const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt);
|
||||
promptView.setFocusItemIndex(1); // default to NO
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayAddScreen(cb) {
|
||||
const self = this;
|
||||
displayAddScreen(cb) {
|
||||
const self = this;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
self.viewControllers.view.setFocus(false);
|
||||
self.client.term.rawWrite(resetScreen());
|
||||
|
||||
theme.displayThemedAsset(
|
||||
self.config.art.add,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
theme.displayThemedAsset(
|
||||
self.config.art.add,
|
||||
self.client,
|
||||
{ font : self.menuConfig.font },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function initOrRedrawViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.add)) {
|
||||
const vc = self.addViewController(
|
||||
'add',
|
||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.Add,
|
||||
};
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.Add,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry);
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function initPreviewUpdates(callback) {
|
||||
const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||
if(previewView) {
|
||||
let timerId;
|
||||
entryView.on('key press', () => {
|
||||
clearTimeout(timerId);
|
||||
timerId = setTimeout( () => {
|
||||
const focused = self.viewControllers.add.getFocusedView();
|
||||
if(focused === entryView) {
|
||||
previewView.setText(entryView.getData());
|
||||
focused.setFocus(true);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
} else {
|
||||
self.viewControllers.add.setFocus(true);
|
||||
self.viewControllers.add.redrawAll();
|
||||
self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry);
|
||||
return callback(null);
|
||||
}
|
||||
},
|
||||
function initPreviewUpdates(callback) {
|
||||
const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||
if(previewView) {
|
||||
let timerId;
|
||||
entryView.on('key press', () => {
|
||||
clearTimeout(timerId);
|
||||
timerId = setTimeout( () => {
|
||||
const focused = self.viewControllers.add.getFocusedView();
|
||||
if(focused === entryView) {
|
||||
previewView.setText(entryView.getData());
|
||||
focused.setFocus(true);
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
return callback(null);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
230
core/sauce.js
230
core/sauce.js
|
@ -27,100 +27,100 @@ exports.SAUCE_SIZE = SAUCE_SIZE;
|
|||
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
||||
|
||||
function readSAUCE(data, cb) {
|
||||
if(data.length < SAUCE_SIZE) {
|
||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||
}
|
||||
if(data.length < SAUCE_SIZE) {
|
||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||
}
|
||||
|
||||
let sauceRec;
|
||||
try {
|
||||
sauceRec = new Parser()
|
||||
.buffer('id', { length : 5 } )
|
||||
.buffer('version', { length : 2 } )
|
||||
.buffer('title', { length: 35 } )
|
||||
.buffer('author', { length : 20 } )
|
||||
.buffer('group', { length: 20 } )
|
||||
.buffer('date', { length: 8 } )
|
||||
.uint32le('fileSize')
|
||||
.int8('dataType')
|
||||
.int8('fileType')
|
||||
.uint16le('tinfo1')
|
||||
.uint16le('tinfo2')
|
||||
.uint16le('tinfo3')
|
||||
.uint16le('tinfo4')
|
||||
.int8('numComments')
|
||||
.int8('flags')
|
||||
// :TODO: does this need to be optional?
|
||||
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
|
||||
.parse(data.slice(data.length - SAUCE_SIZE));
|
||||
} catch(e) {
|
||||
return cb(Errors.Invalid('Invalid SAUCE record'));
|
||||
}
|
||||
let sauceRec;
|
||||
try {
|
||||
sauceRec = new Parser()
|
||||
.buffer('id', { length : 5 } )
|
||||
.buffer('version', { length : 2 } )
|
||||
.buffer('title', { length: 35 } )
|
||||
.buffer('author', { length : 20 } )
|
||||
.buffer('group', { length: 20 } )
|
||||
.buffer('date', { length: 8 } )
|
||||
.uint32le('fileSize')
|
||||
.int8('dataType')
|
||||
.int8('fileType')
|
||||
.uint16le('tinfo1')
|
||||
.uint16le('tinfo2')
|
||||
.uint16le('tinfo3')
|
||||
.uint16le('tinfo4')
|
||||
.int8('numComments')
|
||||
.int8('flags')
|
||||
// :TODO: does this need to be optional?
|
||||
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
|
||||
.parse(data.slice(data.length - SAUCE_SIZE));
|
||||
} catch(e) {
|
||||
return cb(Errors.Invalid('Invalid SAUCE record'));
|
||||
}
|
||||
|
||||
|
||||
if(!SAUCE_ID.equals(sauceRec.id)) {
|
||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||
}
|
||||
if(!SAUCE_ID.equals(sauceRec.id)) {
|
||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||
}
|
||||
|
||||
const ver = iconv.decode(sauceRec.version, 'cp437');
|
||||
const ver = iconv.decode(sauceRec.version, 'cp437');
|
||||
|
||||
if('00' !== ver) {
|
||||
return cb(Errors.Invalid(`Unsupported SAUCE version: ${ver}`));
|
||||
}
|
||||
if('00' !== ver) {
|
||||
return cb(Errors.Invalid(`Unsupported SAUCE version: ${ver}`));
|
||||
}
|
||||
|
||||
if(-1 === SAUCE_VALID_DATA_TYPES.indexOf(sauceRec.dataType)) {
|
||||
return cb(Errors.Invalid(`Unsupported SAUCE DataType: ${sauceRec.dataType}`));
|
||||
}
|
||||
if(-1 === SAUCE_VALID_DATA_TYPES.indexOf(sauceRec.dataType)) {
|
||||
return cb(Errors.Invalid(`Unsupported SAUCE DataType: ${sauceRec.dataType}`));
|
||||
}
|
||||
|
||||
const sauce = {
|
||||
id : iconv.decode(sauceRec.id, 'cp437'),
|
||||
version : iconv.decode(sauceRec.version, 'cp437').trim(),
|
||||
title : iconv.decode(sauceRec.title, 'cp437').trim(),
|
||||
author : iconv.decode(sauceRec.author, 'cp437').trim(),
|
||||
group : iconv.decode(sauceRec.group, 'cp437').trim(),
|
||||
date : iconv.decode(sauceRec.date, 'cp437').trim(),
|
||||
fileSize : sauceRec.fileSize,
|
||||
dataType : sauceRec.dataType,
|
||||
fileType : sauceRec.fileType,
|
||||
tinfo1 : sauceRec.tinfo1,
|
||||
tinfo2 : sauceRec.tinfo2,
|
||||
tinfo3 : sauceRec.tinfo3,
|
||||
tinfo4 : sauceRec.tinfo4,
|
||||
numComments : sauceRec.numComments,
|
||||
flags : sauceRec.flags,
|
||||
tinfos : sauceRec.tinfos,
|
||||
};
|
||||
const sauce = {
|
||||
id : iconv.decode(sauceRec.id, 'cp437'),
|
||||
version : iconv.decode(sauceRec.version, 'cp437').trim(),
|
||||
title : iconv.decode(sauceRec.title, 'cp437').trim(),
|
||||
author : iconv.decode(sauceRec.author, 'cp437').trim(),
|
||||
group : iconv.decode(sauceRec.group, 'cp437').trim(),
|
||||
date : iconv.decode(sauceRec.date, 'cp437').trim(),
|
||||
fileSize : sauceRec.fileSize,
|
||||
dataType : sauceRec.dataType,
|
||||
fileType : sauceRec.fileType,
|
||||
tinfo1 : sauceRec.tinfo1,
|
||||
tinfo2 : sauceRec.tinfo2,
|
||||
tinfo3 : sauceRec.tinfo3,
|
||||
tinfo4 : sauceRec.tinfo4,
|
||||
numComments : sauceRec.numComments,
|
||||
flags : sauceRec.flags,
|
||||
tinfos : sauceRec.tinfos,
|
||||
};
|
||||
|
||||
const dt = SAUCE_DATA_TYPES[sauce.dataType];
|
||||
if(dt && dt.parser) {
|
||||
sauce[dt.name] = dt.parser(sauce);
|
||||
}
|
||||
const dt = SAUCE_DATA_TYPES[sauce.dataType];
|
||||
if(dt && dt.parser) {
|
||||
sauce[dt.name] = dt.parser(sauce);
|
||||
}
|
||||
|
||||
return cb(null, sauce);
|
||||
return cb(null, sauce);
|
||||
}
|
||||
|
||||
// :TODO: These need completed:
|
||||
const SAUCE_DATA_TYPES = {
|
||||
0 : { name : 'None' },
|
||||
1 : { name : 'Character', parser : parseCharacterSAUCE },
|
||||
2 : 'Bitmap',
|
||||
3 : 'Vector',
|
||||
4 : 'Audio',
|
||||
5 : 'BinaryText',
|
||||
6 : 'XBin',
|
||||
7 : 'Archive',
|
||||
8 : 'Executable',
|
||||
0 : { name : 'None' },
|
||||
1 : { name : 'Character', parser : parseCharacterSAUCE },
|
||||
2 : 'Bitmap',
|
||||
3 : 'Vector',
|
||||
4 : 'Audio',
|
||||
5 : 'BinaryText',
|
||||
6 : 'XBin',
|
||||
7 : 'Archive',
|
||||
8 : 'Executable',
|
||||
};
|
||||
|
||||
const SAUCE_CHARACTER_FILE_TYPES = {
|
||||
0 : 'ASCII',
|
||||
1 : 'ANSi',
|
||||
2 : 'ANSiMation',
|
||||
3 : 'RIP script',
|
||||
4 : 'PCBoard',
|
||||
5 : 'Avatar',
|
||||
6 : 'HTML',
|
||||
7 : 'Source',
|
||||
8 : 'TundraDraw',
|
||||
0 : 'ASCII',
|
||||
1 : 'ANSi',
|
||||
2 : 'ANSiMation',
|
||||
3 : 'RIP script',
|
||||
4 : 'PCBoard',
|
||||
5 : 'Avatar',
|
||||
6 : 'HTML',
|
||||
7 : 'Source',
|
||||
8 : 'TundraDraw',
|
||||
};
|
||||
|
||||
//
|
||||
|
@ -129,53 +129,53 @@ const SAUCE_CHARACTER_FILE_TYPES = {
|
|||
// Note that this is the same mapping that x84 uses. Be compatible!
|
||||
//
|
||||
const SAUCE_FONT_TO_ENCODING_HINT = {
|
||||
'Amiga MicroKnight' : 'amiga',
|
||||
'Amiga MicroKnight+' : 'amiga',
|
||||
'Amiga mOsOul' : 'amiga',
|
||||
'Amiga P0T-NOoDLE' : 'amiga',
|
||||
'Amiga Topaz 1' : 'amiga',
|
||||
'Amiga Topaz 1+' : 'amiga',
|
||||
'Amiga Topaz 2' : 'amiga',
|
||||
'Amiga Topaz 2+' : 'amiga',
|
||||
'Atari ATASCII' : 'atari',
|
||||
'IBM EGA43' : 'cp437',
|
||||
'IBM EGA' : 'cp437',
|
||||
'IBM VGA25G' : 'cp437',
|
||||
'IBM VGA50' : 'cp437',
|
||||
'IBM VGA' : 'cp437',
|
||||
'Amiga MicroKnight' : 'amiga',
|
||||
'Amiga MicroKnight+' : 'amiga',
|
||||
'Amiga mOsOul' : 'amiga',
|
||||
'Amiga P0T-NOoDLE' : 'amiga',
|
||||
'Amiga Topaz 1' : 'amiga',
|
||||
'Amiga Topaz 1+' : 'amiga',
|
||||
'Amiga Topaz 2' : 'amiga',
|
||||
'Amiga Topaz 2+' : 'amiga',
|
||||
'Atari ATASCII' : 'atari',
|
||||
'IBM EGA43' : 'cp437',
|
||||
'IBM EGA' : 'cp437',
|
||||
'IBM VGA25G' : 'cp437',
|
||||
'IBM VGA50' : 'cp437',
|
||||
'IBM VGA' : 'cp437',
|
||||
};
|
||||
|
||||
[
|
||||
'437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
||||
'860', '861', '862', '863', '864', '865', '866', '869', '872'
|
||||
'437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
||||
'860', '861', '862', '863', '864', '865', '866', '869', '872'
|
||||
].forEach( page => {
|
||||
const codec = 'cp' + page;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA25g ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA50 ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA ' + page] = codec;
|
||||
const codec = 'cp' + page;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA25g ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA50 ' + page] = codec;
|
||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA ' + page] = codec;
|
||||
});
|
||||
|
||||
function parseCharacterSAUCE(sauce) {
|
||||
const result = {};
|
||||
const result = {};
|
||||
|
||||
result.fileType = SAUCE_CHARACTER_FILE_TYPES[sauce.fileType] || 'Unknown';
|
||||
result.fileType = SAUCE_CHARACTER_FILE_TYPES[sauce.fileType] || 'Unknown';
|
||||
|
||||
if(sauce.fileType === 0 || sauce.fileType === 1 || sauce.fileType === 2) {
|
||||
// convience: create ansiFlags
|
||||
sauce.ansiFlags = sauce.flags;
|
||||
if(sauce.fileType === 0 || sauce.fileType === 1 || sauce.fileType === 2) {
|
||||
// convience: create ansiFlags
|
||||
sauce.ansiFlags = sauce.flags;
|
||||
|
||||
let i = 0;
|
||||
while(i < sauce.tinfos.length && sauce.tinfos[i] !== 0x00) {
|
||||
++i;
|
||||
}
|
||||
let i = 0;
|
||||
while(i < sauce.tinfos.length && sauce.tinfos[i] !== 0x00) {
|
||||
++i;
|
||||
}
|
||||
|
||||
const fontName = iconv.decode(sauce.tinfos.slice(0, i), 'cp437');
|
||||
if(fontName.length > 0) {
|
||||
result.fontName = fontName;
|
||||
}
|
||||
}
|
||||
const fontName = iconv.decode(sauce.tinfos.slice(0, i), 'cp437');
|
||||
if(fontName.length > 0) {
|
||||
result.fontName = fontName;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue