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 = {};
|
const activeDoorNodeInstances = {};
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Abracadabra',
|
name : 'Abracadabra',
|
||||||
desc : 'External BBS Door Module',
|
desc : 'External BBS Door Module',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -60,138 +60,138 @@ exports.moduleInfo = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.getModule = class AbracadabraModule extends MenuModule {
|
exports.getModule = class AbracadabraModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.config = options.menuConfig.config;
|
this.config = options.menuConfig.config;
|
||||||
// :TODO: MenuModule.validateConfig(cb) -- validate config section gracefully instead of asserts! -- { key : type, key2 : type2, ... }
|
// :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.name, 'Config \'name\' is required'));
|
||||||
assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required'));
|
assert(_.isString(this.config.dropFileType, 'Config \'dropFileType\' is required'));
|
||||||
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required'));
|
assert(_.isString(this.config.cmd, 'Config \'cmd\' is required'));
|
||||||
|
|
||||||
this.config.nodeMax = this.config.nodeMax || 0;
|
this.config.nodeMax = this.config.nodeMax || 0;
|
||||||
this.config.args = this.config.args || [];
|
this.config.args = this.config.args || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
:TODO:
|
:TODO:
|
||||||
* disconnecting wile door is open leaves dosemu
|
* disconnecting wile door is open leaves dosemu
|
||||||
* http://bbslink.net/sysop.php support
|
* http://bbslink.net/sysop.php support
|
||||||
* Font support ala all other menus... or does this just work?
|
* Font support ala all other menus... or does this just work?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function validateNodeCount(callback) {
|
function validateNodeCount(callback) {
|
||||||
if(self.config.nodeMax > 0 &&
|
if(self.config.nodeMax > 0 &&
|
||||||
_.isNumber(activeDoorNodeInstances[self.config.name]) &&
|
_.isNumber(activeDoorNodeInstances[self.config.name]) &&
|
||||||
activeDoorNodeInstances[self.config.name] + 1 > self.config.nodeMax)
|
activeDoorNodeInstances[self.config.name] + 1 > self.config.nodeMax)
|
||||||
{
|
{
|
||||||
self.client.log.info(
|
self.client.log.info(
|
||||||
{
|
{
|
||||||
name : self.config.name,
|
name : self.config.name,
|
||||||
activeCount : activeDoorNodeInstances[self.config.name]
|
activeCount : activeDoorNodeInstances[self.config.name]
|
||||||
},
|
},
|
||||||
'Too many active instances');
|
'Too many active instances');
|
||||||
|
|
||||||
if(_.isString(self.config.tooManyArt)) {
|
if(_.isString(self.config.tooManyArt)) {
|
||||||
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
theme.displayThemeArt( { client : self.client, name : self.config.tooManyArt }, function displayed() {
|
||||||
self.pausePrompt( () => {
|
self.pausePrompt( () => {
|
||||||
callback(new Error('Too many active instances'));
|
callback(new Error('Too many active instances'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.client.term.write('\nToo many active instances. Try again later.\n');
|
self.client.term.write('\nToo many active instances. Try again later.\n');
|
||||||
|
|
||||||
// :TODO: Use MenuModule.pausePrompt()
|
// :TODO: Use MenuModule.pausePrompt()
|
||||||
self.pausePrompt( () => {
|
self.pausePrompt( () => {
|
||||||
callback(new Error('Too many active instances'));
|
callback(new Error('Too many active instances'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// :TODO: JS elegant way to do this?
|
// :TODO: JS elegant way to do this?
|
||||||
if(activeDoorNodeInstances[self.config.name]) {
|
if(activeDoorNodeInstances[self.config.name]) {
|
||||||
activeDoorNodeInstances[self.config.name] += 1;
|
activeDoorNodeInstances[self.config.name] += 1;
|
||||||
} else {
|
} else {
|
||||||
activeDoorNodeInstances[self.config.name] = 1;
|
activeDoorNodeInstances[self.config.name] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function generateDropfile(callback) {
|
function generateDropfile(callback) {
|
||||||
self.dropFile = new DropFile(self.client, self.config.dropFileType);
|
self.dropFile = new DropFile(self.client, self.config.dropFileType);
|
||||||
var fullPath = self.dropFile.fullPath;
|
var fullPath = self.dropFile.fullPath;
|
||||||
|
|
||||||
mkdirs(paths.dirname(fullPath), function dirCreated(err) {
|
mkdirs(paths.dirname(fullPath), function dirCreated(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
self.dropFile.createFile(function created(err) {
|
self.dropFile.createFile(function created(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.toString() }, 'Could not start door');
|
self.client.log.warn( { error : err.toString() }, 'Could not start door');
|
||||||
self.lastError = err;
|
self.lastError = err;
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
} else {
|
} else {
|
||||||
self.finishedLoading();
|
self.finishedLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
runDoor() {
|
runDoor() {
|
||||||
|
|
||||||
const exeInfo = {
|
const exeInfo = {
|
||||||
cmd : this.config.cmd,
|
cmd : this.config.cmd,
|
||||||
args : this.config.args,
|
args : this.config.args,
|
||||||
io : this.config.io || 'stdio',
|
io : this.config.io || 'stdio',
|
||||||
encoding : this.config.encoding || this.client.term.outputEncoding,
|
encoding : this.config.encoding || this.client.term.outputEncoding,
|
||||||
dropFile : this.dropFile.fileName,
|
dropFile : this.dropFile.fileName,
|
||||||
node : this.client.node,
|
node : this.client.node,
|
||||||
//inhSocket : this.client.output._handle.fd,
|
//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', () => {
|
doorInstance.once('finished', () => {
|
||||||
//
|
//
|
||||||
// Try to clean up various settings such as scroll regions that may
|
// Try to clean up various settings such as scroll regions that may
|
||||||
// have been set within the door
|
// have been set within the door
|
||||||
//
|
//
|
||||||
this.client.term.rawWrite(
|
this.client.term.rawWrite(
|
||||||
ansi.normal() +
|
ansi.normal() +
|
||||||
ansi.goto(this.client.term.termHeight, this.client.term.termWidth) +
|
ansi.goto(this.client.term.termHeight, this.client.term.termWidth) +
|
||||||
ansi.setScrollRegion() +
|
ansi.setScrollRegion() +
|
||||||
ansi.goto(this.client.term.termHeight, 0) +
|
ansi.goto(this.client.term.termHeight, 0) +
|
||||||
'\r\n\r\n'
|
'\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() {
|
leave() {
|
||||||
super.leave();
|
super.leave();
|
||||||
if(!this.lastError) {
|
if(!this.lastError) {
|
||||||
activeDoorNodeInstances[this.config.name] -= 1;
|
activeDoorNodeInstances[this.config.name] -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedLoading() {
|
finishedLoading() {
|
||||||
this.runDoor();
|
this.runDoor();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
124
core/acs.js
124
core/acs.js
|
@ -10,81 +10,81 @@ const assert = require('assert');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
class ACS {
|
class ACS {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
check(acs, scope, defaultAcs) {
|
check(acs, scope, defaultAcs) {
|
||||||
acs = acs ? acs[scope] : defaultAcs;
|
acs = acs ? acs[scope] : defaultAcs;
|
||||||
acs = acs || defaultAcs;
|
acs = acs || defaultAcs;
|
||||||
try {
|
try {
|
||||||
return checkAcs(acs, { client : this.client } );
|
return checkAcs(acs, { client : this.client } );
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Log.warn( { exception : e, acs : acs }, 'Exception caught checking ACS');
|
Log.warn( { exception : e, acs : acs }, 'Exception caught checking ACS');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Message Conferences & Areas
|
// Message Conferences & Areas
|
||||||
//
|
//
|
||||||
hasMessageConfRead(conf) {
|
hasMessageConfRead(conf) {
|
||||||
return this.check(conf.acs, 'read', ACS.Defaults.MessageConfRead);
|
return this.check(conf.acs, 'read', ACS.Defaults.MessageConfRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMessageAreaRead(area) {
|
hasMessageAreaRead(area) {
|
||||||
return this.check(area.acs, 'read', ACS.Defaults.MessageAreaRead);
|
return this.check(area.acs, 'read', ACS.Defaults.MessageAreaRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// File Base / Areas
|
// File Base / Areas
|
||||||
//
|
//
|
||||||
hasFileAreaRead(area) {
|
hasFileAreaRead(area) {
|
||||||
return this.check(area.acs, 'read', ACS.Defaults.FileAreaRead);
|
return this.check(area.acs, 'read', ACS.Defaults.FileAreaRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFileAreaWrite(area) {
|
hasFileAreaWrite(area) {
|
||||||
return this.check(area.acs, 'write', ACS.Defaults.FileAreaWrite);
|
return this.check(area.acs, 'write', ACS.Defaults.FileAreaWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasFileAreaDownload(area) {
|
hasFileAreaDownload(area) {
|
||||||
return this.check(area.acs, 'download', ACS.Defaults.FileAreaDownload);
|
return this.check(area.acs, 'download', ACS.Defaults.FileAreaDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
getConditionalValue(condArray, memberName) {
|
getConditionalValue(condArray, memberName) {
|
||||||
if(!Array.isArray(condArray)) {
|
if(!Array.isArray(condArray)) {
|
||||||
// no cond array, just use the value
|
// no cond array, just use the value
|
||||||
return condArray;
|
return condArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(_.isString(memberName));
|
assert(_.isString(memberName));
|
||||||
|
|
||||||
const matchCond = condArray.find( cond => {
|
const matchCond = condArray.find( cond => {
|
||||||
if(_.has(cond, 'acs')) {
|
if(_.has(cond, 'acs')) {
|
||||||
try {
|
try {
|
||||||
return checkAcs(cond.acs, { client : this.client } );
|
return checkAcs(cond.acs, { client : this.client } );
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Log.warn( { exception : e, acs : cond }, 'Exception caught checking ACS');
|
Log.warn( { exception : e, acs : cond }, 'Exception caught checking ACS');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return true; // no acs check req.
|
return true; // no acs check req.
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(matchCond) {
|
if(matchCond) {
|
||||||
return matchCond[memberName];
|
return matchCond[memberName];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ACS.Defaults = {
|
ACS.Defaults = {
|
||||||
MessageAreaRead : 'GM[users]',
|
MessageAreaRead : 'GM[users]',
|
||||||
MessageConfRead : 'GM[users]',
|
MessageConfRead : 'GM[users]',
|
||||||
|
|
||||||
FileAreaRead : 'GM[users]',
|
FileAreaRead : 'GM[users]',
|
||||||
FileAreaWrite : 'GM[sysops]',
|
FileAreaWrite : 'GM[sysops]',
|
||||||
FileAreaDownload : 'GM[users]',
|
FileAreaDownload : 'GM[users]',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ACS;
|
module.exports = ACS;
|
||||||
|
|
|
@ -16,278 +16,278 @@ const CR = 0x0d;
|
||||||
const LF = 0x0a;
|
const LF = 0x0a;
|
||||||
|
|
||||||
function ANSIEscapeParser(options) {
|
function ANSIEscapeParser(options) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
events.EventEmitter.call(this);
|
events.EventEmitter.call(this);
|
||||||
|
|
||||||
this.column = 1;
|
this.column = 1;
|
||||||
this.row = 1;
|
this.row = 1;
|
||||||
this.scrollBack = 0;
|
this.scrollBack = 0;
|
||||||
this.graphicRendition = {};
|
this.graphicRendition = {};
|
||||||
|
|
||||||
this.parseState = {
|
this.parseState = {
|
||||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||||
};
|
};
|
||||||
|
|
||||||
options = miscUtil.valueWithDefault(options, {
|
options = miscUtil.valueWithDefault(options, {
|
||||||
mciReplaceChar : '',
|
mciReplaceChar : '',
|
||||||
termHeight : 25,
|
termHeight : 25,
|
||||||
termWidth : 80,
|
termWidth : 80,
|
||||||
trailingLF : 'default', // default|omit|no|yes, ...
|
trailingLF : 'default', // default|omit|no|yes, ...
|
||||||
});
|
});
|
||||||
|
|
||||||
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
|
this.mciReplaceChar = miscUtil.valueWithDefault(options.mciReplaceChar, '');
|
||||||
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
|
this.termHeight = miscUtil.valueWithDefault(options.termHeight, 25);
|
||||||
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
|
this.termWidth = miscUtil.valueWithDefault(options.termWidth, 80);
|
||||||
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
|
this.trailingLF = miscUtil.valueWithDefault(options.trailingLF, 'default');
|
||||||
|
|
||||||
self.moveCursor = function(cols, rows) {
|
self.moveCursor = function(cols, rows) {
|
||||||
self.column += cols;
|
self.column += cols;
|
||||||
self.row += rows;
|
self.row += rows;
|
||||||
|
|
||||||
self.column = Math.max(self.column, 1);
|
self.column = Math.max(self.column, 1);
|
||||||
self.column = Math.min(self.column, self.termWidth); // can't move past term width
|
self.column = Math.min(self.column, self.termWidth); // can't move past term width
|
||||||
self.row = Math.max(self.row, 1);
|
self.row = Math.max(self.row, 1);
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.saveCursorPosition = function() {
|
self.saveCursorPosition = function() {
|
||||||
self.savedPosition = {
|
self.savedPosition = {
|
||||||
row : self.row,
|
row : self.row,
|
||||||
column : self.column
|
column : self.column
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
self.restoreCursorPosition = function() {
|
self.restoreCursorPosition = function() {
|
||||||
self.row = self.savedPosition.row;
|
self.row = self.savedPosition.row;
|
||||||
self.column = self.savedPosition.column;
|
self.column = self.savedPosition.column;
|
||||||
delete self.savedPosition;
|
delete self.savedPosition;
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
// self.rowUpdated();
|
// self.rowUpdated();
|
||||||
};
|
};
|
||||||
|
|
||||||
self.clearScreen = function() {
|
self.clearScreen = function() {
|
||||||
// :TODO: should be doing something with row/column?
|
// :TODO: should be doing something with row/column?
|
||||||
self.emit('clear screen');
|
self.emit('clear screen');
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
self.rowUpdated = function() {
|
self.rowUpdated = function() {
|
||||||
self.emit('row update', self.row + self.scrollBack);
|
self.emit('row update', self.row + self.scrollBack);
|
||||||
};*/
|
};*/
|
||||||
|
|
||||||
self.positionUpdated = function() {
|
self.positionUpdated = function() {
|
||||||
self.emit('position update', self.row, self.column);
|
self.emit('position update', self.row, self.column);
|
||||||
};
|
};
|
||||||
|
|
||||||
function literal(text) {
|
function literal(text) {
|
||||||
const len = text.length;
|
const len = text.length;
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
let start = 0;
|
let start = 0;
|
||||||
let charCode;
|
let charCode;
|
||||||
|
|
||||||
while(pos < len) {
|
while(pos < len) {
|
||||||
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
|
charCode = text.charCodeAt(pos) & 0xff; // 8bit clean
|
||||||
|
|
||||||
switch(charCode) {
|
switch(charCode) {
|
||||||
case CR :
|
case CR :
|
||||||
self.emit('literal', text.slice(start, pos));
|
self.emit('literal', text.slice(start, pos));
|
||||||
start = pos;
|
start = pos;
|
||||||
|
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case LF :
|
case LF :
|
||||||
self.emit('literal', text.slice(start, pos));
|
self.emit('literal', text.slice(start, pos));
|
||||||
start = pos;
|
start = pos;
|
||||||
|
|
||||||
self.row += 1;
|
self.row += 1;
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
if(self.column === self.termWidth) {
|
if(self.column === self.termWidth) {
|
||||||
self.emit('literal', text.slice(start, pos + 1));
|
self.emit('literal', text.slice(start, pos + 1));
|
||||||
start = pos + 1;
|
start = pos + 1;
|
||||||
|
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
self.row += 1;
|
self.row += 1;
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
} else {
|
} else {
|
||||||
self.column += 1;
|
self.column += 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
++pos;
|
++pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Finalize this chunk
|
// Finalize this chunk
|
||||||
//
|
//
|
||||||
if(self.column > self.termWidth) {
|
if(self.column > self.termWidth) {
|
||||||
self.column = 1;
|
self.column = 1;
|
||||||
self.row += 1;
|
self.row += 1;
|
||||||
|
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
const rem = text.slice(start);
|
const rem = text.slice(start);
|
||||||
if(rem) {
|
if(rem) {
|
||||||
self.emit('literal', rem);
|
self.emit('literal', rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMCI(buffer) {
|
function parseMCI(buffer) {
|
||||||
// :TODO: move this to "constants" seciton @ top
|
// :TODO: move this to "constants" seciton @ top
|
||||||
var mciRe = /%([A-Z]{2})([0-9]{1,2})?(?:\(([0-9A-Za-z,]+)\))*/g;
|
var mciRe = /%([A-Z]{2})([0-9]{1,2})?(?:\(([0-9A-Za-z,]+)\))*/g;
|
||||||
var pos = 0;
|
var pos = 0;
|
||||||
var match;
|
var match;
|
||||||
var mciCode;
|
var mciCode;
|
||||||
var args;
|
var args;
|
||||||
var id;
|
var id;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
pos = mciRe.lastIndex;
|
pos = mciRe.lastIndex;
|
||||||
match = mciRe.exec(buffer);
|
match = mciRe.exec(buffer);
|
||||||
|
|
||||||
if(null !== match) {
|
if(null !== match) {
|
||||||
if(match.index > pos) {
|
if(match.index > pos) {
|
||||||
literal(buffer.slice(pos, match.index));
|
literal(buffer.slice(pos, match.index));
|
||||||
}
|
}
|
||||||
|
|
||||||
mciCode = match[1];
|
mciCode = match[1];
|
||||||
id = match[2] || null;
|
id = match[2] || null;
|
||||||
|
|
||||||
if(match[3]) {
|
if(match[3]) {
|
||||||
args = match[3].split(',');
|
args = match[3].split(',');
|
||||||
} else {
|
} else {
|
||||||
args = [];
|
args = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// if MCI codes are changing, save off the current color
|
// if MCI codes are changing, save off the current color
|
||||||
var fullMciCode = mciCode + (id || '');
|
var fullMciCode = mciCode + (id || '');
|
||||||
if(self.lastMciCode !== fullMciCode) {
|
if(self.lastMciCode !== fullMciCode) {
|
||||||
|
|
||||||
self.lastMciCode = fullMciCode;
|
self.lastMciCode = fullMciCode;
|
||||||
|
|
||||||
self.graphicRenditionForErase = _.clone(self.graphicRendition);
|
self.graphicRenditionForErase = _.clone(self.graphicRendition);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
self.emit('mci', {
|
self.emit('mci', {
|
||||||
mci : mciCode,
|
mci : mciCode,
|
||||||
id : id ? parseInt(id, 10) : null,
|
id : id ? parseInt(id, 10) : null,
|
||||||
args : args,
|
args : args,
|
||||||
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
|
SGR : ansi.getSGRFromGraphicRendition(self.graphicRendition, true)
|
||||||
});
|
});
|
||||||
|
|
||||||
if(self.mciReplaceChar.length > 0) {
|
if(self.mciReplaceChar.length > 0) {
|
||||||
const sgrCtrl = ansi.getSGRFromGraphicRendition(self.graphicRenditionForErase);
|
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));
|
literal(new Array(match[0].length + 1).join(self.mciReplaceChar));
|
||||||
} else {
|
} else {
|
||||||
literal(match[0]);
|
literal(match[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} while(0 !== mciRe.lastIndex);
|
} while(0 !== mciRe.lastIndex);
|
||||||
|
|
||||||
if(pos < buffer.length) {
|
if(pos < buffer.length) {
|
||||||
literal(buffer.slice(pos));
|
literal(buffer.slice(pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.reset = function(input) {
|
self.reset = function(input) {
|
||||||
self.parseState = {
|
self.parseState = {
|
||||||
// ignore anything past EOF marker, if any
|
// ignore anything past EOF marker, if any
|
||||||
buffer : input.split(String.fromCharCode(0x1a), 1)[0],
|
buffer : input.split(String.fromCharCode(0x1a), 1)[0],
|
||||||
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
re : /(?:\x1b\x5b)([?=;0-9]*?)([ABCDHJKfhlmnpsu])/g, // eslint-disable-line no-control-regex
|
||||||
stop : false,
|
stop : false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
self.stop = function() {
|
self.stop = function() {
|
||||||
self.parseState.stop = true;
|
self.parseState.stop = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.parse = function(input) {
|
self.parse = function(input) {
|
||||||
if(input) {
|
if(input) {
|
||||||
self.reset(input);
|
self.reset(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||||
var pos;
|
var pos;
|
||||||
var match;
|
var match;
|
||||||
var opCode;
|
var opCode;
|
||||||
var args;
|
var args;
|
||||||
var re = self.parseState.re;
|
var re = self.parseState.re;
|
||||||
var buffer = self.parseState.buffer;
|
var buffer = self.parseState.buffer;
|
||||||
|
|
||||||
self.parseState.stop = false;
|
self.parseState.stop = false;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if(self.parseState.stop) {
|
if(self.parseState.stop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = re.lastIndex;
|
pos = re.lastIndex;
|
||||||
match = re.exec(buffer);
|
match = re.exec(buffer);
|
||||||
|
|
||||||
if(null !== match) {
|
if(null !== match) {
|
||||||
if(match.index > pos) {
|
if(match.index > pos) {
|
||||||
parseMCI(buffer.slice(pos, match.index));
|
parseMCI(buffer.slice(pos, match.index));
|
||||||
}
|
}
|
||||||
|
|
||||||
opCode = match[2];
|
opCode = match[2];
|
||||||
args = match[1].split(';').map(v => parseInt(v, 10)); // convert to array of ints
|
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('chunk', match[0]);
|
||||||
self.emit('control', match[0], opCode, args);
|
self.emit('control', match[0], opCode, args);
|
||||||
}
|
}
|
||||||
} while(0 !== re.lastIndex);
|
} while(0 !== re.lastIndex);
|
||||||
|
|
||||||
if(pos < buffer.length) {
|
if(pos < buffer.length) {
|
||||||
var lastBit = buffer.slice(pos);
|
var lastBit = buffer.slice(pos);
|
||||||
|
|
||||||
// :TODO: check for various ending LF's, not just DOS \r\n
|
// :TODO: check for various ending LF's, not just DOS \r\n
|
||||||
if('\r\n' === lastBit.slice(-2).toString()) {
|
if('\r\n' === lastBit.slice(-2).toString()) {
|
||||||
switch(self.trailingLF) {
|
switch(self.trailingLF) {
|
||||||
case 'default' :
|
case 'default' :
|
||||||
//
|
//
|
||||||
// Default is to *not* omit the trailing LF
|
// Default is to *not* omit the trailing LF
|
||||||
// if we're going to end on termHeight
|
// if we're going to end on termHeight
|
||||||
//
|
//
|
||||||
if(this.termHeight === self.row) {
|
if(this.termHeight === self.row) {
|
||||||
lastBit = lastBit.slice(0, -2);
|
lastBit = lastBit.slice(0, -2);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'omit' :
|
case 'omit' :
|
||||||
case 'no' :
|
case 'no' :
|
||||||
case false :
|
case false :
|
||||||
lastBit = lastBit.slice(0, -2);
|
lastBit = lastBit.slice(0, -2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseMCI(lastBit);
|
parseMCI(lastBit);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emit('complete');
|
self.emit('complete');
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
self.parse = function(buffer, savedRe) {
|
self.parse = function(buffer, savedRe) {
|
||||||
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
// :TODO: ensure this conforms to ANSI-BBS / CTerm / bansi.txt for movement/etc.
|
||||||
// :TODO: move this to "constants" section @ top
|
// :TODO: move this to "constants" section @ top
|
||||||
|
@ -329,164 +329,164 @@ function ANSIEscapeParser(options) {
|
||||||
};
|
};
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function escape(opCode, args) {
|
function escape(opCode, args) {
|
||||||
let arg;
|
let arg;
|
||||||
|
|
||||||
switch(opCode) {
|
switch(opCode) {
|
||||||
// cursor up
|
// cursor up
|
||||||
case 'A' :
|
case 'A' :
|
||||||
//arg = args[0] || 1;
|
//arg = args[0] || 1;
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
arg = isNaN(args[0]) ? 1 : args[0];
|
||||||
self.moveCursor(0, -arg);
|
self.moveCursor(0, -arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// cursor down
|
// cursor down
|
||||||
case 'B' :
|
case 'B' :
|
||||||
//arg = args[0] || 1;
|
//arg = args[0] || 1;
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
arg = isNaN(args[0]) ? 1 : args[0];
|
||||||
self.moveCursor(0, arg);
|
self.moveCursor(0, arg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// cursor forward/right
|
// cursor forward/right
|
||||||
case 'C' :
|
case 'C' :
|
||||||
//arg = args[0] || 1;
|
//arg = args[0] || 1;
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
arg = isNaN(args[0]) ? 1 : args[0];
|
||||||
self.moveCursor(arg, 0);
|
self.moveCursor(arg, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// cursor back/left
|
// cursor back/left
|
||||||
case 'D' :
|
case 'D' :
|
||||||
//arg = args[0] || 1;
|
//arg = args[0] || 1;
|
||||||
arg = isNaN(args[0]) ? 1 : args[0];
|
arg = isNaN(args[0]) ? 1 : args[0];
|
||||||
self.moveCursor(-arg, 0);
|
self.moveCursor(-arg, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'f' : // horiz & vertical
|
case 'f' : // horiz & vertical
|
||||||
case 'H' : // cursor position
|
case 'H' : // cursor position
|
||||||
//self.row = args[0] || 1;
|
//self.row = args[0] || 1;
|
||||||
//self.column = args[1] || 1;
|
//self.column = args[1] || 1;
|
||||||
self.row = isNaN(args[0]) ? 1 : args[0];
|
self.row = isNaN(args[0]) ? 1 : args[0];
|
||||||
self.column = isNaN(args[1]) ? 1 : args[1];
|
self.column = isNaN(args[1]) ? 1 : args[1];
|
||||||
//self.rowUpdated();
|
//self.rowUpdated();
|
||||||
self.positionUpdated();
|
self.positionUpdated();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// save position
|
// save position
|
||||||
case 's' :
|
case 's' :
|
||||||
self.saveCursorPosition();
|
self.saveCursorPosition();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// restore position
|
// restore position
|
||||||
case 'u' :
|
case 'u' :
|
||||||
self.restoreCursorPosition();
|
self.restoreCursorPosition();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// set graphic rendition
|
// set graphic rendition
|
||||||
case 'm' :
|
case 'm' :
|
||||||
self.graphicRendition.reset = false;
|
self.graphicRendition.reset = false;
|
||||||
|
|
||||||
for(let i = 0, len = args.length; i < len; ++i) {
|
for(let i = 0, len = args.length; i < len; ++i) {
|
||||||
arg = args[i];
|
arg = args[i];
|
||||||
|
|
||||||
if(ANSIEscapeParser.foregroundColors[arg]) {
|
if(ANSIEscapeParser.foregroundColors[arg]) {
|
||||||
self.graphicRendition.fg = arg;
|
self.graphicRendition.fg = arg;
|
||||||
} else if(ANSIEscapeParser.backgroundColors[arg]) {
|
} else if(ANSIEscapeParser.backgroundColors[arg]) {
|
||||||
self.graphicRendition.bg = arg;
|
self.graphicRendition.bg = arg;
|
||||||
} else if(ANSIEscapeParser.styles[arg]) {
|
} else if(ANSIEscapeParser.styles[arg]) {
|
||||||
switch(arg) {
|
switch(arg) {
|
||||||
case 0 :
|
case 0 :
|
||||||
// clear out everything
|
// clear out everything
|
||||||
delete self.graphicRendition.intensity;
|
delete self.graphicRendition.intensity;
|
||||||
delete self.graphicRendition.underline;
|
delete self.graphicRendition.underline;
|
||||||
delete self.graphicRendition.blink;
|
delete self.graphicRendition.blink;
|
||||||
delete self.graphicRendition.negative;
|
delete self.graphicRendition.negative;
|
||||||
delete self.graphicRendition.invisible;
|
delete self.graphicRendition.invisible;
|
||||||
|
|
||||||
delete self.graphicRendition.fg;
|
delete self.graphicRendition.fg;
|
||||||
delete self.graphicRendition.bg;
|
delete self.graphicRendition.bg;
|
||||||
|
|
||||||
self.graphicRendition.reset = true;
|
self.graphicRendition.reset = true;
|
||||||
//self.graphicRendition.fg = 39;
|
//self.graphicRendition.fg = 39;
|
||||||
//self.graphicRendition.bg = 49;
|
//self.graphicRendition.bg = 49;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1 :
|
case 1 :
|
||||||
case 2 :
|
case 2 :
|
||||||
case 22 :
|
case 22 :
|
||||||
self.graphicRendition.intensity = arg;
|
self.graphicRendition.intensity = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 4 :
|
case 4 :
|
||||||
case 24 :
|
case 24 :
|
||||||
self.graphicRendition.underline = arg;
|
self.graphicRendition.underline = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 5 :
|
case 5 :
|
||||||
case 6 :
|
case 6 :
|
||||||
case 25 :
|
case 25 :
|
||||||
self.graphicRendition.blink = arg;
|
self.graphicRendition.blink = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 7 :
|
case 7 :
|
||||||
case 27 :
|
case 27 :
|
||||||
self.graphicRendition.negative = arg;
|
self.graphicRendition.negative = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 8 :
|
case 8 :
|
||||||
case 28 :
|
case 28 :
|
||||||
self.graphicRendition.invisible = arg;
|
self.graphicRendition.invisible = arg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
Log.trace( { attribute : arg }, 'Unknown attribute while parsing ANSI');
|
Log.trace( { attribute : arg }, 'Unknown attribute while parsing ANSI');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.emit('sgr update', self.graphicRendition);
|
self.emit('sgr update', self.graphicRendition);
|
||||||
break; // m
|
break; // m
|
||||||
|
|
||||||
// :TODO: s, u, K
|
// :TODO: s, u, K
|
||||||
|
|
||||||
// erase display/screen
|
// erase display/screen
|
||||||
case 'J' :
|
case 'J' :
|
||||||
// :TODO: Handle other 'J' types!
|
// :TODO: Handle other 'J' types!
|
||||||
if(2 === args[0]) {
|
if(2 === args[0]) {
|
||||||
self.clearScreen();
|
self.clearScreen();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(ANSIEscapeParser, events.EventEmitter);
|
util.inherits(ANSIEscapeParser, events.EventEmitter);
|
||||||
|
|
||||||
ANSIEscapeParser.foregroundColors = {
|
ANSIEscapeParser.foregroundColors = {
|
||||||
30 : 'black',
|
30 : 'black',
|
||||||
31 : 'red',
|
31 : 'red',
|
||||||
32 : 'green',
|
32 : 'green',
|
||||||
33 : 'yellow',
|
33 : 'yellow',
|
||||||
34 : 'blue',
|
34 : 'blue',
|
||||||
35 : 'magenta',
|
35 : 'magenta',
|
||||||
36 : 'cyan',
|
36 : 'cyan',
|
||||||
37 : 'white',
|
37 : 'white',
|
||||||
39 : 'default', // same as white for most implementations
|
39 : 'default', // same as white for most implementations
|
||||||
|
|
||||||
90 : 'grey'
|
90 : 'grey'
|
||||||
};
|
};
|
||||||
Object.freeze(ANSIEscapeParser.foregroundColors);
|
Object.freeze(ANSIEscapeParser.foregroundColors);
|
||||||
|
|
||||||
ANSIEscapeParser.backgroundColors = {
|
ANSIEscapeParser.backgroundColors = {
|
||||||
40 : 'black',
|
40 : 'black',
|
||||||
41 : 'red',
|
41 : 'red',
|
||||||
42 : 'green',
|
42 : 'green',
|
||||||
43 : 'yellow',
|
43 : 'yellow',
|
||||||
44 : 'blue',
|
44 : 'blue',
|
||||||
45 : 'magenta',
|
45 : 'magenta',
|
||||||
46 : 'cyan',
|
46 : 'cyan',
|
||||||
47 : 'white',
|
47 : 'white',
|
||||||
49 : 'default', // same as black for most implementations
|
49 : 'default', // same as black for most implementations
|
||||||
};
|
};
|
||||||
Object.freeze(ANSIEscapeParser.backgroundColors);
|
Object.freeze(ANSIEscapeParser.backgroundColors);
|
||||||
|
|
||||||
|
@ -501,24 +501,24 @@ Object.freeze(ANSIEscapeParser.backgroundColors);
|
||||||
// can be grouped by concept here in code.
|
// can be grouped by concept here in code.
|
||||||
//
|
//
|
||||||
ANSIEscapeParser.styles = {
|
ANSIEscapeParser.styles = {
|
||||||
0 : 'default', // Everything disabled
|
0 : 'default', // Everything disabled
|
||||||
|
|
||||||
1 : 'intensityBright', // aka bold
|
1 : 'intensityBright', // aka bold
|
||||||
2 : 'intensityDim',
|
2 : 'intensityDim',
|
||||||
22 : 'intensityNormal',
|
22 : 'intensityNormal',
|
||||||
|
|
||||||
4 : 'underlineOn', // 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
|
24 : 'underlineOff', // Not supported by most BBS-like terminals
|
||||||
|
|
||||||
5 : 'blinkSlow', // blinkSlow & blinkFast are generally treated the same
|
5 : 'blinkSlow', // blinkSlow & blinkFast are generally treated the same
|
||||||
6 : 'blinkFast', // blinkSlow & blinkFast are generally treated the same
|
6 : 'blinkFast', // blinkSlow & blinkFast are generally treated the same
|
||||||
25 : 'blinkOff',
|
25 : 'blinkOff',
|
||||||
|
|
||||||
7 : 'negativeImageOn', // 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"
|
27 : 'negativeImageOff', // Generally not supported or treated as "reverse FG & BG"
|
||||||
|
|
||||||
8 : 'invisibleOn', // FG set to BG
|
8 : 'invisibleOn', // FG set to BG
|
||||||
28 : 'invisibleOff', // Not supported by most BBS-like terminals
|
28 : 'invisibleOff', // Not supported by most BBS-like terminals
|
||||||
};
|
};
|
||||||
Object.freeze(ANSIEscapeParser.styles);
|
Object.freeze(ANSIEscapeParser.styles);
|
||||||
|
|
||||||
|
|
|
@ -5,216 +5,216 @@
|
||||||
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
|
const ANSIEscapeParser = require('./ansi_escape_parser.js').ANSIEscapeParser;
|
||||||
const ANSI = require('./ansi_term.js');
|
const ANSI = require('./ansi_term.js');
|
||||||
const {
|
const {
|
||||||
splitTextAtTerms,
|
splitTextAtTerms,
|
||||||
renderStringLength
|
renderStringLength
|
||||||
} = require('./string_util.js');
|
} = require('./string_util.js');
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = function ansiPrep(input, options, cb) {
|
module.exports = function ansiPrep(input, options, cb) {
|
||||||
if(!input) {
|
if(!input) {
|
||||||
return cb(null, '');
|
return cb(null, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
options.termWidth = options.termWidth || 80;
|
options.termWidth = options.termWidth || 80;
|
||||||
options.termHeight = options.termHeight || 25;
|
options.termHeight = options.termHeight || 25;
|
||||||
options.cols = options.cols || options.termWidth || 80;
|
options.cols = options.cols || options.termWidth || 80;
|
||||||
options.rows = options.rows || options.termHeight || 'auto';
|
options.rows = options.rows || options.termHeight || 'auto';
|
||||||
options.startCol = options.startCol || 1;
|
options.startCol = options.startCol || 1;
|
||||||
options.exportMode = options.exportMode || false;
|
options.exportMode = options.exportMode || false;
|
||||||
options.fillLines = _.get(options, 'fillLines', true);
|
options.fillLines = _.get(options, 'fillLines', true);
|
||||||
options.indent = options.indent || 0;
|
options.indent = options.indent || 0;
|
||||||
|
|
||||||
// in auto we start out at 25 rows, but can always expand for more
|
// 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 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 parser = new ANSIEscapeParser( { termHeight : options.termHeight, termWidth : options.termWidth } );
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
row : 0,
|
row : 0,
|
||||||
col : 0,
|
col : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastRow = 0;
|
let lastRow = 0;
|
||||||
|
|
||||||
function ensureRow(row) {
|
function ensureRow(row) {
|
||||||
if(canvas[row]) {
|
if(canvas[row]) {
|
||||||
return;
|
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) => {
|
parser.on('position update', (row, col) => {
|
||||||
state.row = row - 1;
|
state.row = row - 1;
|
||||||
state.col = col - 1;
|
state.col = col - 1;
|
||||||
|
|
||||||
if(0 === state.col) {
|
if(0 === state.col) {
|
||||||
state.initialSgr = state.lastSgr;
|
state.initialSgr = state.lastSgr;
|
||||||
}
|
}
|
||||||
|
|
||||||
lastRow = Math.max(state.row, lastRow);
|
lastRow = Math.max(state.row, lastRow);
|
||||||
});
|
});
|
||||||
|
|
||||||
parser.on('literal', literal => {
|
parser.on('literal', literal => {
|
||||||
//
|
//
|
||||||
// CR/LF are handled for 'position update'; we don't need the chars themselves
|
// CR/LF are handled for 'position update'; we don't need the chars themselves
|
||||||
//
|
//
|
||||||
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
literal = literal.replace(/\r?\n|[\r\u2028\u2029]/g, '');
|
||||||
|
|
||||||
for(let c of literal) {
|
for(let c of literal) {
|
||||||
if(state.col < options.cols && ('auto' === options.rows || state.row < options.rows)) {
|
if(state.col < options.cols && ('auto' === options.rows || state.row < options.rows)) {
|
||||||
ensureRow(state.row);
|
ensureRow(state.row);
|
||||||
|
|
||||||
if(0 === state.col) {
|
if(0 === state.col) {
|
||||||
canvas[state.row][state.col].initialSgr = state.initialSgr;
|
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) {
|
if(state.sgr) {
|
||||||
canvas[state.row][state.col].sgr = _.clone(state.sgr);
|
canvas[state.row][state.col].sgr = _.clone(state.sgr);
|
||||||
state.lastSgr = canvas[state.row][state.col].sgr;
|
state.lastSgr = canvas[state.row][state.col].sgr;
|
||||||
state.sgr = null;
|
state.sgr = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.col += 1;
|
state.col += 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
parser.on('sgr update', sgr => {
|
parser.on('sgr update', sgr => {
|
||||||
ensureRow(state.row);
|
ensureRow(state.row);
|
||||||
|
|
||||||
if(state.col < options.cols) {
|
if(state.col < options.cols) {
|
||||||
canvas[state.row][state.col].sgr = _.clone(sgr);
|
canvas[state.row][state.col].sgr = _.clone(sgr);
|
||||||
state.lastSgr = canvas[state.row][state.col].sgr;
|
state.lastSgr = canvas[state.row][state.col].sgr;
|
||||||
} else {
|
} else {
|
||||||
state.sgr = sgr;
|
state.sgr = sgr;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getLastPopulatedColumn(row) {
|
function getLastPopulatedColumn(row) {
|
||||||
let col = row.length;
|
let col = row.length;
|
||||||
while(--col > 0) {
|
while(--col > 0) {
|
||||||
if(row[col].char || row[col].sgr) {
|
if(row[col].char || row[col].sgr) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.on('complete', () => {
|
parser.on('complete', () => {
|
||||||
let output = '';
|
let output = '';
|
||||||
let line;
|
let line;
|
||||||
let sgr;
|
let sgr;
|
||||||
|
|
||||||
canvas.slice(0, lastRow + 1).forEach(row => {
|
canvas.slice(0, lastRow + 1).forEach(row => {
|
||||||
const lastCol = getLastPopulatedColumn(row) + 1;
|
const lastCol = getLastPopulatedColumn(row) + 1;
|
||||||
|
|
||||||
let i;
|
let i;
|
||||||
line = options.indent ?
|
line = options.indent ?
|
||||||
output.length > 0 ? ' '.repeat(options.indent) : '' :
|
output.length > 0 ? ' '.repeat(options.indent) : '' :
|
||||||
'';
|
'';
|
||||||
|
|
||||||
for(i = 0; i < lastCol; ++i) {
|
for(i = 0; i < lastCol; ++i) {
|
||||||
const col = row[i];
|
const col = row[i];
|
||||||
|
|
||||||
sgr = !options.asciiMode && 0 === i ?
|
sgr = !options.asciiMode && 0 === i ?
|
||||||
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' :
|
col.initialSgr ? ANSI.getSGRFromGraphicRendition(col.initialSgr) : '' :
|
||||||
'';
|
'';
|
||||||
|
|
||||||
if(!options.asciiMode && col.sgr) {
|
if(!options.asciiMode && col.sgr) {
|
||||||
sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
|
sgr += ANSI.getSGRFromGraphicRendition(col.sgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
line += `${sgr}${col.char || ' '}`;
|
line += `${sgr}${col.char || ' '}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
output += line;
|
output += line;
|
||||||
|
|
||||||
if(i < row.length) {
|
if(i < row.length) {
|
||||||
output += `${options.asciiMode ? '' : ANSI.blackBG()}`;
|
output += `${options.asciiMode ? '' : ANSI.blackBG()}`;
|
||||||
if(options.fillLines) {
|
if(options.fillLines) {
|
||||||
output += `${row.slice(i).map( () => ' ').join('')}`;//${lastSgr}`;
|
output += `${row.slice(i).map( () => ' ').join('')}`;//${lastSgr}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.startCol + i < options.termWidth || options.forceLineTerm) {
|
if(options.startCol + i < options.termWidth || options.forceLineTerm) {
|
||||||
output += '\r\n';
|
output += '\r\n';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(options.exportMode) {
|
if(options.exportMode) {
|
||||||
//
|
//
|
||||||
// If we're in export mode, we do some additional hackery:
|
// If we're in export mode, we do some additional hackery:
|
||||||
//
|
//
|
||||||
// * Hard wrap ALL lines at <= 79 *characters* (not visible columns)
|
// * 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>
|
// 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
|
// represents chars to get back to the position we were previously at
|
||||||
//
|
//
|
||||||
// * Replace contig spaces with ESC[<N>C as well to save... space.
|
// * 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
|
// :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
|
const MAX_CHARS = 79 - 8; // 79 max, - 8 for max ESC seq's we may prefix a line with
|
||||||
let exportOutput = '';
|
let exportOutput = '';
|
||||||
|
|
||||||
let m;
|
let m;
|
||||||
let afterSeq;
|
let afterSeq;
|
||||||
let wantMore;
|
let wantMore;
|
||||||
let renderStart;
|
let renderStart;
|
||||||
|
|
||||||
splitTextAtTerms(output).forEach(fullLine => {
|
splitTextAtTerms(output).forEach(fullLine => {
|
||||||
renderStart = 0;
|
renderStart = 0;
|
||||||
|
|
||||||
while(fullLine.length > 0) {
|
while(fullLine.length > 0) {
|
||||||
let splitAt;
|
let splitAt;
|
||||||
const ANSI_REGEXP = ANSI.getFullMatchRegExp();
|
const ANSI_REGEXP = ANSI.getFullMatchRegExp();
|
||||||
wantMore = true;
|
wantMore = true;
|
||||||
|
|
||||||
while((m = ANSI_REGEXP.exec(fullLine))) {
|
while((m = ANSI_REGEXP.exec(fullLine))) {
|
||||||
afterSeq = m.index + m[0].length;
|
afterSeq = m.index + m[0].length;
|
||||||
|
|
||||||
if(afterSeq < MAX_CHARS) {
|
if(afterSeq < MAX_CHARS) {
|
||||||
// after current seq
|
// after current seq
|
||||||
splitAt = afterSeq;
|
splitAt = afterSeq;
|
||||||
} else {
|
} else {
|
||||||
if(m.index < MAX_CHARS) {
|
if(m.index < MAX_CHARS) {
|
||||||
// before last found seq
|
// before last found seq
|
||||||
splitAt = m.index;
|
splitAt = m.index;
|
||||||
wantMore = false; // can't eat up any more
|
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(splitAt) {
|
||||||
if(wantMore) {
|
if(wantMore) {
|
||||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
splitAt = Math.min(fullLine.length, MAX_CHARS - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const part = fullLine.slice(0, splitAt);
|
const part = fullLine.slice(0, splitAt);
|
||||||
fullLine = fullLine.slice(splitAt);
|
fullLine = fullLine.slice(splitAt);
|
||||||
renderStart += renderStringLength(part);
|
renderStart += renderStringLength(part);
|
||||||
exportOutput += `${part}\r\n`;
|
exportOutput += `${part}\r\n`;
|
||||||
|
|
||||||
if(fullLine.length > 0) { // more to go for this line?
|
if(fullLine.length > 0) { // more to go for this line?
|
||||||
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
exportOutput += `${ANSI.up()}${ANSI.right(renderStart)}`;
|
||||||
} else {
|
} else {
|
||||||
exportOutput += ANSI.up();
|
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 ESC_CSI = '\u001b[';
|
||||||
|
|
||||||
const CONTROL = {
|
const CONTROL = {
|
||||||
up : 'A',
|
up : 'A',
|
||||||
down : 'B',
|
down : 'B',
|
||||||
|
|
||||||
forward : 'C',
|
forward : 'C',
|
||||||
right : 'C',
|
right : 'C',
|
||||||
|
|
||||||
back : 'D',
|
back : 'D',
|
||||||
left : 'D',
|
left : 'D',
|
||||||
|
|
||||||
nextLine : 'E',
|
nextLine : 'E',
|
||||||
prevLine : 'F',
|
prevLine : 'F',
|
||||||
horizAbsolute : 'G',
|
horizAbsolute : 'G',
|
||||||
|
|
||||||
//
|
//
|
||||||
// CSI [ p1 ] J
|
// CSI [ p1 ] J
|
||||||
// Erase in Page / Erase Data
|
// Erase in Page / Erase Data
|
||||||
// Defaults: p1 = 0
|
// Defaults: p1 = 0
|
||||||
// Erases from the current screen according to the value of p1
|
// Erases from the current screen according to the value of p1
|
||||||
// 0 - Erase from the current position to the end of the screen.
|
// 0 - Erase from the current position to the end of the screen.
|
||||||
// 1 - Erase from the current position to the start 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
|
// 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
|
// the cursor to position 1/1 as a number of BBS programs assume
|
||||||
// this behaviour.
|
// this behaviour.
|
||||||
// Erased characters are set to the current attribute.
|
// Erased characters are set to the current attribute.
|
||||||
//
|
//
|
||||||
// Support:
|
// Support:
|
||||||
// * SyncTERM: Works as expected
|
// * SyncTERM: Works as expected
|
||||||
// * NetRunner: Always clears a screen *height* (e.g. 25) regardless of p1
|
// * NetRunner: Always clears a screen *height* (e.g. 25) regardless of p1
|
||||||
// and screen remainder
|
// and screen remainder
|
||||||
//
|
//
|
||||||
eraseData : 'J',
|
eraseData : 'J',
|
||||||
|
|
||||||
eraseLine : 'K',
|
eraseLine : 'K',
|
||||||
insertLine : 'L',
|
insertLine : 'L',
|
||||||
|
|
||||||
//
|
//
|
||||||
// CSI [ p1 ] M
|
// CSI [ p1 ] M
|
||||||
// Delete Line(s) / "ANSI" Music
|
// Delete Line(s) / "ANSI" Music
|
||||||
// Defaults: p1 = 1
|
// Defaults: p1 = 1
|
||||||
// Deletes the current line and the p1 - 1 lines after it scrolling the
|
// 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
|
// 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.
|
// empty lines at the end of the screen with the current attribute.
|
||||||
// If "ANSI" Music is fully enabled (CSI = 2 M), performs "ANSI" music
|
// If "ANSI" Music is fully enabled (CSI = 2 M), performs "ANSI" music
|
||||||
// instead.
|
// instead.
|
||||||
// See "ANSI" MUSIC section for more details.
|
// See "ANSI" MUSIC section for more details.
|
||||||
//
|
//
|
||||||
// Support:
|
// Support:
|
||||||
// * SyncTERM: Works as expected
|
// * SyncTERM: Works as expected
|
||||||
// * NetRunner:
|
// * NetRunner:
|
||||||
//
|
//
|
||||||
// General Notes:
|
// General Notes:
|
||||||
// See also notes in bansi.txt and cterm.txt about the various
|
// See also notes in bansi.txt and cterm.txt about the various
|
||||||
// incompatibilities & oddities around this sequence. ANSI-BBS
|
// incompatibilities & oddities around this sequence. ANSI-BBS
|
||||||
// states that it *should* work with any value of p1.
|
// states that it *should* work with any value of p1.
|
||||||
//
|
//
|
||||||
deleteLine : 'M',
|
deleteLine : 'M',
|
||||||
ansiMusic : 'M',
|
ansiMusic : 'M',
|
||||||
|
|
||||||
scrollUp : 'S',
|
scrollUp : 'S',
|
||||||
scrollDown : 'T',
|
scrollDown : 'T',
|
||||||
setScrollRegion : 'r',
|
setScrollRegion : 'r',
|
||||||
savePos : 's',
|
savePos : 's',
|
||||||
restorePos : 'u',
|
restorePos : 'u',
|
||||||
queryPos : '6n',
|
queryPos : '6n',
|
||||||
queryScreenSize : '255n', // See bansi.txt
|
queryScreenSize : '255n', // See bansi.txt
|
||||||
goto : 'H', // row Pr, column Pc -- same as f
|
goto : 'H', // row Pr, column Pc -- same as f
|
||||||
gotoAlt : 'f', // same as H
|
gotoAlt : 'f', // same as H
|
||||||
|
|
||||||
blinkToBrightIntensity : '?33h',
|
blinkToBrightIntensity : '?33h',
|
||||||
blinkNormal : '?33l',
|
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
|
hideCursor : '?25l', // Nonstandard - cterm.txt
|
||||||
showCursor : '?25h', // 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
|
// :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
|
// 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
|
// See http://cvs.synchro.net/cgi-bin/viewcvs.cgi/*checkout*/src/conio/cterm.txt
|
||||||
//
|
//
|
||||||
const SGRValues = {
|
const SGRValues = {
|
||||||
reset : 0,
|
reset : 0,
|
||||||
bold : 1,
|
bold : 1,
|
||||||
dim : 2,
|
dim : 2,
|
||||||
blink : 5,
|
blink : 5,
|
||||||
fastBlink : 6,
|
fastBlink : 6,
|
||||||
negative : 7,
|
negative : 7,
|
||||||
hidden : 8,
|
hidden : 8,
|
||||||
|
|
||||||
normal : 22, //
|
normal : 22, //
|
||||||
steady : 25,
|
steady : 25,
|
||||||
positive : 27,
|
positive : 27,
|
||||||
|
|
||||||
black : 30,
|
black : 30,
|
||||||
red : 31,
|
red : 31,
|
||||||
green : 32,
|
green : 32,
|
||||||
yellow : 33,
|
yellow : 33,
|
||||||
blue : 34,
|
blue : 34,
|
||||||
magenta : 35,
|
magenta : 35,
|
||||||
cyan : 36,
|
cyan : 36,
|
||||||
white : 37,
|
white : 37,
|
||||||
|
|
||||||
blackBG : 40,
|
blackBG : 40,
|
||||||
redBG : 41,
|
redBG : 41,
|
||||||
greenBG : 42,
|
greenBG : 42,
|
||||||
yellowBG : 43,
|
yellowBG : 43,
|
||||||
blueBG : 44,
|
blueBG : 44,
|
||||||
magentaBG : 45,
|
magentaBG : 45,
|
||||||
cyanBG : 46,
|
cyanBG : 46,
|
||||||
whiteBG : 47,
|
whiteBG : 47,
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFullMatchRegExp(flags = 'g') {
|
function getFullMatchRegExp(flags = 'g') {
|
||||||
// :TODO: expand this a bit - see strip-ansi/etc.
|
// :TODO: expand this a bit - see strip-ansi/etc.
|
||||||
// :TODO: \u009b ?
|
// :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
|
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) {
|
function getFGColorValue(name) {
|
||||||
return SGRValues[name];
|
return SGRValues[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBGColorValue(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
|
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
||||||
//
|
//
|
||||||
const SYNCTERM_FONT_AND_ENCODING_TABLE = [
|
const SYNCTERM_FONT_AND_ENCODING_TABLE = [
|
||||||
'cp437',
|
'cp437',
|
||||||
'cp1251',
|
'cp1251',
|
||||||
'koi8_r',
|
'koi8_r',
|
||||||
'iso8859_2',
|
'iso8859_2',
|
||||||
'iso8859_4',
|
'iso8859_4',
|
||||||
'cp866',
|
'cp866',
|
||||||
'iso8859_9',
|
'iso8859_9',
|
||||||
'haik8',
|
'haik8',
|
||||||
'iso8859_8',
|
'iso8859_8',
|
||||||
'koi8_u',
|
'koi8_u',
|
||||||
'iso8859_15',
|
'iso8859_15',
|
||||||
'iso8859_4',
|
'iso8859_4',
|
||||||
'koi8_r_b',
|
'koi8_r_b',
|
||||||
'iso8859_4',
|
'iso8859_4',
|
||||||
'iso8859_5',
|
'iso8859_5',
|
||||||
'ARMSCII_8',
|
'ARMSCII_8',
|
||||||
'iso8859_15',
|
'iso8859_15',
|
||||||
'cp850',
|
'cp850',
|
||||||
'cp850',
|
'cp850',
|
||||||
'cp885',
|
'cp885',
|
||||||
'cp1251',
|
'cp1251',
|
||||||
'iso8859_7',
|
'iso8859_7',
|
||||||
'koi8-r_c',
|
'koi8-r_c',
|
||||||
'iso8859_4',
|
'iso8859_4',
|
||||||
'iso8859_1',
|
'iso8859_1',
|
||||||
'cp866',
|
'cp866',
|
||||||
'cp437',
|
'cp437',
|
||||||
'cp866',
|
'cp866',
|
||||||
'cp885',
|
'cp885',
|
||||||
'cp866_u',
|
'cp866_u',
|
||||||
'iso8859_1',
|
'iso8859_1',
|
||||||
'cp1131',
|
'cp1131',
|
||||||
'c64_upper',
|
'c64_upper',
|
||||||
'c64_lower',
|
'c64_lower',
|
||||||
'c128_upper',
|
'c128_upper',
|
||||||
'c128_lower',
|
'c128_lower',
|
||||||
'atari',
|
'atari',
|
||||||
'pot_noodle',
|
'pot_noodle',
|
||||||
'mo_soul',
|
'mo_soul',
|
||||||
'microknight_plus',
|
'microknight_plus',
|
||||||
'topaz_plus',
|
'topaz_plus',
|
||||||
'microknight',
|
'microknight',
|
||||||
'topaz',
|
'topaz',
|
||||||
];
|
];
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -267,137 +267,137 @@ const SYNCTERM_FONT_AND_ENCODING_TABLE = [
|
||||||
// replaced with '_' for lookup purposes.
|
// replaced with '_' for lookup purposes.
|
||||||
//
|
//
|
||||||
const FONT_ALIAS_TO_SYNCTERM_MAP = {
|
const FONT_ALIAS_TO_SYNCTERM_MAP = {
|
||||||
'cp437' : 'cp437',
|
'cp437' : 'cp437',
|
||||||
'ibm_vga' : 'cp437',
|
'ibm_vga' : 'cp437',
|
||||||
'ibmpc' : 'cp437',
|
'ibmpc' : 'cp437',
|
||||||
'ibm_pc' : 'cp437',
|
'ibm_pc' : 'cp437',
|
||||||
'pc' : 'cp437',
|
'pc' : 'cp437',
|
||||||
'cp437_art' : 'cp437',
|
'cp437_art' : 'cp437',
|
||||||
'ibmpcart' : 'cp437',
|
'ibmpcart' : 'cp437',
|
||||||
'ibmpc_art' : 'cp437',
|
'ibmpc_art' : 'cp437',
|
||||||
'ibm_pc_art' : 'cp437',
|
'ibm_pc_art' : 'cp437',
|
||||||
'msdos_art' : 'cp437',
|
'msdos_art' : 'cp437',
|
||||||
'msdosart' : 'cp437',
|
'msdosart' : 'cp437',
|
||||||
'pc_art' : 'cp437',
|
'pc_art' : 'cp437',
|
||||||
'pcart' : 'cp437',
|
'pcart' : 'cp437',
|
||||||
|
|
||||||
'ibm_vga50' : 'cp437',
|
'ibm_vga50' : 'cp437',
|
||||||
'ibm_vga25g' : 'cp437',
|
'ibm_vga25g' : 'cp437',
|
||||||
'ibm_ega' : 'cp437',
|
'ibm_ega' : 'cp437',
|
||||||
'ibm_ega43' : 'cp437',
|
'ibm_ega43' : 'cp437',
|
||||||
|
|
||||||
'topaz' : 'topaz',
|
'topaz' : 'topaz',
|
||||||
'amiga_topaz_1' : 'topaz',
|
'amiga_topaz_1' : 'topaz',
|
||||||
'amiga_topaz_1+' : 'topaz_plus',
|
'amiga_topaz_1+' : 'topaz_plus',
|
||||||
'topazplus' : 'topaz_plus',
|
'topazplus' : 'topaz_plus',
|
||||||
'topaz_plus' : 'topaz_plus',
|
'topaz_plus' : 'topaz_plus',
|
||||||
'amiga_topaz_2' : 'topaz',
|
'amiga_topaz_2' : 'topaz',
|
||||||
'amiga_topaz_2+' : 'topaz_plus',
|
'amiga_topaz_2+' : 'topaz_plus',
|
||||||
'topaz2plus' : 'topaz_plus',
|
'topaz2plus' : 'topaz_plus',
|
||||||
|
|
||||||
'pot_noodle' : 'pot_noodle',
|
'pot_noodle' : 'pot_noodle',
|
||||||
'p0tnoodle' : 'pot_noodle',
|
'p0tnoodle' : 'pot_noodle',
|
||||||
'amiga_p0t-noodle' : 'pot_noodle',
|
'amiga_p0t-noodle' : 'pot_noodle',
|
||||||
|
|
||||||
'mo_soul' : 'mo_soul',
|
'mo_soul' : 'mo_soul',
|
||||||
'mosoul' : 'mo_soul',
|
'mosoul' : 'mo_soul',
|
||||||
'mO\'sOul' : 'mo_soul',
|
'mO\'sOul' : 'mo_soul',
|
||||||
|
|
||||||
'amiga_microknight' : 'microknight',
|
'amiga_microknight' : 'microknight',
|
||||||
'amiga_microknight+' : 'microknight_plus',
|
'amiga_microknight+' : 'microknight_plus',
|
||||||
|
|
||||||
'atari' : 'atari',
|
'atari' : 'atari',
|
||||||
'atarist' : 'atari',
|
'atarist' : 'atari',
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function setSyncTERMFont(name, fontPage) {
|
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);
|
const p2 = SYNCTERM_FONT_AND_ENCODING_TABLE.indexOf(name);
|
||||||
if(p2 > -1) {
|
if(p2 > -1) {
|
||||||
return `${ESC_CSI}${p1};${p2} D`;
|
return `${ESC_CSI}${p1};${p2} D`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSyncTERMFontFromAlias(alias) {
|
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) {
|
function setSyncTermFontWithAlias(nameOrAlias) {
|
||||||
nameOrAlias = getSyncTERMFontFromAlias(nameOrAlias) || nameOrAlias;
|
nameOrAlias = getSyncTERMFontFromAlias(nameOrAlias) || nameOrAlias;
|
||||||
return setSyncTERMFont(nameOrAlias);
|
return setSyncTERMFont(nameOrAlias);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEC_CURSOR_STYLE = {
|
const DEC_CURSOR_STYLE = {
|
||||||
'blinking block' : 0,
|
'blinking block' : 0,
|
||||||
'default' : 1,
|
'default' : 1,
|
||||||
'steady block' : 2,
|
'steady block' : 2,
|
||||||
'blinking underline' : 3,
|
'blinking underline' : 3,
|
||||||
'steady underline' : 4,
|
'steady underline' : 4,
|
||||||
'blinking bar' : 5,
|
'blinking bar' : 5,
|
||||||
'steady bar' : 6,
|
'steady bar' : 6,
|
||||||
};
|
};
|
||||||
|
|
||||||
function setCursorStyle(cursorStyle) {
|
function setCursorStyle(cursorStyle) {
|
||||||
const ps = DEC_CURSOR_STYLE[cursorStyle];
|
const ps = DEC_CURSOR_STYLE[cursorStyle];
|
||||||
if(ps) {
|
if(ps) {
|
||||||
return `${ESC_CSI}${ps} q`;
|
return `${ESC_CSI}${ps} q`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create methods such as up(), nextLine(),...
|
// Create methods such as up(), nextLine(),...
|
||||||
Object.keys(CONTROL).forEach(function onControlName(name) {
|
Object.keys(CONTROL).forEach(function onControlName(name) {
|
||||||
const code = CONTROL[name];
|
const code = CONTROL[name];
|
||||||
|
|
||||||
exports[name] = function() {
|
exports[name] = function() {
|
||||||
let c = code;
|
let c = code;
|
||||||
if(arguments.length > 0) {
|
if(arguments.length > 0) {
|
||||||
// arguments are array like -- we want an array
|
// arguments are array like -- we want an array
|
||||||
c = Array.prototype.slice.call(arguments).map(Math.round).join(';') + code;
|
c = Array.prototype.slice.call(arguments).map(Math.round).join(';') + code;
|
||||||
}
|
}
|
||||||
return `${ESC_CSI}${c}`;
|
return `${ESC_CSI}${c}`;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create various color methods such as white(), yellowBG(), reset(), ...
|
// Create various color methods such as white(), yellowBG(), reset(), ...
|
||||||
Object.keys(SGRValues).forEach( name => {
|
Object.keys(SGRValues).forEach( name => {
|
||||||
const code = SGRValues[name];
|
const code = SGRValues[name];
|
||||||
|
|
||||||
exports[name] = function() {
|
exports[name] = function() {
|
||||||
return `${ESC_CSI}${code}m`;
|
return `${ESC_CSI}${code}m`;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function sgr() {
|
function sgr() {
|
||||||
//
|
//
|
||||||
// - Allow an single array or variable number of arguments
|
// - Allow an single array or variable number of arguments
|
||||||
// - Each element can be either a integer or string found in SGRValues
|
// - Each element can be either a integer or string found in SGRValues
|
||||||
// which in turn maps to a integer
|
// which in turn maps to a integer
|
||||||
//
|
//
|
||||||
if(arguments.length <= 0) {
|
if(arguments.length <= 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = [];
|
let result = [];
|
||||||
const args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
const args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
|
||||||
|
|
||||||
for(let i = 0; i < args.length; ++i) {
|
for(let i = 0; i < args.length; ++i) {
|
||||||
const arg = args[i];
|
const arg = args[i];
|
||||||
if(_.isString(arg) && arg in SGRValues) {
|
if(_.isString(arg) && arg in SGRValues) {
|
||||||
result.push(SGRValues[arg]);
|
result.push(SGRValues[arg]);
|
||||||
} else if(_.isNumber(arg)) {
|
} else if(_.isNumber(arg)) {
|
||||||
result.push(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.
|
// to a ANSI SGR sequence.
|
||||||
//
|
//
|
||||||
function getSGRFromGraphicRendition(graphicRendition, initialReset) {
|
function getSGRFromGraphicRendition(graphicRendition, initialReset) {
|
||||||
let sgrSeq = [];
|
let sgrSeq = [];
|
||||||
let styleCount = 0;
|
let styleCount = 0;
|
||||||
|
|
||||||
[ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach( s => {
|
[ 'intensity', 'underline', 'blink', 'negative', 'invisible' ].forEach( s => {
|
||||||
if(graphicRendition[s]) {
|
if(graphicRendition[s]) {
|
||||||
sgrSeq.push(graphicRendition[s]);
|
sgrSeq.push(graphicRendition[s]);
|
||||||
++styleCount;
|
++styleCount;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(graphicRendition.fg) {
|
if(graphicRendition.fg) {
|
||||||
sgrSeq.push(graphicRendition.fg);
|
sgrSeq.push(graphicRendition.fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(graphicRendition.bg) {
|
if(graphicRendition.bg) {
|
||||||
sgrSeq.push(graphicRendition.bg);
|
sgrSeq.push(graphicRendition.bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(0 === styleCount || initialReset) {
|
if(0 === styleCount || initialReset) {
|
||||||
sgrSeq.unshift(0);
|
sgrSeq.unshift(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sgr(sgrSeq);
|
return sgr(sgrSeq);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -435,19 +435,19 @@ function getSGRFromGraphicRendition(graphicRendition, initialReset) {
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
function clearScreen() {
|
function clearScreen() {
|
||||||
return exports.eraseData(2);
|
return exports.eraseData(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetScreen() {
|
function resetScreen() {
|
||||||
return `${exports.reset()}${exports.eraseData(2)}${exports.goHome()}`;
|
return `${exports.reset()}${exports.eraseData(2)}${exports.goHome()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normal() {
|
function normal() {
|
||||||
return sgr( [ 'normal', 'reset' ] );
|
return sgr( [ 'normal', 'reset' ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
function goHome() {
|
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!
|
// and use term width -- generally 80 columns -- will display garbled!
|
||||||
//
|
//
|
||||||
function disableVT100LineWrapping() {
|
function disableVT100LineWrapping() {
|
||||||
return `${ESC_CSI}?7l`;
|
return `${ESC_CSI}?7l`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setEmulatedBaudRate(rate) {
|
function setEmulatedBaudRate(rate) {
|
||||||
const speed = {
|
const speed = {
|
||||||
unlimited : 0,
|
unlimited : 0,
|
||||||
off : 0,
|
off : 0,
|
||||||
0 : 0,
|
0 : 0,
|
||||||
300 : 1,
|
300 : 1,
|
||||||
600 : 2,
|
600 : 2,
|
||||||
1200 : 3,
|
1200 : 3,
|
||||||
2400 : 4,
|
2400 : 4,
|
||||||
4800 : 5,
|
4800 : 5,
|
||||||
9600 : 6,
|
9600 : 6,
|
||||||
19200 : 7,
|
19200 : 7,
|
||||||
38400 : 8,
|
38400 : 8,
|
||||||
57600 : 9,
|
57600 : 9,
|
||||||
76800 : 10,
|
76800 : 10,
|
||||||
115200 : 11,
|
115200 : 11,
|
||||||
}[rate] || 0;
|
}[rate] || 0;
|
||||||
return 0 === speed ? exports.emulationSpeed() : exports.emulationSpeed(1, speed);
|
return 0 === speed ? exports.emulationSpeed() : exports.emulationSpeed(1, speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
function vtxHyperlink(client, url, len) {
|
function vtxHyperlink(client, url, len) {
|
||||||
if(!client.terminalSupports('vtx_hyperlink')) {
|
if(!client.terminalSupports('vtx_hyperlink')) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
len = len || url.length;
|
len = len || url.length;
|
||||||
|
|
||||||
url = url.split('').map(c => c.charCodeAt(0)).join(';');
|
url = url.split('').map(c => c.charCodeAt(0)).join(';');
|
||||||
return `${ESC_CSI}1;${len};1;1;${url}\\`;
|
return `${ESC_CSI}1;${len};1;1;${url}\\`;
|
||||||
}
|
}
|
|
@ -16,314 +16,314 @@ const paths = require('path');
|
||||||
let archiveUtil;
|
let archiveUtil;
|
||||||
|
|
||||||
class Archiver {
|
class Archiver {
|
||||||
constructor(config) {
|
constructor(config) {
|
||||||
this.compress = config.compress;
|
this.compress = config.compress;
|
||||||
this.decompress = config.decompress;
|
this.decompress = config.decompress;
|
||||||
this.list = config.list;
|
this.list = config.list;
|
||||||
this.extract = config.extract;
|
this.extract = config.extract;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok() {
|
ok() {
|
||||||
return this.canCompress() && this.canDecompress();
|
return this.canCompress() && this.canDecompress();
|
||||||
}
|
}
|
||||||
|
|
||||||
can(what) {
|
can(what) {
|
||||||
if(!_.has(this, [ what, 'cmd' ]) || !_.has(this, [ what, 'args' ])) {
|
if(!_.has(this, [ what, 'cmd' ]) || !_.has(this, [ what, 'args' ])) {
|
||||||
return false;
|
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'); }
|
canCompress() { return this.can('compress'); }
|
||||||
canDecompress() { return this.can('decompress'); }
|
canDecompress() { return this.can('decompress'); }
|
||||||
canList() { return this.can('list'); } // :TODO: validate entryMatch
|
canList() { return this.can('list'); } // :TODO: validate entryMatch
|
||||||
canExtract() { return this.can('extract'); }
|
canExtract() { return this.can('extract'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = class ArchiveUtil {
|
module.exports = class ArchiveUtil {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.archivers = {};
|
this.archivers = {};
|
||||||
this.longestSignature = 0;
|
this.longestSignature = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// singleton access
|
// singleton access
|
||||||
static getInstance() {
|
static getInstance() {
|
||||||
if(!archiveUtil) {
|
if(!archiveUtil) {
|
||||||
archiveUtil = new ArchiveUtil();
|
archiveUtil = new ArchiveUtil();
|
||||||
archiveUtil.init();
|
archiveUtil.init();
|
||||||
}
|
}
|
||||||
return archiveUtil;
|
return archiveUtil;
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
//
|
//
|
||||||
// Load configuration
|
// Load configuration
|
||||||
//
|
//
|
||||||
const config = Config();
|
const config = Config();
|
||||||
if(_.has(config, 'archives.archivers')) {
|
if(_.has(config, 'archives.archivers')) {
|
||||||
Object.keys(config.archives.archivers).forEach(archKey => {
|
Object.keys(config.archives.archivers).forEach(archKey => {
|
||||||
|
|
||||||
const archConfig = config.archives.archivers[archKey];
|
const archConfig = config.archives.archivers[archKey];
|
||||||
const archiver = new Archiver(archConfig);
|
const archiver = new Archiver(archConfig);
|
||||||
|
|
||||||
if(!archiver.ok()) {
|
if(!archiver.ok()) {
|
||||||
// :TODO: Log warning - bad archiver/config
|
// :TODO: Log warning - bad archiver/config
|
||||||
}
|
}
|
||||||
|
|
||||||
this.archivers[archKey] = archiver;
|
this.archivers[archKey] = archiver;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isObject(config.fileTypes)) {
|
if(_.isObject(config.fileTypes)) {
|
||||||
const updateSig = (ft) => {
|
const updateSig = (ft) => {
|
||||||
ft.sig = Buffer.from(ft.sig, 'hex');
|
ft.sig = Buffer.from(ft.sig, 'hex');
|
||||||
ft.offset = ft.offset || 0;
|
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
|
// :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;
|
const sigLen = ft.offset + ft.sig.length;
|
||||||
if(sigLen > this.longestSignature) {
|
if(sigLen > this.longestSignature) {
|
||||||
this.longestSignature = sigLen;
|
this.longestSignature = sigLen;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.keys(config.fileTypes).forEach(mimeType => {
|
Object.keys(config.fileTypes).forEach(mimeType => {
|
||||||
const fileType = config.fileTypes[mimeType];
|
const fileType = config.fileTypes[mimeType];
|
||||||
if(Array.isArray(fileType)) {
|
if(Array.isArray(fileType)) {
|
||||||
fileType.forEach(ft => {
|
fileType.forEach(ft => {
|
||||||
if(ft.sig) {
|
if(ft.sig) {
|
||||||
updateSig(ft);
|
updateSig(ft);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if(fileType.sig) {
|
} else if(fileType.sig) {
|
||||||
updateSig(fileType);
|
updateSig(fileType);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getArchiver(mimeTypeOrExtension, justExtention) {
|
getArchiver(mimeTypeOrExtension, justExtention) {
|
||||||
const mimeType = resolveMimeType(mimeTypeOrExtension);
|
const mimeType = resolveMimeType(mimeTypeOrExtension);
|
||||||
|
|
||||||
if(!mimeType) { // lookup returns false on failure
|
if(!mimeType) { // lookup returns false on failure
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = Config();
|
const config = Config();
|
||||||
let fileType = _.get(config, [ 'fileTypes', mimeType ] );
|
let fileType = _.get(config, [ 'fileTypes', mimeType ] );
|
||||||
|
|
||||||
if(Array.isArray(fileType)) {
|
if(Array.isArray(fileType)) {
|
||||||
if(!justExtention) {
|
if(!justExtention) {
|
||||||
// need extention for lookup; ambiguous as-is :(
|
// need extention for lookup; ambiguous as-is :(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// further refine by extention
|
// further refine by extention
|
||||||
fileType = fileType.find(ft => justExtention === ft.ext);
|
fileType = fileType.find(ft => justExtention === ft.ext);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isObject(fileType)) {
|
if(!_.isObject(fileType)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(fileType.archiveHandler) {
|
if(fileType.archiveHandler) {
|
||||||
return _.get( config, [ 'archives', 'archivers', fileType.archiveHandler ] );
|
return _.get( config, [ 'archives', 'archivers', fileType.archiveHandler ] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
haveArchiver(archType) {
|
haveArchiver(archType) {
|
||||||
return this.getArchiver(archType) ? true : false;
|
return this.getArchiver(archType) ? true : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: implement me:
|
// :TODO: implement me:
|
||||||
/*
|
/*
|
||||||
detectTypeWithBuf(buf, cb) {
|
detectTypeWithBuf(buf, cb) {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
detectType(path, cb) {
|
detectType(path, cb) {
|
||||||
fs.open(path, 'r', (err, fd) => {
|
fs.open(path, 'r', (err, fd) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buf = Buffer.alloc(this.longestSignature);
|
const buf = Buffer.alloc(this.longestSignature);
|
||||||
fs.read(fd, buf, 0, buf.length, 0, (err, bytesRead) => {
|
fs.read(fd, buf, 0, buf.length, 0, (err, bytesRead) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => {
|
const archFormat = _.findKey(Config().fileTypes, fileTypeInfo => {
|
||||||
const fileTypeInfos = Array.isArray(fileTypeInfo) ? fileTypeInfo : [ fileTypeInfo ];
|
const fileTypeInfos = Array.isArray(fileTypeInfo) ? fileTypeInfo : [ fileTypeInfo ];
|
||||||
return fileTypeInfos.find(fti => {
|
return fileTypeInfos.find(fti => {
|
||||||
if(!fti.sig || !fti.archiveHandler) {
|
if(!fti.sig || !fti.archiveHandler) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const lenNeeded = fti.offset + fti.sig.length;
|
const lenNeeded = fti.offset + fti.sig.length;
|
||||||
|
|
||||||
if(bytesRead < lenNeeded) {
|
if(bytesRead < lenNeeded) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const comp = buf.slice(fti.offset, fti.offset + fti.sig.length);
|
const comp = buf.slice(fti.offset, fti.offset + fti.sig.length);
|
||||||
return (fti.sig.equals(comp));
|
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) {
|
spawnHandler(proc, action, cb) {
|
||||||
// pty.js doesn't currently give us a error when things fail,
|
// pty.js doesn't currently give us a error when things fail,
|
||||||
// so we have this horrible, horrible hack:
|
// so we have this horrible, horrible hack:
|
||||||
let err;
|
let err;
|
||||||
proc.once('data', d => {
|
proc.once('data', d => {
|
||||||
if(_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
if(_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
||||||
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
proc.once('exit', exitCode => {
|
proc.once('exit', exitCode => {
|
||||||
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err);
|
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
compressTo(archType, archivePath, files, cb) {
|
compressTo(archType, archivePath, files, cb) {
|
||||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
archivePath : archivePath,
|
archivePath : archivePath,
|
||||||
fileList : files.join(' '), // :TODO: probably need same hack as extractTo here!
|
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;
|
let proc;
|
||||||
try {
|
try {
|
||||||
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
|
proc = pty.spawn(archiver.compress.cmd, args, this.getPtyOpts());
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.spawnHandler(proc, 'Compression', cb);
|
return this.spawnHandler(proc, 'Compression', cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTo(archivePath, extractPath, archType, fileList, cb) {
|
extractTo(archivePath, extractPath, archType, fileList, cb) {
|
||||||
let haveFileList;
|
let haveFileList;
|
||||||
|
|
||||||
if(!cb && _.isFunction(fileList)) {
|
if(!cb && _.isFunction(fileList)) {
|
||||||
cb = fileList;
|
cb = fileList;
|
||||||
fileList = [];
|
fileList = [];
|
||||||
haveFileList = false;
|
haveFileList = false;
|
||||||
} else {
|
} else {
|
||||||
haveFileList = true;
|
haveFileList = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
archivePath : archivePath,
|
archivePath : archivePath,
|
||||||
extractPath : extractPath,
|
extractPath : extractPath,
|
||||||
};
|
};
|
||||||
|
|
||||||
let action = haveFileList ? 'extract' : 'decompress';
|
let action = haveFileList ? 'extract' : 'decompress';
|
||||||
if('extract' === action && !_.isObject(archiver[action])) {
|
if('extract' === action && !_.isObject(archiver[action])) {
|
||||||
// we're forced to do a full decompress
|
// we're forced to do a full decompress
|
||||||
action = 'decompress';
|
action = 'decompress';
|
||||||
haveFileList = false;
|
haveFileList = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to treat {fileList} special in that it should be broken up to 0:n args
|
// we need to treat {fileList} special in that it should be broken up to 0:n args
|
||||||
const args = archiver[action].args.map( arg => {
|
const args = archiver[action].args.map( arg => {
|
||||||
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
return '{fileList}' === arg ? arg : stringFormat(arg, fmtObj);
|
||||||
});
|
});
|
||||||
|
|
||||||
const fileListPos = args.indexOf('{fileList}');
|
const fileListPos = args.indexOf('{fileList}');
|
||||||
if(fileListPos > -1) {
|
if(fileListPos > -1) {
|
||||||
// replace {fileList} with 0:n sep file list arguments
|
// replace {fileList} with 0:n sep file list arguments
|
||||||
args.splice.apply(args, [fileListPos, 1].concat(fileList));
|
args.splice.apply(args, [fileListPos, 1].concat(fileList));
|
||||||
}
|
}
|
||||||
|
|
||||||
let proc;
|
let proc;
|
||||||
try {
|
try {
|
||||||
proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath));
|
proc = pty.spawn(archiver[action].cmd, args, this.getPtyOpts(extractPath));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.spawnHandler(proc, (haveFileList ? 'Extraction' : 'Decompression'), cb);
|
return this.spawnHandler(proc, (haveFileList ? 'Extraction' : 'Decompression'), cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
listEntries(archivePath, archType, cb) {
|
listEntries(archivePath, archType, cb) {
|
||||||
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
const archiver = this.getArchiver(archType, paths.extname(archivePath));
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
archivePath : archivePath,
|
archivePath : archivePath,
|
||||||
};
|
};
|
||||||
|
|
||||||
const args = archiver.list.args.map( arg => stringFormat(arg, fmtObj) );
|
const args = archiver.list.args.map( arg => stringFormat(arg, fmtObj) );
|
||||||
|
|
||||||
let proc;
|
let proc;
|
||||||
try {
|
try {
|
||||||
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
|
proc = pty.spawn(archiver.list.cmd, args, this.getPtyOpts());
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = '';
|
let output = '';
|
||||||
proc.on('data', data => {
|
proc.on('data', data => {
|
||||||
// :TODO: hack for: execvp(3) failed.: No such file or directory
|
// :TODO: hack for: execvp(3) failed.: No such file or directory
|
||||||
|
|
||||||
output += data;
|
output += data;
|
||||||
});
|
});
|
||||||
|
|
||||||
proc.once('exit', exitCode => {
|
proc.once('exit', exitCode => {
|
||||||
if(exitCode) {
|
if(exitCode) {
|
||||||
return cb(Errors.ExternalProcess(`List failed with exit code: ${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 entries = [];
|
||||||
const entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm');
|
const entryMatchRe = new RegExp(archiver.list.entryMatch, 'gm');
|
||||||
let m;
|
let m;
|
||||||
while((m = entryMatchRe.exec(output))) {
|
while((m = entryMatchRe.exec(output))) {
|
||||||
entries.push({
|
entries.push({
|
||||||
byteSize : parseInt(m[entryGroupOrder.byteSize]),
|
byteSize : parseInt(m[entryGroupOrder.byteSize]),
|
||||||
fileName : m[entryGroupOrder.fileName].trim(),
|
fileName : m[entryGroupOrder.fileName].trim(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null, entries);
|
return cb(null, entries);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getPtyOpts(extractPath) {
|
getPtyOpts(extractPath) {
|
||||||
const opts = {
|
const opts = {
|
||||||
name : 'enigma-archiver',
|
name : 'enigma-archiver',
|
||||||
cols : 80,
|
cols : 80,
|
||||||
rows : 24,
|
rows : 24,
|
||||||
env : process.env,
|
env : process.env,
|
||||||
};
|
};
|
||||||
if(extractPath) {
|
if(extractPath) {
|
||||||
opts.cwd = extractPath;
|
opts.cwd = extractPath;
|
||||||
}
|
}
|
||||||
// :TODO: set cwd to supplied temp path if not sepcific extract
|
// :TODO: set cwd to supplied temp path if not sepcific extract
|
||||||
return opts;
|
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
|
// :TODO: return font + font mapped information from SAUCE
|
||||||
|
|
||||||
const SUPPORTED_ART_TYPES = {
|
const SUPPORTED_ART_TYPES = {
|
||||||
// :TODO: the defualt encoding are really useless if they are all the same ...
|
// :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
|
// perhaps .ansamiga and .ascamiga could be supported as well as overrides via conf
|
||||||
'.ans' : { name : 'ANSI', defaultEncoding : 'cp437', eof : 0x1a },
|
'.ans' : { name : 'ANSI', defaultEncoding : 'cp437', eof : 0x1a },
|
||||||
'.asc' : { name : 'ASCII', defaultEncoding : 'cp437', eof : 0x1a },
|
'.asc' : { name : 'ASCII', defaultEncoding : 'cp437', eof : 0x1a },
|
||||||
'.pcb' : { name : 'PCBoard', defaultEncoding : 'cp437', eof : 0x1a },
|
'.pcb' : { name : 'PCBoard', defaultEncoding : 'cp437', eof : 0x1a },
|
||||||
'.bbs' : { name : 'Wildcat', defaultEncoding : 'cp437', eof : 0x1a },
|
'.bbs' : { name : 'Wildcat', defaultEncoding : 'cp437', eof : 0x1a },
|
||||||
|
|
||||||
'.amiga' : { name : 'Amiga', defaultEncoding : 'amiga', eof : 0x1a },
|
'.amiga' : { name : 'Amiga', defaultEncoding : 'amiga', eof : 0x1a },
|
||||||
'.txt' : { name : 'Amiga Text', defaultEncoding : 'cp437', eof : 0x1a },
|
'.txt' : { name : 'Amiga Text', defaultEncoding : 'cp437', eof : 0x1a },
|
||||||
// :TODO: extentions for wwiv, renegade, celerity, syncronet, ...
|
// :TODO: extentions for wwiv, renegade, celerity, syncronet, ...
|
||||||
// :TODO: extension for atari
|
// :TODO: extension for atari
|
||||||
// :TODO: extension for topaz ansi/ascii.
|
// :TODO: extension for topaz ansi/ascii.
|
||||||
};
|
};
|
||||||
|
|
||||||
function getFontNameFromSAUCE(sauce) {
|
function getFontNameFromSAUCE(sauce) {
|
||||||
if(sauce.Character) {
|
if(sauce.Character) {
|
||||||
return sauce.Character.fontName;
|
return sauce.Character.fontName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sliceAtEOF(data, eofMarker) {
|
function sliceAtEOF(data, eofMarker) {
|
||||||
let eof = data.length;
|
let eof = data.length;
|
||||||
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE)
|
const stopPos = Math.max(data.length - (256), 0); // 256 = 2 * sizeof(SAUCE)
|
||||||
|
|
||||||
for(let i = eof - 1; i > stopPos; i--) {
|
for(let i = eof - 1; i > stopPos; i--) {
|
||||||
if(eofMarker === data[i]) {
|
if(eofMarker === data[i]) {
|
||||||
eof = i;
|
eof = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data.slice(0, eof);
|
return data.slice(0, eof);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArtFromPath(path, options, cb) {
|
function getArtFromPath(path, options, cb) {
|
||||||
fs.readFile(path, (err, data) => {
|
fs.readFile(path, (err, data) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Convert from encodedAs -> j
|
// Convert from encodedAs -> j
|
||||||
//
|
//
|
||||||
const ext = paths.extname(path).toLowerCase();
|
const ext = paths.extname(path).toLowerCase();
|
||||||
const encoding = options.encodedAs || defaultEncodingFromExtension(ext);
|
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() {
|
function sliceOfData() {
|
||||||
if(options.fullFile === true) {
|
if(options.fullFile === true) {
|
||||||
return iconv.decode(data, encoding);
|
return iconv.decode(data, encoding);
|
||||||
} else {
|
} else {
|
||||||
const eofMarker = defaultEofFromExtension(ext);
|
const eofMarker = defaultEofFromExtension(ext);
|
||||||
return iconv.decode(eofMarker ? sliceAtEOF(data, eofMarker) : data, encoding);
|
return iconv.decode(eofMarker ? sliceAtEOF(data, eofMarker) : data, encoding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getResult(sauce) {
|
function getResult(sauce) {
|
||||||
const result = {
|
const result = {
|
||||||
data : sliceOfData(),
|
data : sliceOfData(),
|
||||||
fromPath : path,
|
fromPath : path,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(sauce) {
|
if(sauce) {
|
||||||
result.sauce = sauce;
|
result.sauce = sauce;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.readSauce === true) {
|
if(options.readSauce === true) {
|
||||||
sauce.readSAUCE(data, (err, sauce) => {
|
sauce.readSAUCE(data, (err, sauce) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(null, getResult());
|
return cb(null, getResult());
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// If a encoding was not provided & we have a mapping from
|
// If a encoding was not provided & we have a mapping from
|
||||||
// the information provided by SAUCE, use that.
|
// the information provided by SAUCE, use that.
|
||||||
//
|
//
|
||||||
if(!options.encodedAs) {
|
if(!options.encodedAs) {
|
||||||
/*
|
/*
|
||||||
if(sauce.Character && sauce.Character.fontName) {
|
if(sauce.Character && sauce.Character.fontName) {
|
||||||
var enc = SAUCE_FONT_TO_ENCODING_HINT[sauce.Character.fontName];
|
var enc = SAUCE_FONT_TO_ENCODING_HINT[sauce.Character.fontName];
|
||||||
if(enc) {
|
if(enc) {
|
||||||
|
@ -114,115 +114,115 @@ function getArtFromPath(path, options, cb) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
return cb(null, getResult(sauce));
|
return cb(null, getResult(sauce));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return cb(null, getResult());
|
return cb(null, getResult());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArt(name, options, cb) {
|
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.basePath = miscUtil.valueWithDefault(options.basePath, Config().paths.art);
|
||||||
options.asAnsi = miscUtil.valueWithDefault(options.asAnsi, true);
|
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) {
|
if('' !== ext) {
|
||||||
options.types = [ ext.toLowerCase() ];
|
options.types = [ ext.toLowerCase() ];
|
||||||
} else {
|
} else {
|
||||||
if(_.isUndefined(options.types)) {
|
if(_.isUndefined(options.types)) {
|
||||||
options.types = Object.keys(SUPPORTED_ART_TYPES);
|
options.types = Object.keys(SUPPORTED_ART_TYPES);
|
||||||
} else if(_.isString(options.types)) {
|
} else if(_.isString(options.types)) {
|
||||||
options.types = [ options.types.toLowerCase() ];
|
options.types = [ options.types.toLowerCase() ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an extension is provided, just read the file now
|
// If an extension is provided, just read the file now
|
||||||
if('' !== ext) {
|
if('' !== ext) {
|
||||||
const directPath = paths.join(options.basePath, name);
|
const directPath = paths.join(options.basePath, name);
|
||||||
return getArtFromPath(directPath, options, cb);
|
return getArtFromPath(directPath, options, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.readdir(options.basePath, (err, files) => {
|
fs.readdir(options.basePath, (err, files) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = files.filter( file => {
|
const filtered = files.filter( file => {
|
||||||
//
|
//
|
||||||
// Ignore anything not allowed in |options.types|
|
// Ignore anything not allowed in |options.types|
|
||||||
//
|
//
|
||||||
const fext = paths.extname(file);
|
const fext = paths.extname(file);
|
||||||
if(!options.types.includes(fext.toLowerCase())) {
|
if(!options.types.includes(fext.toLowerCase())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bn = paths.basename(file, fext).toLowerCase();
|
const bn = paths.basename(file, fext).toLowerCase();
|
||||||
if(options.random) {
|
if(options.random) {
|
||||||
const suppliedBn = paths.basename(name, fext).toLowerCase();
|
const suppliedBn = paths.basename(name, fext).toLowerCase();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Random selection enabled. We'll allow for
|
// Random selection enabled. We'll allow for
|
||||||
// basename1.ext, basename2.ext, ...
|
// basename1.ext, basename2.ext, ...
|
||||||
//
|
//
|
||||||
if(!bn.startsWith(suppliedBn)) {
|
if(!bn.startsWith(suppliedBn)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = bn.substr(suppliedBn.length);
|
const num = bn.substr(suppliedBn.length);
|
||||||
if(num.length > 0) {
|
if(num.length > 0) {
|
||||||
if(isNaN(parseInt(num, 10))) {
|
if(isNaN(parseInt(num, 10))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
// We've already validated the extension (above). Must be an exact
|
// We've already validated the extension (above). Must be an exact
|
||||||
// match to basename here
|
// match to basename here
|
||||||
//
|
//
|
||||||
if(bn != paths.basename(name, fext).toLowerCase()) {
|
if(bn != paths.basename(name, fext).toLowerCase()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if(filtered.length > 0) {
|
if(filtered.length > 0) {
|
||||||
//
|
//
|
||||||
// We should now have:
|
// We should now have:
|
||||||
// - Exactly (1) item in |filtered| if non-random
|
// - Exactly (1) item in |filtered| if non-random
|
||||||
// - 1:n items in |filtered| to choose from if random
|
// - 1:n items in |filtered| to choose from if random
|
||||||
//
|
//
|
||||||
let readPath;
|
let readPath;
|
||||||
if(options.random) {
|
if(options.random) {
|
||||||
readPath = paths.join(options.basePath, filtered[Math.floor(Math.random() * filtered.length)]);
|
readPath = paths.join(options.basePath, filtered[Math.floor(Math.random() * filtered.length)]);
|
||||||
} else {
|
} else {
|
||||||
assert(1 === filtered.length);
|
assert(1 === filtered.length);
|
||||||
readPath = paths.join(options.basePath, filtered[0]);
|
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) {
|
function defaultEncodingFromExtension(ext) {
|
||||||
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
||||||
return artType ? artType.defaultEncoding : 'utf8';
|
return artType ? artType.defaultEncoding : 'utf8';
|
||||||
}
|
}
|
||||||
|
|
||||||
function defaultEofFromExtension(ext) {
|
function defaultEofFromExtension(ext) {
|
||||||
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
const artType = SUPPORTED_ART_TYPES[ext.toLowerCase()];
|
||||||
if(artType) {
|
if(artType) {
|
||||||
return artType.eof;
|
return artType.eof;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: Implement the following
|
// :TODO: Implement the following
|
||||||
|
@ -230,161 +230,161 @@ function defaultEofFromExtension(ext) {
|
||||||
// * Cancel (disabled | <keys> )
|
// * Cancel (disabled | <keys> )
|
||||||
// * Resume from pause -> continous (disabled | <keys>)
|
// * Resume from pause -> continous (disabled | <keys>)
|
||||||
function display(client, art, options, cb) {
|
function display(client, art, options, cb) {
|
||||||
if(_.isFunction(options) && !cb) {
|
if(_.isFunction(options) && !cb) {
|
||||||
cb = options;
|
cb = options;
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!art || !art.length) {
|
if(!art || !art.length) {
|
||||||
return cb(new Error('Empty art'));
|
return cb(new Error('Empty art'));
|
||||||
}
|
}
|
||||||
|
|
||||||
options.mciReplaceChar = options.mciReplaceChar || ' ';
|
options.mciReplaceChar = options.mciReplaceChar || ' ';
|
||||||
options.disableMciCache = options.disableMciCache || false;
|
options.disableMciCache = options.disableMciCache || false;
|
||||||
|
|
||||||
// :TODO: this is going to be broken into two approaches controlled via options:
|
// :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.
|
// 1) Standard - use internal tracking of locations for MCI -- no CPR's/etc.
|
||||||
// 2) CPR driven
|
// 2) CPR driven
|
||||||
|
|
||||||
if(!_.isBoolean(options.iceColors)) {
|
if(!_.isBoolean(options.iceColors)) {
|
||||||
// try to detect from SAUCE
|
// try to detect from SAUCE
|
||||||
if(_.has(options, 'sauce.ansiFlags') && (options.sauce.ansiFlags & (1 << 0))) {
|
if(_.has(options, 'sauce.ansiFlags') && (options.sauce.ansiFlags & (1 << 0))) {
|
||||||
options.iceColors = true;
|
options.iceColors = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ansiParser = new aep.ANSIEscapeParser({
|
const ansiParser = new aep.ANSIEscapeParser({
|
||||||
mciReplaceChar : options.mciReplaceChar,
|
mciReplaceChar : options.mciReplaceChar,
|
||||||
termHeight : client.term.termHeight,
|
termHeight : client.term.termHeight,
|
||||||
termWidth : client.term.termWidth,
|
termWidth : client.term.termWidth,
|
||||||
trailingLF : options.trailingLF,
|
trailingLF : options.trailingLF,
|
||||||
});
|
});
|
||||||
|
|
||||||
let parseComplete = false;
|
let parseComplete = false;
|
||||||
let cprListener;
|
let cprListener;
|
||||||
let mciMap;
|
let mciMap;
|
||||||
const mciCprQueue = [];
|
const mciCprQueue = [];
|
||||||
let artHash;
|
let artHash;
|
||||||
let mciMapFromCache;
|
let mciMapFromCache;
|
||||||
|
|
||||||
function completed() {
|
function completed() {
|
||||||
if(cprListener) {
|
if(cprListener) {
|
||||||
client.removeListener('cursor position report', cprListener);
|
client.removeListener('cursor position report', cprListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!options.disableMciCache && !mciMapFromCache) {
|
if(!options.disableMciCache && !mciMapFromCache) {
|
||||||
// cache our MCI findings...
|
// cache our MCI findings...
|
||||||
client.mciCache[artHash] = mciMap;
|
client.mciCache[artHash] = mciMap;
|
||||||
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Added MCI map to cache');
|
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Added MCI map to cache');
|
||||||
}
|
}
|
||||||
|
|
||||||
ansiParser.removeAllListeners(); // :TODO: Necessary???
|
ansiParser.removeAllListeners(); // :TODO: Necessary???
|
||||||
|
|
||||||
const extraInfo = {
|
const extraInfo = {
|
||||||
height : ansiParser.row - 1,
|
height : ansiParser.row - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
return cb(null, mciMap, extraInfo);
|
return cb(null, mciMap, extraInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!options.disableMciCache) {
|
if(!options.disableMciCache) {
|
||||||
artHash = xxhash.hash(Buffer.from(art), 0xCAFEBABE);
|
artHash = xxhash.hash(Buffer.from(art), 0xCAFEBABE);
|
||||||
|
|
||||||
// see if we have a mciMap cached for this art
|
// see if we have a mciMap cached for this art
|
||||||
if(client.mciCache) {
|
if(client.mciCache) {
|
||||||
mciMap = client.mciCache[artHash];
|
mciMap = client.mciCache[artHash];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mciMap) {
|
if(mciMap) {
|
||||||
mciMapFromCache = true;
|
mciMapFromCache = true;
|
||||||
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Loaded MCI map from cache');
|
client.log.trace( { artHash : artHash.toString(16), mciMap : mciMap }, 'Loaded MCI map from cache');
|
||||||
} else {
|
} else {
|
||||||
// no cached MCI info
|
// no cached MCI info
|
||||||
mciMap = {};
|
mciMap = {};
|
||||||
|
|
||||||
cprListener = function(pos) {
|
cprListener = function(pos) {
|
||||||
if(mciCprQueue.length > 0) {
|
if(mciCprQueue.length > 0) {
|
||||||
mciMap[mciCprQueue.shift()].position = pos;
|
mciMap[mciCprQueue.shift()].position = pos;
|
||||||
|
|
||||||
if(parseComplete && 0 === mciCprQueue.length) {
|
if(parseComplete && 0 === mciCprQueue.length) {
|
||||||
return completed();
|
return completed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
client.on('cursor position report', cprListener);
|
client.on('cursor position report', cprListener);
|
||||||
|
|
||||||
let generatedId = 100;
|
let generatedId = 100;
|
||||||
|
|
||||||
ansiParser.on('mci', mciInfo => {
|
ansiParser.on('mci', mciInfo => {
|
||||||
// :TODO: ensure generatedId's do not conflict with any existing |id|
|
// :TODO: ensure generatedId's do not conflict with any existing |id|
|
||||||
const id = _.isNumber(mciInfo.id) ? mciInfo.id : generatedId;
|
const id = _.isNumber(mciInfo.id) ? mciInfo.id : generatedId;
|
||||||
const mapKey = `${mciInfo.mci}${id}`;
|
const mapKey = `${mciInfo.mci}${id}`;
|
||||||
const mapEntry = mciMap[mapKey];
|
const mapEntry = mciMap[mapKey];
|
||||||
|
|
||||||
if(mapEntry) {
|
if(mapEntry) {
|
||||||
mapEntry.focusSGR = mciInfo.SGR;
|
mapEntry.focusSGR = mciInfo.SGR;
|
||||||
mapEntry.focusArgs = mciInfo.args;
|
mapEntry.focusArgs = mciInfo.args;
|
||||||
} else {
|
} else {
|
||||||
mciMap[mapKey] = {
|
mciMap[mapKey] = {
|
||||||
args : mciInfo.args,
|
args : mciInfo.args,
|
||||||
SGR : mciInfo.SGR,
|
SGR : mciInfo.SGR,
|
||||||
code : mciInfo.mci,
|
code : mciInfo.mci,
|
||||||
id : id,
|
id : id,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!mciInfo.id) {
|
if(!mciInfo.id) {
|
||||||
++generatedId;
|
++generatedId;
|
||||||
}
|
}
|
||||||
|
|
||||||
mciCprQueue.push(mapKey);
|
mciCprQueue.push(mapKey);
|
||||||
client.term.rawWrite(ansi.queryPos());
|
client.term.rawWrite(ansi.queryPos());
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ansiParser.on('literal', literal => client.term.write(literal, false) );
|
ansiParser.on('literal', literal => client.term.write(literal, false) );
|
||||||
ansiParser.on('control', control => client.term.rawWrite(control) );
|
ansiParser.on('control', control => client.term.rawWrite(control) );
|
||||||
|
|
||||||
ansiParser.on('complete', () => {
|
ansiParser.on('complete', () => {
|
||||||
parseComplete = true;
|
parseComplete = true;
|
||||||
|
|
||||||
if(0 === mciCprQueue.length) {
|
if(0 === mciCprQueue.length) {
|
||||||
return completed();
|
return completed();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let initSeq = '';
|
let initSeq = '';
|
||||||
if(options.font) {
|
if(options.font) {
|
||||||
initSeq = ansi.setSyncTermFontWithAlias(options.font);
|
initSeq = ansi.setSyncTermFontWithAlias(options.font);
|
||||||
} else if(options.sauce) {
|
} else if(options.sauce) {
|
||||||
let fontName = getFontNameFromSAUCE(options.sauce);
|
let fontName = getFontNameFromSAUCE(options.sauce);
|
||||||
if(fontName) {
|
if(fontName) {
|
||||||
fontName = ansi.getSyncTERMFontFromAlias(fontName);
|
fontName = ansi.getSyncTERMFontFromAlias(fontName);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Set SyncTERM font if we're switching only. Most terminals
|
// Set SyncTERM font if we're switching only. Most terminals
|
||||||
// that support this ESC sequence can only show *one* font
|
// that support this ESC sequence can only show *one* font
|
||||||
// at a time. This applies to detection only (e.g. SAUCE).
|
// at a time. This applies to detection only (e.g. SAUCE).
|
||||||
// If explicit, we'll set it no matter what (above)
|
// If explicit, we'll set it no matter what (above)
|
||||||
//
|
//
|
||||||
if(fontName && client.term.currentSyncFont != fontName) {
|
if(fontName && client.term.currentSyncFont != fontName) {
|
||||||
client.term.currentSyncFont = fontName;
|
client.term.currentSyncFont = fontName;
|
||||||
initSeq = ansi.setSyncTERMFont(fontName);
|
initSeq = ansi.setSyncTERMFont(fontName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.iceColors) {
|
if(options.iceColors) {
|
||||||
initSeq += ansi.blinkToBrightIntensity();
|
initSeq += ansi.blinkToBrightIntensity();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(initSeq) {
|
if(initSeq) {
|
||||||
client.term.rawWrite(initSeq);
|
client.term.rawWrite(initSeq);
|
||||||
}
|
}
|
||||||
|
|
||||||
ansiParser.reset(art);
|
ansiParser.reset(art);
|
||||||
return ansiParser.parse();
|
return ansiParser.parse();
|
||||||
}
|
}
|
||||||
|
|
138
core/asset.js
138
core/asset.js
|
@ -18,111 +18,111 @@ exports.resolveSystemStatAsset = resolveSystemStatAsset;
|
||||||
exports.getViewPropertyAsset = getViewPropertyAsset;
|
exports.getViewPropertyAsset = getViewPropertyAsset;
|
||||||
|
|
||||||
const ALL_ASSETS = [
|
const ALL_ASSETS = [
|
||||||
'art',
|
'art',
|
||||||
'menu',
|
'menu',
|
||||||
'method',
|
'method',
|
||||||
'userModule',
|
'userModule',
|
||||||
'systemMethod',
|
'systemMethod',
|
||||||
'systemModule',
|
'systemModule',
|
||||||
'prompt',
|
'prompt',
|
||||||
'config',
|
'config',
|
||||||
'sysStat',
|
'sysStat',
|
||||||
];
|
];
|
||||||
|
|
||||||
const ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*');
|
const ASSET_RE = new RegExp('\\@(' + ALL_ASSETS.join('|') + ')\\:([\\w\\d\\.]*)(?:\\/([\\w\\d\\_]+))*');
|
||||||
|
|
||||||
function parseAsset(s) {
|
function parseAsset(s) {
|
||||||
const m = ASSET_RE.exec(s);
|
const m = ASSET_RE.exec(s);
|
||||||
|
|
||||||
if(m) {
|
if(m) {
|
||||||
let result = { type : m[1] };
|
let result = { type : m[1] };
|
||||||
|
|
||||||
if(m[3]) {
|
if(m[3]) {
|
||||||
result.location = m[2];
|
result.location = m[2];
|
||||||
result.asset = m[3];
|
result.asset = m[3];
|
||||||
} else {
|
} else {
|
||||||
result.asset = m[2];
|
result.asset = m[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAssetWithShorthand(spec, defaultType) {
|
function getAssetWithShorthand(spec, defaultType) {
|
||||||
if(!_.isString(spec)) {
|
if(!_.isString(spec)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if('@' === spec[0]) {
|
if('@' === spec[0]) {
|
||||||
const asset = parseAsset(spec);
|
const asset = parseAsset(spec);
|
||||||
assert(_.isString(asset.type));
|
assert(_.isString(asset.type));
|
||||||
|
|
||||||
return asset;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type : defaultType,
|
type : defaultType,
|
||||||
asset : spec,
|
asset : spec,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArtAsset(spec) {
|
function getArtAsset(spec) {
|
||||||
const asset = getAssetWithShorthand(spec, 'art');
|
const asset = getAssetWithShorthand(spec, 'art');
|
||||||
|
|
||||||
if(!asset) {
|
if(!asset) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( ['art', 'method' ].indexOf(asset.type) > -1);
|
assert( ['art', 'method' ].indexOf(asset.type) > -1);
|
||||||
return asset;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModuleAsset(spec) {
|
function getModuleAsset(spec) {
|
||||||
const asset = getAssetWithShorthand(spec, 'systemModule');
|
const asset = getAssetWithShorthand(spec, 'systemModule');
|
||||||
|
|
||||||
if(!asset) {
|
if(!asset) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( ['userModule', 'systemModule' ].includes(asset.type) );
|
assert( ['userModule', 'systemModule' ].includes(asset.type) );
|
||||||
|
|
||||||
return asset;
|
return asset;
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveConfigAsset(spec) {
|
function resolveConfigAsset(spec) {
|
||||||
const asset = parseAsset(spec);
|
const asset = parseAsset(spec);
|
||||||
if(asset) {
|
if(asset) {
|
||||||
assert('config' === asset.type);
|
assert('config' === asset.type);
|
||||||
|
|
||||||
const path = asset.asset.split('.');
|
const path = asset.asset.split('.');
|
||||||
let conf = Config();
|
let conf = Config();
|
||||||
for(let i = 0; i < path.length; ++i) {
|
for(let i = 0; i < path.length; ++i) {
|
||||||
if(_.isUndefined(conf[path[i]])) {
|
if(_.isUndefined(conf[path[i]])) {
|
||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
conf = conf[path[i]];
|
conf = conf[path[i]];
|
||||||
}
|
}
|
||||||
return conf;
|
return conf;
|
||||||
} else {
|
} else {
|
||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveSystemStatAsset(spec) {
|
function resolveSystemStatAsset(spec) {
|
||||||
const asset = parseAsset(spec);
|
const asset = parseAsset(spec);
|
||||||
if(!asset) {
|
if(!asset) {
|
||||||
return spec;
|
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) {
|
function getViewPropertyAsset(src) {
|
||||||
if(!_.isString(src) || '@' !== src.charAt(0)) {
|
if(!_.isString(src) || '@' !== src.charAt(0)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseAsset(src);
|
return parseAsset(src);
|
||||||
}
|
}
|
||||||
|
|
452
core/bbs.js
452
core/bbs.js
|
@ -41,253 +41,253 @@ valid args:
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function printHelpAndExit() {
|
function printHelpAndExit() {
|
||||||
console.info(HELP);
|
console.info(HELP);
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function processArgs(callback) {
|
function processArgs(callback) {
|
||||||
const argv = require('minimist')(process.argv.slice(2));
|
const argv = require('minimist')(process.argv.slice(2));
|
||||||
|
|
||||||
if(argv.help) {
|
if(argv.help) {
|
||||||
printHelpAndExit();
|
printHelpAndExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const configOverridePath = argv.config;
|
const configOverridePath = argv.config;
|
||||||
|
|
||||||
return callback(null, configOverridePath || conf.getDefaultPath(), _.isString(configOverridePath));
|
return callback(null, configOverridePath || conf.getDefaultPath(), _.isString(configOverridePath));
|
||||||
},
|
},
|
||||||
function initConfig(configPath, configPathSupplied, callback) {
|
function initConfig(configPath, configPathSupplied, callback) {
|
||||||
const configFile = configPath + 'config.hjson';
|
const configFile = configPath + 'config.hjson';
|
||||||
conf.init(resolvePath(configFile), function configInit(err) {
|
conf.init(resolvePath(configFile), function configInit(err) {
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the user supplied a path and we can't read/parse it
|
// If the user supplied a path and we can't read/parse it
|
||||||
// then it's a fatal error
|
// then it's a fatal error
|
||||||
//
|
//
|
||||||
if(err) {
|
if(err) {
|
||||||
if('ENOENT' === err.code) {
|
if('ENOENT' === err.code) {
|
||||||
if(configPathSupplied) {
|
if(configPathSupplied) {
|
||||||
console.error('Configuration file does not exist: ' + configFile);
|
console.error('Configuration file does not exist: ' + configFile);
|
||||||
} else {
|
} else {
|
||||||
configPathSupplied = null; // make non-fatal; we'll go with defaults
|
configPathSupplied = null; // make non-fatal; we'll go with defaults
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error(err.toString());
|
console.error(err.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function initSystem(callback) {
|
function initSystem(callback) {
|
||||||
initialize(function init(err) {
|
initialize(function init(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error('Error initializing: ' + util.inspect(err));
|
console.error('Error initializing: ' + util.inspect(err));
|
||||||
}
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
// note this is escaped:
|
// note this is escaped:
|
||||||
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
||||||
console.info(FULL_COPYRIGHT);
|
console.info(FULL_COPYRIGHT);
|
||||||
if(!err) {
|
if(!err) {
|
||||||
console.info(banner);
|
console.info(banner);
|
||||||
}
|
}
|
||||||
console.info('System started!');
|
console.info('System started!');
|
||||||
});
|
});
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error('Error initializing: ' + util.inspect(err));
|
console.error('Error initializing: ' + util.inspect(err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shutdownSystem() {
|
function shutdownSystem() {
|
||||||
const msg = 'Process interrupted. Shutting down...';
|
const msg = 'Process interrupted. Shutting down...';
|
||||||
console.info(msg);
|
console.info(msg);
|
||||||
logger.log.info(msg);
|
logger.log.info(msg);
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function closeConnections(callback) {
|
function closeConnections(callback) {
|
||||||
const ClientConns = require('./client_connections.js');
|
const ClientConns = require('./client_connections.js');
|
||||||
const activeConnections = ClientConns.getActiveConnections();
|
const activeConnections = ClientConns.getActiveConnections();
|
||||||
let i = activeConnections.length;
|
let i = activeConnections.length;
|
||||||
while(i--) {
|
while(i--) {
|
||||||
const activeTerm = activeConnections[i].term;
|
const activeTerm = activeConnections[i].term;
|
||||||
if(activeTerm) {
|
if(activeTerm) {
|
||||||
activeTerm.write('\n\nServer is shutting down NOW! Disconnecting...\n\n');
|
activeTerm.write('\n\nServer is shutting down NOW! Disconnecting...\n\n');
|
||||||
}
|
}
|
||||||
ClientConns.removeClient(activeConnections[i]);
|
ClientConns.removeClient(activeConnections[i]);
|
||||||
}
|
}
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
function stopListeningServers(callback) {
|
function stopListeningServers(callback) {
|
||||||
return require('./listening_server.js').shutdown( () => {
|
return require('./listening_server.js').shutdown( () => {
|
||||||
return callback(null); // ignore err
|
return callback(null); // ignore err
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function stopEventScheduler(callback) {
|
function stopEventScheduler(callback) {
|
||||||
if(initServices.eventScheduler) {
|
if(initServices.eventScheduler) {
|
||||||
return initServices.eventScheduler.shutdown( () => {
|
return initServices.eventScheduler.shutdown( () => {
|
||||||
return callback(null); // ignore err
|
return callback(null); // ignore err
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function stopFileAreaWeb(callback) {
|
function stopFileAreaWeb(callback) {
|
||||||
require('./file_area_web.js').startup( () => {
|
require('./file_area_web.js').startup( () => {
|
||||||
return callback(null); // ignore err
|
return callback(null); // ignore err
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function stopMsgNetwork(callback) {
|
function stopMsgNetwork(callback) {
|
||||||
require('./msg_network.js').shutdown(callback);
|
require('./msg_network.js').shutdown(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
console.info('Goodbye!');
|
console.info('Goodbye!');
|
||||||
return process.exit();
|
return process.exit();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize(cb) {
|
function initialize(cb) {
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function createMissingDirectories(callback) {
|
function createMissingDirectories(callback) {
|
||||||
async.each(Object.keys(conf.config.paths), function entry(pathKey, next) {
|
async.each(Object.keys(conf.config.paths), function entry(pathKey, next) {
|
||||||
mkdirs(conf.config.paths[pathKey], function dirCreated(err) {
|
mkdirs(conf.config.paths[pathKey], function dirCreated(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error('Could not create path: ' + conf.config.paths[pathKey] + ': ' + err.toString());
|
console.error('Could not create path: ' + conf.config.paths[pathKey] + ': ' + err.toString());
|
||||||
}
|
}
|
||||||
return next(err);
|
return next(err);
|
||||||
});
|
});
|
||||||
}, function dirCreationComplete(err) {
|
}, function dirCreationComplete(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function basicInit(callback) {
|
function basicInit(callback) {
|
||||||
logger.init();
|
logger.init();
|
||||||
logger.log.info(
|
logger.log.info(
|
||||||
{ version : require('../package.json').version },
|
{ version : require('../package.json').version },
|
||||||
'**** ENiGMA½ Bulletin Board System Starting Up! ****');
|
'**** 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);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function initDatabases(callback) {
|
function initDatabases(callback) {
|
||||||
return database.initializeDatabases(callback);
|
return database.initializeDatabases(callback);
|
||||||
},
|
},
|
||||||
function initMimeTypes(callback) {
|
function initMimeTypes(callback) {
|
||||||
return require('./mime_util.js').startup(callback);
|
return require('./mime_util.js').startup(callback);
|
||||||
},
|
},
|
||||||
function initStatLog(callback) {
|
function initStatLog(callback) {
|
||||||
return require('./stat_log.js').init(callback);
|
return require('./stat_log.js').init(callback);
|
||||||
},
|
},
|
||||||
function initConfigs(callback) {
|
function initConfigs(callback) {
|
||||||
return require('./config_util.js').init(callback);
|
return require('./config_util.js').init(callback);
|
||||||
},
|
},
|
||||||
function initThemes(callback) {
|
function initThemes(callback) {
|
||||||
// Have to pull in here so it's after Config init
|
// Have to pull in here so it's after Config init
|
||||||
require('./theme.js').initAvailableThemes( (err, themeCount) => {
|
require('./theme.js').initAvailableThemes( (err, themeCount) => {
|
||||||
logger.log.info({ themeCount }, 'Themes initialized');
|
logger.log.info({ themeCount }, 'Themes initialized');
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function loadSysOpInformation(callback) {
|
function loadSysOpInformation(callback) {
|
||||||
//
|
//
|
||||||
// Copy over some +op information from the user DB -> system propertys.
|
// Copy over some +op information from the user DB -> system propertys.
|
||||||
// * Makes this accessible for MCI codes, easy non-blocking access, etc.
|
// * 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
|
// * We do this every time as the op is free to change this information just
|
||||||
// like any other user
|
// like any other user
|
||||||
//
|
//
|
||||||
const User = require('./user.js');
|
const User = require('./user.js');
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getOpUserName(next) {
|
function getOpUserName(next) {
|
||||||
return User.getUserName(1, next);
|
return User.getUserName(1, next);
|
||||||
},
|
},
|
||||||
function getOpProps(opUserName, next) {
|
function getOpProps(opUserName, next) {
|
||||||
const propLoadOpts = {
|
const propLoadOpts = {
|
||||||
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
|
names : [ 'real_name', 'sex', 'email_address', 'location', 'affiliation' ],
|
||||||
};
|
};
|
||||||
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
|
User.loadProperties(User.RootUserID, propLoadOpts, (err, opProps) => {
|
||||||
return next(err, opUserName, opProps);
|
return next(err, opUserName, opProps);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err, opUserName, opProps) => {
|
(err, opUserName, opProps) => {
|
||||||
const StatLog = require('./stat_log.js');
|
const StatLog = require('./stat_log.js');
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
|
[ 'username', 'real_name', 'sex', 'email_address', 'location', 'affiliation' ].forEach(v => {
|
||||||
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
|
StatLog.setNonPeristentSystemStat(`sysop_${v}`, 'N/A');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
opProps.username = opUserName;
|
opProps.username = opUserName;
|
||||||
|
|
||||||
_.each(opProps, (v, k) => {
|
_.each(opProps, (v, k) => {
|
||||||
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
StatLog.setNonPeristentSystemStat(`sysop_${k}`, v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initFileAreaStats(callback) {
|
function initFileAreaStats(callback) {
|
||||||
const getAreaStats = require('./file_base_area.js').getAreaStats;
|
const getAreaStats = require('./file_base_area.js').getAreaStats;
|
||||||
getAreaStats( (err, stats) => {
|
getAreaStats( (err, stats) => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
const StatLog = require('./stat_log.js');
|
const StatLog = require('./stat_log.js');
|
||||||
StatLog.setNonPeristentSystemStat('file_base_area_stats', stats);
|
StatLog.setNonPeristentSystemStat('file_base_area_stats', stats);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function initMCI(callback) {
|
function initMCI(callback) {
|
||||||
return require('./predefined_mci.js').init(callback);
|
return require('./predefined_mci.js').init(callback);
|
||||||
},
|
},
|
||||||
function readyMessageNetworkSupport(callback) {
|
function readyMessageNetworkSupport(callback) {
|
||||||
return require('./msg_network.js').startup(callback);
|
return require('./msg_network.js').startup(callback);
|
||||||
},
|
},
|
||||||
function readyEvents(callback) {
|
function readyEvents(callback) {
|
||||||
return require('./events.js').startup(callback);
|
return require('./events.js').startup(callback);
|
||||||
},
|
},
|
||||||
function listenConnections(callback) {
|
function listenConnections(callback) {
|
||||||
return require('./listening_server.js').startup(callback);
|
return require('./listening_server.js').startup(callback);
|
||||||
},
|
},
|
||||||
function readyFileBaseArea(callback) {
|
function readyFileBaseArea(callback) {
|
||||||
return require('./file_base_area.js').startup(callback);
|
return require('./file_base_area.js').startup(callback);
|
||||||
},
|
},
|
||||||
function readyFileAreaWeb(callback) {
|
function readyFileAreaWeb(callback) {
|
||||||
return require('./file_area_web.js').startup(callback);
|
return require('./file_area_web.js').startup(callback);
|
||||||
},
|
},
|
||||||
function readyPasswordReset(callback) {
|
function readyPasswordReset(callback) {
|
||||||
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;
|
const WebPasswordReset = require('./web_password_reset.js').WebPasswordReset;
|
||||||
return WebPasswordReset.startup(callback);
|
return WebPasswordReset.startup(callback);
|
||||||
},
|
},
|
||||||
function readyEventScheduler(callback) {
|
function readyEventScheduler(callback) {
|
||||||
const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule;
|
const EventSchedulerModule = require('./event_scheduler.js').EventSchedulerModule;
|
||||||
EventSchedulerModule.loadAndStart( (err, modInst) => {
|
EventSchedulerModule.loadAndStart( (err, modInst) => {
|
||||||
initServices.eventScheduler = modInst;
|
initServices.eventScheduler = modInst;
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function onComplete(err) {
|
function onComplete(err) {
|
||||||
return cb(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
|
// :TODO: ENH: Support nodeMax and tooManyArt
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'BBSLink',
|
name : 'BBSLink',
|
||||||
desc : 'BBSLink Access Module',
|
desc : 'BBSLink Access Module',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class BBSLinkModule extends MenuModule {
|
exports.getModule = class BBSLinkModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.config = options.menuConfig.config;
|
this.config = options.menuConfig.config;
|
||||||
this.config.host = this.config.host || 'games.bbslink.net';
|
this.config.host = this.config.host || 'games.bbslink.net';
|
||||||
this.config.port = this.config.port || 23;
|
this.config.port = this.config.port || 23;
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
let token;
|
let token;
|
||||||
let randomKey;
|
let randomKey;
|
||||||
let clientTerminated;
|
let clientTerminated;
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function validateConfig(callback) {
|
function validateConfig(callback) {
|
||||||
if(_.isString(self.config.sysCode) &&
|
if(_.isString(self.config.sysCode) &&
|
||||||
_.isString(self.config.authCode) &&
|
_.isString(self.config.authCode) &&
|
||||||
_.isString(self.config.schemeCode) &&
|
_.isString(self.config.schemeCode) &&
|
||||||
_.isString(self.config.door))
|
_.isString(self.config.door))
|
||||||
{
|
{
|
||||||
callback(null);
|
callback(null);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('Configuration is missing option(s)'));
|
callback(new Error('Configuration is missing option(s)'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function acquireToken(callback) {
|
function acquireToken(callback) {
|
||||||
//
|
//
|
||||||
// Acquire an authentication token
|
// Acquire an authentication token
|
||||||
//
|
//
|
||||||
crypto.randomBytes(16, function rand(ex, buf) {
|
crypto.randomBytes(16, function rand(ex, buf) {
|
||||||
if(ex) {
|
if(ex) {
|
||||||
callback(ex);
|
callback(ex);
|
||||||
} else {
|
} else {
|
||||||
randomKey = buf.toString('base64').substr(0, 6);
|
randomKey = buf.toString('base64').substr(0, 6);
|
||||||
self.simpleHttpRequest('/token.php?key=' + randomKey, null, function resp(err, body) {
|
self.simpleHttpRequest('/token.php?key=' + randomKey, null, function resp(err, body) {
|
||||||
if(err) {
|
if(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
} else {
|
} else {
|
||||||
token = body.trim();
|
token = body.trim();
|
||||||
self.client.log.trace( { token : token }, 'BBSLink token');
|
self.client.log.trace( { token : token }, 'BBSLink token');
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function authenticateToken(callback) {
|
function authenticateToken(callback) {
|
||||||
//
|
//
|
||||||
// Authenticate the token we acquired previously
|
// Authenticate the token we acquired previously
|
||||||
//
|
//
|
||||||
var headers = {
|
var headers = {
|
||||||
'X-User' : self.client.user.userId.toString(),
|
'X-User' : self.client.user.userId.toString(),
|
||||||
'X-System' : self.config.sysCode,
|
'X-System' : self.config.sysCode,
|
||||||
'X-Auth' : crypto.createHash('md5').update(self.config.authCode + token).digest('hex'),
|
'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-Code' : crypto.createHash('md5').update(self.config.schemeCode + token).digest('hex'),
|
||||||
'X-Rows' : self.client.term.termHeight.toString(),
|
'X-Rows' : self.client.term.termHeight.toString(),
|
||||||
'X-Key' : randomKey,
|
'X-Key' : randomKey,
|
||||||
'X-Door' : self.config.door,
|
'X-Door' : self.config.door,
|
||||||
'X-Token' : token,
|
'X-Token' : token,
|
||||||
'X-Type' : 'enigma-bbs',
|
'X-Type' : 'enigma-bbs',
|
||||||
'X-Version' : packageJson.version,
|
'X-Version' : packageJson.version,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.simpleHttpRequest('/auth.php?key=' + randomKey, headers, function resp(err, body) {
|
self.simpleHttpRequest('/auth.php?key=' + randomKey, headers, function resp(err, body) {
|
||||||
var status = body.trim();
|
var status = body.trim();
|
||||||
|
|
||||||
if('complete' === status) {
|
if('complete' === status) {
|
||||||
callback(null);
|
callback(null);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('Bad authentication status: ' + status));
|
callback(new Error('Bad authentication status: ' + status));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function createTelnetBridge(callback) {
|
function createTelnetBridge(callback) {
|
||||||
//
|
//
|
||||||
// Authentication with BBSLink successful. Now, we need to create a telnet
|
// Authentication with BBSLink successful. Now, we need to create a telnet
|
||||||
// bridge from us to them
|
// bridge from us to them
|
||||||
//
|
//
|
||||||
var connectOpts = {
|
var connectOpts = {
|
||||||
port : self.config.port,
|
port : self.config.port,
|
||||||
host : self.config.host,
|
host : self.config.host,
|
||||||
};
|
};
|
||||||
|
|
||||||
var clientTerminated;
|
var clientTerminated;
|
||||||
|
|
||||||
self.client.term.write(resetScreen());
|
self.client.term.write(resetScreen());
|
||||||
self.client.term.write(' Connecting to BBSLink.net, please wait...\n');
|
self.client.term.write(' Connecting to BBSLink.net, please wait...\n');
|
||||||
|
|
||||||
var bridgeConnection = net.createConnection(connectOpts, function connected() {
|
var bridgeConnection = net.createConnection(connectOpts, function connected() {
|
||||||
self.client.log.info(connectOpts, 'BBSLink bridge connection established');
|
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.once('end', function clientEnd() {
|
||||||
self.client.log.info('Connection ended. Terminating BBSLink connection');
|
self.client.log.info('Connection ended. Terminating BBSLink connection');
|
||||||
clientTerminated = true;
|
clientTerminated = true;
|
||||||
bridgeConnection.end();
|
bridgeConnection.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
var restorePipe = function() {
|
var restorePipe = function() {
|
||||||
self.client.term.output.unpipe(bridgeConnection);
|
self.client.term.output.unpipe(bridgeConnection);
|
||||||
self.client.term.output.resume();
|
self.client.term.output.resume();
|
||||||
};
|
};
|
||||||
|
|
||||||
bridgeConnection.on('data', function incomingData(data) {
|
bridgeConnection.on('data', function incomingData(data) {
|
||||||
// pass along
|
// pass along
|
||||||
// :TODO: just pipe this as well
|
// :TODO: just pipe this as well
|
||||||
self.client.term.rawWrite(data);
|
self.client.term.rawWrite(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
bridgeConnection.on('end', function connectionEnd() {
|
bridgeConnection.on('end', function connectionEnd() {
|
||||||
restorePipe();
|
restorePipe();
|
||||||
callback(clientTerminated ? new Error('Client connection terminated') : null);
|
callback(clientTerminated ? new Error('Client connection terminated') : null);
|
||||||
});
|
});
|
||||||
|
|
||||||
bridgeConnection.on('error', function error(err) {
|
bridgeConnection.on('error', function error(err) {
|
||||||
self.client.log.info('BBSLink bridge connection error: ' + err.message);
|
self.client.log.info('BBSLink bridge connection error: ' + err.message);
|
||||||
restorePipe();
|
restorePipe();
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
|
self.client.log.warn( { error : err.toString() }, 'BBSLink connection error');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!clientTerminated) {
|
if(!clientTerminated) {
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
simpleHttpRequest(path, headers, cb) {
|
simpleHttpRequest(path, headers, cb) {
|
||||||
const getOpts = {
|
const getOpts = {
|
||||||
host : this.config.host,
|
host : this.config.host,
|
||||||
path : path,
|
path : path,
|
||||||
headers : headers,
|
headers : headers,
|
||||||
};
|
};
|
||||||
|
|
||||||
const req = http.get(getOpts, function response(resp) {
|
const req = http.get(getOpts, function response(resp) {
|
||||||
let data = '';
|
let data = '';
|
||||||
|
|
||||||
resp.on('data', function chunk(c) {
|
resp.on('data', function chunk(c) {
|
||||||
data += c;
|
data += c;
|
||||||
});
|
});
|
||||||
|
|
||||||
resp.on('end', function respEnd() {
|
resp.on('end', function respEnd() {
|
||||||
cb(null, data);
|
cb(null, data);
|
||||||
req.end();
|
req.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', function reqErr(err) {
|
req.on('error', function reqErr(err) {
|
||||||
cb(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 MenuModule = require('./menu_module.js').MenuModule;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getModDatabasePath,
|
getModDatabasePath,
|
||||||
getTransactionDatabase
|
getTransactionDatabase
|
||||||
} = require('./database.js');
|
} = require('./database.js');
|
||||||
|
|
||||||
const ViewController = require('./view_controller.js').ViewController;
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
|
@ -23,397 +23,397 @@ const _ = require('lodash');
|
||||||
// :TODO: add notes field
|
// :TODO: add notes field
|
||||||
|
|
||||||
const moduleInfo = exports.moduleInfo = {
|
const moduleInfo = exports.moduleInfo = {
|
||||||
name : 'BBS List',
|
name : 'BBS List',
|
||||||
desc : 'List of other BBSes',
|
desc : 'List of other BBSes',
|
||||||
author : 'Andrew Pamment',
|
author : 'Andrew Pamment',
|
||||||
packageName : 'com.magickabbs.enigma.bbslist'
|
packageName : 'com.magickabbs.enigma.bbslist'
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
view : {
|
view : {
|
||||||
BBSList : 1,
|
BBSList : 1,
|
||||||
SelectedBBSName : 2,
|
SelectedBBSName : 2,
|
||||||
SelectedBBSSysOp : 3,
|
SelectedBBSSysOp : 3,
|
||||||
SelectedBBSTelnet : 4,
|
SelectedBBSTelnet : 4,
|
||||||
SelectedBBSWww : 5,
|
SelectedBBSWww : 5,
|
||||||
SelectedBBSLoc : 6,
|
SelectedBBSLoc : 6,
|
||||||
SelectedBBSSoftware : 7,
|
SelectedBBSSoftware : 7,
|
||||||
SelectedBBSNotes : 8,
|
SelectedBBSNotes : 8,
|
||||||
SelectedBBSSubmitter : 9,
|
SelectedBBSSubmitter : 9,
|
||||||
},
|
},
|
||||||
add : {
|
add : {
|
||||||
BBSName : 1,
|
BBSName : 1,
|
||||||
Sysop : 2,
|
Sysop : 2,
|
||||||
Telnet : 3,
|
Telnet : 3,
|
||||||
Www : 4,
|
Www : 4,
|
||||||
Location : 5,
|
Location : 5,
|
||||||
Software : 6,
|
Software : 6,
|
||||||
Notes : 7,
|
Notes : 7,
|
||||||
Error : 8,
|
Error : 8,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormIds = {
|
const FormIds = {
|
||||||
View : 0,
|
View : 0,
|
||||||
Add : 1,
|
Add : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SELECTED_MCI_NAME_TO_ENTRY = {
|
const SELECTED_MCI_NAME_TO_ENTRY = {
|
||||||
SelectedBBSName : 'bbsName',
|
SelectedBBSName : 'bbsName',
|
||||||
SelectedBBSSysOp : 'sysOp',
|
SelectedBBSSysOp : 'sysOp',
|
||||||
SelectedBBSTelnet : 'telnet',
|
SelectedBBSTelnet : 'telnet',
|
||||||
SelectedBBSWww : 'www',
|
SelectedBBSWww : 'www',
|
||||||
SelectedBBSLoc : 'location',
|
SelectedBBSLoc : 'location',
|
||||||
SelectedBBSSoftware : 'software',
|
SelectedBBSSoftware : 'software',
|
||||||
SelectedBBSSubmitter : 'submitter',
|
SelectedBBSSubmitter : 'submitter',
|
||||||
SelectedBBSSubmitterId : 'submitterUserId',
|
SelectedBBSSubmitterId : 'submitterUserId',
|
||||||
SelectedBBSNotes : 'notes',
|
SelectedBBSNotes : 'notes',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class BBSListModule extends MenuModule {
|
exports.getModule = class BBSListModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
//
|
//
|
||||||
// Validators
|
// Validators
|
||||||
//
|
//
|
||||||
viewValidationListener : function(err, cb) {
|
viewValidationListener : function(err, cb) {
|
||||||
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
|
const errMsgView = self.viewControllers.add.getView(MciViewIds.add.Error);
|
||||||
if(errMsgView) {
|
if(errMsgView) {
|
||||||
if(err) {
|
if(err) {
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.message);
|
||||||
} else {
|
} else {
|
||||||
errMsgView.clearText();
|
errMsgView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
// Key & submit handlers
|
// Key & submit handlers
|
||||||
//
|
//
|
||||||
addBBS : function(formData, extraArgs, cb) {
|
addBBS : function(formData, extraArgs, cb) {
|
||||||
self.displayAddScreen(cb);
|
self.displayAddScreen(cb);
|
||||||
},
|
},
|
||||||
deleteBBS : function(formData, extraArgs, cb) {
|
deleteBBS : function(formData, extraArgs, cb) {
|
||||||
if(!_.isNumber(self.selectedBBS) || 0 === self.entries.length) {
|
if(!_.isNumber(self.selectedBBS) || 0 === self.entries.length) {
|
||||||
return cb(null);
|
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()) {
|
if(self.entries[self.selectedBBS].submitterUserId !== self.client.user.userId && !self.client.user.isSysOp()) {
|
||||||
// must be owner or +op
|
// must be owner or +op
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const entry = self.entries[self.selectedBBS];
|
const entry = self.entries[self.selectedBBS];
|
||||||
if(!entry) {
|
if(!entry) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.database.run(
|
self.database.run(
|
||||||
`DELETE FROM bbs_list
|
`DELETE FROM bbs_list
|
||||||
WHERE id=?;`,
|
WHERE id=?;`,
|
||||||
[ entry.id ],
|
[ entry.id ],
|
||||||
err => {
|
err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
self.client.log.error( { err : err }, 'Error deleting from BBS list');
|
self.client.log.error( { err : err }, 'Error deleting from BBS list');
|
||||||
} else {
|
} else {
|
||||||
self.entries.splice(self.selectedBBS, 1);
|
self.entries.splice(self.selectedBBS, 1);
|
||||||
|
|
||||||
self.setEntries(entriesView);
|
self.setEntries(entriesView);
|
||||||
|
|
||||||
if(self.entries.length > 0) {
|
if(self.entries.length > 0) {
|
||||||
entriesView.focusPrevious();
|
entriesView.focusPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.viewControllers.view.redrawAll();
|
self.viewControllers.view.redrawAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
submitBBS : function(formData, extraArgs, cb) {
|
submitBBS : function(formData, extraArgs, cb) {
|
||||||
|
|
||||||
let ok = true;
|
let ok = true;
|
||||||
[ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => {
|
[ 'BBSName', 'Sysop', 'Telnet' ].forEach( mciName => {
|
||||||
if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) {
|
if('' === self.viewControllers.add.getView(MciViewIds.add[mciName]).getData()) {
|
||||||
ok = false;
|
ok = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(!ok) {
|
if(!ok) {
|
||||||
// validators should prevent this!
|
// validators should prevent this!
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.database.run(
|
self.database.run(
|
||||||
`INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes)
|
`INSERT INTO bbs_list (bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes)
|
||||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?);`,
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?);`,
|
||||||
[
|
[
|
||||||
formData.value.name, formData.value.sysop, formData.value.telnet, formData.value.www,
|
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
|
formData.value.location, formData.value.software, self.client.user.userId, formData.value.notes
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.error( { err : err }, 'Error adding to BBS list');
|
self.client.log.error( { err : err }, 'Error adding to BBS list');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.clearAddForm();
|
self.clearAddForm();
|
||||||
self.displayBBSList(true, cb);
|
self.displayBBSList(true, cb);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
cancelSubmit : function(formData, extraArgs, cb) {
|
cancelSubmit : function(formData, extraArgs, cb) {
|
||||||
self.clearAddForm();
|
self.clearAddForm();
|
||||||
self.displayBBSList(true, cb);
|
self.displayBBSList(true, cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function beforeDisplayArt(callback) {
|
function beforeDisplayArt(callback) {
|
||||||
self.beforeArt(callback);
|
self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function display(callback) {
|
function display(callback) {
|
||||||
self.displayBBSList(false, callback);
|
self.displayBBSList(false, callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||||
}
|
}
|
||||||
self.finishedLoading();
|
self.finishedLoading();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawSelectedEntry(entry) {
|
drawSelectedEntry(entry) {
|
||||||
if(!entry) {
|
if(!entry) {
|
||||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||||
this.setViewText('view', MciViewIds.view[mciName], '');
|
this.setViewText('view', MciViewIds.view[mciName], '');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)';
|
const youSubmittedFormat = this.menuConfig.youSubmittedFormat || '{submitter} (You!)';
|
||||||
|
|
||||||
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
Object.keys(SELECTED_MCI_NAME_TO_ENTRY).forEach(mciName => {
|
||||||
const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]];
|
const t = entry[SELECTED_MCI_NAME_TO_ENTRY[mciName]];
|
||||||
if(MciViewIds.view[mciName]) {
|
if(MciViewIds.view[mciName]) {
|
||||||
|
|
||||||
if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId) {
|
if('SelectedBBSSubmitter' == mciName && entry.submitterUserId == this.client.user.userId) {
|
||||||
this.setViewText('view',MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry));
|
this.setViewText('view',MciViewIds.view.SelectedBBSSubmitter, stringFormat(youSubmittedFormat, entry));
|
||||||
} else {
|
} else {
|
||||||
this.setViewText('view',MciViewIds.view[mciName], t);
|
this.setViewText('view',MciViewIds.view[mciName], t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setEntries(entriesView) {
|
setEntries(entriesView) {
|
||||||
const config = this.menuConfig.config;
|
const config = this.menuConfig.config;
|
||||||
const listFormat = config.listFormat || '{bbsName}';
|
const listFormat = config.listFormat || '{bbsName}';
|
||||||
const focusListFormat = config.focusListFormat || '{bbsName}';
|
const focusListFormat = config.focusListFormat || '{bbsName}';
|
||||||
|
|
||||||
entriesView.setItems(this.entries.map( e => stringFormat(listFormat, e) ) );
|
entriesView.setItems(this.entries.map( e => stringFormat(listFormat, e) ) );
|
||||||
entriesView.setFocusItems(this.entries.map( e => stringFormat(focusListFormat, e) ) );
|
entriesView.setFocusItems(this.entries.map( e => stringFormat(focusListFormat, e) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
displayBBSList(clearScreen, cb) {
|
displayBBSList(clearScreen, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
if(self.viewControllers.add) {
|
if(self.viewControllers.add) {
|
||||||
self.viewControllers.add.setFocus(false);
|
self.viewControllers.add.setFocus(false);
|
||||||
}
|
}
|
||||||
if (clearScreen) {
|
if (clearScreen) {
|
||||||
self.client.term.rawWrite(ansi.resetScreen());
|
self.client.term.rawWrite(ansi.resetScreen());
|
||||||
}
|
}
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
self.menuConfig.config.art.entries,
|
self.menuConfig.config.art.entries,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font, trailingLF : false },
|
{ font : self.menuConfig.font, trailingLF : false },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initOrRedrawViewController(artData, callback) {
|
function initOrRedrawViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers.add)) {
|
if(_.isUndefined(self.viewControllers.add)) {
|
||||||
const vc = self.addViewController(
|
const vc = self.addViewController(
|
||||||
'view',
|
'view',
|
||||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds.View,
|
formId : FormIds.View,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
} else {
|
} else {
|
||||||
self.viewControllers.view.setFocus(true);
|
self.viewControllers.view.setFocus(true);
|
||||||
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw();
|
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function fetchEntries(callback) {
|
function fetchEntries(callback) {
|
||||||
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
|
const entriesView = self.viewControllers.view.getView(MciViewIds.view.BBSList);
|
||||||
self.entries = [];
|
self.entries = [];
|
||||||
|
|
||||||
self.database.each(
|
self.database.each(
|
||||||
`SELECT id, bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes
|
`SELECT id, bbs_name, sysop, telnet, www, location, software, submitter_user_id, notes
|
||||||
FROM bbs_list;`,
|
FROM bbs_list;`,
|
||||||
(err, row) => {
|
(err, row) => {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
self.entries.push({
|
self.entries.push({
|
||||||
id : row.id,
|
id : row.id,
|
||||||
bbsName : row.bbs_name,
|
bbsName : row.bbs_name,
|
||||||
sysOp : row.sysop,
|
sysOp : row.sysop,
|
||||||
telnet : row.telnet,
|
telnet : row.telnet,
|
||||||
www : row.www,
|
www : row.www,
|
||||||
location : row.location,
|
location : row.location,
|
||||||
software : row.software,
|
software : row.software,
|
||||||
submitterUserId : row.submitter_user_id,
|
submitterUserId : row.submitter_user_id,
|
||||||
notes : row.notes,
|
notes : row.notes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
return callback(err, entriesView);
|
return callback(err, entriesView);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function getUserNames(entriesView, callback) {
|
function getUserNames(entriesView, callback) {
|
||||||
async.each(self.entries, (entry, next) => {
|
async.each(self.entries, (entry, next) => {
|
||||||
User.getUserName(entry.submitterUserId, (err, username) => {
|
User.getUserName(entry.submitterUserId, (err, username) => {
|
||||||
if(username) {
|
if(username) {
|
||||||
entry.submitter = username;
|
entry.submitter = username;
|
||||||
} else {
|
} else {
|
||||||
entry.submitter = 'N/A';
|
entry.submitter = 'N/A';
|
||||||
}
|
}
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
return callback(null, entriesView);
|
return callback(null, entriesView);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function populateEntries(entriesView, callback) {
|
function populateEntries(entriesView, callback) {
|
||||||
self.setEntries(entriesView);
|
self.setEntries(entriesView);
|
||||||
|
|
||||||
entriesView.on('index update', idx => {
|
entriesView.on('index update', idx => {
|
||||||
const entry = self.entries[idx];
|
const entry = self.entries[idx];
|
||||||
|
|
||||||
self.drawSelectedEntry(entry);
|
self.drawSelectedEntry(entry);
|
||||||
|
|
||||||
if(!entry) {
|
if(!entry) {
|
||||||
self.selectedBBS = -1;
|
self.selectedBBS = -1;
|
||||||
} else {
|
} else {
|
||||||
self.selectedBBS = idx;
|
self.selectedBBS = idx;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (self.selectedBBS >= 0) {
|
if (self.selectedBBS >= 0) {
|
||||||
entriesView.setFocusItemIndex(self.selectedBBS);
|
entriesView.setFocusItemIndex(self.selectedBBS);
|
||||||
self.drawSelectedEntry(self.entries[self.selectedBBS]);
|
self.drawSelectedEntry(self.entries[self.selectedBBS]);
|
||||||
} else if (self.entries.length > 0) {
|
} else if (self.entries.length > 0) {
|
||||||
self.selectedBBS = 0;
|
self.selectedBBS = 0;
|
||||||
entriesView.setFocusItemIndex(0);
|
entriesView.setFocusItemIndex(0);
|
||||||
self.drawSelectedEntry(self.entries[0]);
|
self.drawSelectedEntry(self.entries[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
entriesView.redraw();
|
entriesView.redraw();
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAddScreen(cb) {
|
displayAddScreen(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
self.viewControllers.view.setFocus(false);
|
self.viewControllers.view.setFocus(false);
|
||||||
self.client.term.rawWrite(ansi.resetScreen());
|
self.client.term.rawWrite(ansi.resetScreen());
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
self.menuConfig.config.art.add,
|
self.menuConfig.config.art.add,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font },
|
{ font : self.menuConfig.font },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initOrRedrawViewController(artData, callback) {
|
function initOrRedrawViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers.add)) {
|
if(_.isUndefined(self.viewControllers.add)) {
|
||||||
const vc = self.addViewController(
|
const vc = self.addViewController(
|
||||||
'add',
|
'add',
|
||||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds.Add,
|
formId : FormIds.Add,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
} else {
|
} else {
|
||||||
self.viewControllers.add.setFocus(true);
|
self.viewControllers.add.setFocus(true);
|
||||||
self.viewControllers.add.redrawAll();
|
self.viewControllers.add.redrawAll();
|
||||||
self.viewControllers.add.switchFocus(MciViewIds.add.BBSName);
|
self.viewControllers.add.switchFocus(MciViewIds.add.BBSName);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAddForm() {
|
clearAddForm() {
|
||||||
[ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => {
|
[ 'BBSName', 'Sysop', 'Telnet', 'Www', 'Location', 'Software', 'Error', 'Notes' ].forEach( mciName => {
|
||||||
this.setViewText('add', MciViewIds.add[mciName], '');
|
this.setViewText('add', MciViewIds.add[mciName], '');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initDatabase(cb) {
|
initDatabase(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function openDatabase(callback) {
|
function openDatabase(callback) {
|
||||||
self.database = getTransactionDatabase(new sqlite3.Database(
|
self.database = getTransactionDatabase(new sqlite3.Database(
|
||||||
getModDatabasePath(moduleInfo),
|
getModDatabasePath(moduleInfo),
|
||||||
callback
|
callback
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
function createTables(callback) {
|
function createTables(callback) {
|
||||||
self.database.serialize( () => {
|
self.database.serialize( () => {
|
||||||
self.database.run(
|
self.database.run(
|
||||||
`CREATE TABLE IF NOT EXISTS bbs_list (
|
`CREATE TABLE IF NOT EXISTS bbs_list (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
bbs_name VARCHAR NOT NULL,
|
bbs_name VARCHAR NOT NULL,
|
||||||
sysop VARCHAR NOT NULL,
|
sysop VARCHAR NOT NULL,
|
||||||
|
@ -424,20 +424,20 @@ exports.getModule = class BBSListModule extends MenuModule {
|
||||||
submitter_user_id INTEGER NOT NULL,
|
submitter_user_id INTEGER NOT NULL,
|
||||||
notes VARCHAR
|
notes VARCHAR
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeArt(cb) {
|
beforeArt(cb) {
|
||||||
super.beforeArt(err => {
|
super.beforeArt(err => {
|
||||||
return err ? cb(err) : this.initDatabase(cb);
|
return err ? cb(err) : this.initDatabase(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,24 +8,24 @@ const util = require('util');
|
||||||
exports.ButtonView = ButtonView;
|
exports.ButtonView = ButtonView;
|
||||||
|
|
||||||
function ButtonView(options) {
|
function ButtonView(options) {
|
||||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||||
options.justify = miscUtil.valueWithDefault(options.justify, 'center');
|
options.justify = miscUtil.valueWithDefault(options.justify, 'center');
|
||||||
options.cursor = miscUtil.valueWithDefault(options.cursor, 'hide');
|
options.cursor = miscUtil.valueWithDefault(options.cursor, 'hide');
|
||||||
|
|
||||||
TextView.call(this, options);
|
TextView.call(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(ButtonView, TextView);
|
util.inherits(ButtonView, TextView);
|
||||||
|
|
||||||
ButtonView.prototype.onKeyPress = function(ch, key) {
|
ButtonView.prototype.onKeyPress = function(ch, key) {
|
||||||
if(this.isKeyMapped('accept', key.name) || ' ' === ch) {
|
if(this.isKeyMapped('accept', key.name) || ' ' === ch) {
|
||||||
this.submitData = 'accept';
|
this.submitData = 'accept';
|
||||||
this.emit('action', 'accept');
|
this.emit('action', 'accept');
|
||||||
delete this.submitData;
|
delete this.submitData;
|
||||||
} else {
|
} else {
|
||||||
ButtonView.super_.prototype.onKeyPress.call(this, ch, key);
|
ButtonView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/*
|
/*
|
||||||
ButtonView.prototype.onKeyPress = function(ch, key) {
|
ButtonView.prototype.onKeyPress = function(ch, key) {
|
||||||
|
@ -39,5 +39,5 @@ ButtonView.prototype.onKeyPress = function(ch, key) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ButtonView.prototype.getData = function() {
|
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_ANYWHERE = /(?:\u001b)([a-zA-Z0-9])/;
|
||||||
const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
const RE_META_KEYCODE = new RegExp('^' + RE_META_KEYCODE_ANYWHERE.source + '$');
|
||||||
const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
const RE_FUNCTION_KEYCODE_ANYWHERE = new RegExp('(?:\u001b+)(O|N|\\[|\\[\\[)(?:' + [
|
||||||
'(\\d+)(?:;(\\d+))?([~^$])',
|
'(\\d+)(?:;(\\d+))?([~^$])',
|
||||||
'(?:M([@ #!a`])(.)(.))', // mouse stuff
|
'(?:M([@ #!a`])(.)(.))', // mouse stuff
|
||||||
'(?:1;)?(\\d+)?([a-zA-Z@])'
|
'(?:1;)?(\\d+)?([a-zA-Z@])'
|
||||||
].join('|') + ')');
|
].join('|') + ')');
|
||||||
|
|
||||||
const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
|
const RE_FUNCTION_KEYCODE = new RegExp('^' + RE_FUNCTION_KEYCODE_ANYWHERE.source);
|
||||||
const RE_ESC_CODE_ANYWHERE = new RegExp( [
|
const RE_ESC_CODE_ANYWHERE = new RegExp( [
|
||||||
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
RE_FUNCTION_KEYCODE_ANYWHERE.source,
|
||||||
RE_META_KEYCODE_ANYWHERE.source,
|
RE_META_KEYCODE_ANYWHERE.source,
|
||||||
RE_DSR_RESPONSE_ANYWHERE.source,
|
RE_DSR_RESPONSE_ANYWHERE.source,
|
||||||
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
|
RE_DEV_ATTR_RESPONSE_ANYWHERE.source,
|
||||||
/\u001b./.source
|
/\u001b./.source
|
||||||
].join('|'));
|
].join('|'));
|
||||||
|
|
||||||
|
|
||||||
function Client(/*input, output*/) {
|
function Client(/*input, output*/) {
|
||||||
stream.call(this);
|
stream.call(this);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.user = new User();
|
this.user = new User();
|
||||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||||
this.lastKeyPressMs = Date.now();
|
this.lastKeyPressMs = Date.now();
|
||||||
this.menuStack = new MenuStack(this);
|
this.menuStack = new MenuStack(this);
|
||||||
this.acs = new ACS(this);
|
this.acs = new ACS(this);
|
||||||
this.mciCache = {};
|
this.mciCache = {};
|
||||||
|
|
||||||
this.clearMciCache = function() {
|
this.clearMciCache = function() {
|
||||||
this.mciCache = {};
|
this.mciCache = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(this, 'node', {
|
Object.defineProperty(this, 'node', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return self.session.id + 1;
|
return self.session.id + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, 'currentMenuModule', {
|
Object.defineProperty(this, 'currentMenuModule', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return self.menuStack.currentModule;
|
return self.menuStack.currentModule;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setTemporaryDirectDataHandler = function(handler) {
|
this.setTemporaryDirectDataHandler = function(handler) {
|
||||||
this.input.removeAllListeners('data');
|
this.input.removeAllListeners('data');
|
||||||
this.input.on('data', handler);
|
this.input.on('data', handler);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.restoreDataHandler = function() {
|
this.restoreDataHandler = function() {
|
||||||
this.input.removeAllListeners('data');
|
this.input.removeAllListeners('data');
|
||||||
this.input.on('data', this.dataHandler);
|
this.input.on('data', this.dataHandler);
|
||||||
};
|
};
|
||||||
|
|
||||||
Events.on(Events.getSystemEvents().ThemeChanged, ( { themeId } ) => {
|
Events.on(Events.getSystemEvents().ThemeChanged, ( { themeId } ) => {
|
||||||
if(_.get(this.currentTheme, 'info.themeId') === themeId) {
|
if(_.get(this.currentTheme, 'info.themeId') === themeId) {
|
||||||
this.currentTheme = require('./theme.js').getAvailableThemes().get(themeId);
|
this.currentTheme = require('./theme.js').getAvailableThemes().get(themeId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Peek at incoming |data| and emit events for any special
|
// Peek at incoming |data| and emit events for any special
|
||||||
// handling that may include:
|
// handling that may include:
|
||||||
// * Keyboard input
|
// * Keyboard input
|
||||||
// * ANSI CSR's and the like
|
// * ANSI CSR's and the like
|
||||||
//
|
//
|
||||||
// References:
|
// References:
|
||||||
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
// * http://www.ansi-bbs.org/ansi-bbs-core-server.html
|
||||||
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
// * Christopher Jeffrey's Blessed library @ https://github.com/chjj/blessed/
|
||||||
//
|
//
|
||||||
this.getTermClient = function(deviceAttr) {
|
this.getTermClient = function(deviceAttr) {
|
||||||
let termClient = {
|
let termClient = {
|
||||||
//
|
//
|
||||||
// See http://www.fbl.cz/arctel/download/techman.pdf
|
// See http://www.fbl.cz/arctel/download/techman.pdf
|
||||||
//
|
//
|
||||||
// Known clients:
|
// Known clients:
|
||||||
// * Irssi ConnectBot (Android)
|
// * Irssi ConnectBot (Android)
|
||||||
//
|
//
|
||||||
'63;1;2' : 'arctel',
|
'63;1;2' : 'arctel',
|
||||||
'50;86;84;88' : 'vtx',
|
'50;86;84;88' : 'vtx',
|
||||||
}[deviceAttr];
|
}[deviceAttr];
|
||||||
|
|
||||||
if(!termClient) {
|
if(!termClient) {
|
||||||
if(_.startsWith(deviceAttr, '67;84;101;114;109')) {
|
if(_.startsWith(deviceAttr, '67;84;101;114;109')) {
|
||||||
//
|
//
|
||||||
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
// See https://github.com/protomouse/synchronet/blob/master/src/conio/cterm.txt
|
||||||
//
|
//
|
||||||
// Known clients:
|
// Known clients:
|
||||||
// * SyncTERM
|
// * SyncTERM
|
||||||
//
|
//
|
||||||
termClient = 'cterm';
|
termClient = 'cterm';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return termClient;
|
return termClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isMouseInput = function(data) {
|
this.isMouseInput = function(data) {
|
||||||
return /\x1b\[M/.test(data) || // eslint-disable-line no-control-regex
|
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\[M([\x00\u0020-\uffff]{3})/.test(data) || // eslint-disable-line no-control-regex
|
||||||
/\u001b\[(\d+;\d+;\d+)M/.test(data) ||
|
/\u001b\[(\d+;\d+;\d+)M/.test(data) ||
|
||||||
/\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) ||
|
/\u001b\[<(\d+;\d+;\d+)([mM])/.test(data) ||
|
||||||
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) ||
|
/\u001b\[<(\d+;\d+;\d+;\d+)&w/.test(data) ||
|
||||||
/\u001b\[24([0135])~\[(\d+),(\d+)\]\r/.test(data) ||
|
/\u001b\[24([0135])~\[(\d+),(\d+)\]\r/.test(data) ||
|
||||||
/\u001b\[(O|I)/.test(data);
|
/\u001b\[(O|I)/.test(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getKeyComponentsFromCode = function(code) {
|
this.getKeyComponentsFromCode = function(code) {
|
||||||
return {
|
return {
|
||||||
// xterm/gnome
|
// xterm/gnome
|
||||||
'OP' : { name : 'f1' },
|
'OP' : { name : 'f1' },
|
||||||
'OQ' : { name : 'f2' },
|
'OQ' : { name : 'f2' },
|
||||||
'OR' : { name : 'f3' },
|
'OR' : { name : 'f3' },
|
||||||
'OS' : { name : 'f4' },
|
'OS' : { name : 'f4' },
|
||||||
|
|
||||||
'OA' : { name : 'up arrow' },
|
'OA' : { name : 'up arrow' },
|
||||||
'OB' : { name : 'down arrow' },
|
'OB' : { name : 'down arrow' },
|
||||||
'OC' : { name : 'right arrow' },
|
'OC' : { name : 'right arrow' },
|
||||||
'OD' : { name : 'left arrow' },
|
'OD' : { name : 'left arrow' },
|
||||||
'OE' : { name : 'clear' },
|
'OE' : { name : 'clear' },
|
||||||
'OF' : { name : 'end' },
|
'OF' : { name : 'end' },
|
||||||
'OH' : { name : 'home' },
|
'OH' : { name : 'home' },
|
||||||
|
|
||||||
// xterm/rxvt
|
// xterm/rxvt
|
||||||
'[11~' : { name : 'f1' },
|
'[11~' : { name : 'f1' },
|
||||||
'[12~' : { name : 'f2' },
|
'[12~' : { name : 'f2' },
|
||||||
'[13~' : { name : 'f3' },
|
'[13~' : { name : 'f3' },
|
||||||
'[14~' : { name : 'f4' },
|
'[14~' : { name : 'f4' },
|
||||||
|
|
||||||
'[1~' : { name : 'home' },
|
'[1~' : { name : 'home' },
|
||||||
'[2~' : { name : 'insert' },
|
'[2~' : { name : 'insert' },
|
||||||
'[3~' : { name : 'delete' },
|
'[3~' : { name : 'delete' },
|
||||||
'[4~' : { name : 'end' },
|
'[4~' : { name : 'end' },
|
||||||
'[5~' : { name : 'page up' },
|
'[5~' : { name : 'page up' },
|
||||||
'[6~' : { name : 'page down' },
|
'[6~' : { name : 'page down' },
|
||||||
|
|
||||||
// Cygwin & libuv
|
// Cygwin & libuv
|
||||||
'[[A' : { name : 'f1' },
|
'[[A' : { name : 'f1' },
|
||||||
'[[B' : { name : 'f2' },
|
'[[B' : { name : 'f2' },
|
||||||
'[[C' : { name : 'f3' },
|
'[[C' : { name : 'f3' },
|
||||||
'[[D' : { name : 'f4' },
|
'[[D' : { name : 'f4' },
|
||||||
'[[E' : { name : 'f5' },
|
'[[E' : { name : 'f5' },
|
||||||
|
|
||||||
// Common impls
|
// Common impls
|
||||||
'[15~' : { name : 'f5' },
|
'[15~' : { name : 'f5' },
|
||||||
'[17~' : { name : 'f6' },
|
'[17~' : { name : 'f6' },
|
||||||
'[18~' : { name : 'f7' },
|
'[18~' : { name : 'f7' },
|
||||||
'[19~' : { name : 'f8' },
|
'[19~' : { name : 'f8' },
|
||||||
'[20~' : { name : 'f9' },
|
'[20~' : { name : 'f9' },
|
||||||
'[21~' : { name : 'f10' },
|
'[21~' : { name : 'f10' },
|
||||||
'[23~' : { name : 'f11' },
|
'[23~' : { name : 'f11' },
|
||||||
'[24~' : { name : 'f12' },
|
'[24~' : { name : 'f12' },
|
||||||
|
|
||||||
// xterm
|
// xterm
|
||||||
'[A' : { name : 'up arrow' },
|
'[A' : { name : 'up arrow' },
|
||||||
'[B' : { name : 'down arrow' },
|
'[B' : { name : 'down arrow' },
|
||||||
'[C' : { name : 'right arrow' },
|
'[C' : { name : 'right arrow' },
|
||||||
'[D' : { name : 'left arrow' },
|
'[D' : { name : 'left arrow' },
|
||||||
'[E' : { name : 'clear' },
|
'[E' : { name : 'clear' },
|
||||||
'[F' : { name : 'end' },
|
'[F' : { name : 'end' },
|
||||||
'[H' : { name : 'home' },
|
'[H' : { name : 'home' },
|
||||||
|
|
||||||
// PuTTY
|
// PuTTY
|
||||||
'[[5~' : { name : 'page up' },
|
'[[5~' : { name : 'page up' },
|
||||||
'[[6~' : { name : 'page down' },
|
'[[6~' : { name : 'page down' },
|
||||||
|
|
||||||
// rvxt
|
// rvxt
|
||||||
'[7~' : { name : 'home' },
|
'[7~' : { name : 'home' },
|
||||||
'[8~' : { name : 'end' },
|
'[8~' : { name : 'end' },
|
||||||
|
|
||||||
// rxvt with modifiers
|
// rxvt with modifiers
|
||||||
'[a' : { name : 'up arrow', shift : true },
|
'[a' : { name : 'up arrow', shift : true },
|
||||||
'[b' : { name : 'down arrow', shift : true },
|
'[b' : { name : 'down arrow', shift : true },
|
||||||
'[c' : { name : 'right arrow', shift : true },
|
'[c' : { name : 'right arrow', shift : true },
|
||||||
'[d' : { name : 'left arrow', shift : true },
|
'[d' : { name : 'left arrow', shift : true },
|
||||||
'[e' : { name : 'clear', shift : true },
|
'[e' : { name : 'clear', shift : true },
|
||||||
|
|
||||||
'[2$' : { name : 'insert', shift : true },
|
'[2$' : { name : 'insert', shift : true },
|
||||||
'[3$' : { name : 'delete', shift : true },
|
'[3$' : { name : 'delete', shift : true },
|
||||||
'[5$' : { name : 'page up', shift : true },
|
'[5$' : { name : 'page up', shift : true },
|
||||||
'[6$' : { name : 'page down', shift : true },
|
'[6$' : { name : 'page down', shift : true },
|
||||||
'[7$' : { name : 'home', shift : true },
|
'[7$' : { name : 'home', shift : true },
|
||||||
'[8$' : { name : 'end', shift : true },
|
'[8$' : { name : 'end', shift : true },
|
||||||
|
|
||||||
'Oa' : { name : 'up arrow', ctrl : true },
|
'Oa' : { name : 'up arrow', ctrl : true },
|
||||||
'Ob' : { name : 'down arrow', ctrl : true },
|
'Ob' : { name : 'down arrow', ctrl : true },
|
||||||
'Oc' : { name : 'right arrow', ctrl : true },
|
'Oc' : { name : 'right arrow', ctrl : true },
|
||||||
'Od' : { name : 'left arrow', ctrl : true },
|
'Od' : { name : 'left arrow', ctrl : true },
|
||||||
'Oe' : { name : 'clear', ctrl : true },
|
'Oe' : { name : 'clear', ctrl : true },
|
||||||
|
|
||||||
'[2^' : { name : 'insert', ctrl : true },
|
'[2^' : { name : 'insert', ctrl : true },
|
||||||
'[3^' : { name : 'delete', ctrl : true },
|
'[3^' : { name : 'delete', ctrl : true },
|
||||||
'[5^' : { name : 'page up', ctrl : true },
|
'[5^' : { name : 'page up', ctrl : true },
|
||||||
'[6^' : { name : 'page down', ctrl : true },
|
'[6^' : { name : 'page down', ctrl : true },
|
||||||
'[7^' : { name : 'home', ctrl : true },
|
'[7^' : { name : 'home', ctrl : true },
|
||||||
'[8^' : { name : 'end', ctrl : true },
|
'[8^' : { name : 'end', ctrl : true },
|
||||||
|
|
||||||
// SyncTERM / EtherTerm
|
// SyncTERM / EtherTerm
|
||||||
'[K' : { name : 'end' },
|
'[K' : { name : 'end' },
|
||||||
'[@' : { name : 'insert' },
|
'[@' : { name : 'insert' },
|
||||||
'[V' : { name : 'page up' },
|
'[V' : { name : 'page up' },
|
||||||
'[U' : { name : 'page down' },
|
'[U' : { name : 'page down' },
|
||||||
|
|
||||||
// other
|
// other
|
||||||
'[Z' : { name : 'tab', shift : true },
|
'[Z' : { name : 'tab', shift : true },
|
||||||
}[code];
|
}[code];
|
||||||
};
|
};
|
||||||
|
|
||||||
this.on('data', function clientData(data) {
|
this.on('data', function clientData(data) {
|
||||||
// create a uniform format that can be parsed below
|
// create a uniform format that can be parsed below
|
||||||
if(data[0] > 127 && undefined === data[1]) {
|
if(data[0] > 127 && undefined === data[1]) {
|
||||||
data[0] -= 128;
|
data[0] -= 128;
|
||||||
data = '\u001b' + data.toString('utf-8');
|
data = '\u001b' + data.toString('utf-8');
|
||||||
} else {
|
} else {
|
||||||
data = data.toString('utf-8');
|
data = data.toString('utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self.isMouseInput(data)) {
|
if(self.isMouseInput(data)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var buf = [];
|
var buf = [];
|
||||||
var m;
|
var m;
|
||||||
while((m = RE_ESC_CODE_ANYWHERE.exec(data))) {
|
while((m = RE_ESC_CODE_ANYWHERE.exec(data))) {
|
||||||
buf = buf.concat(data.slice(0, m.index).split(''));
|
buf = buf.concat(data.slice(0, m.index).split(''));
|
||||||
buf.push(m[0]);
|
buf.push(m[0]);
|
||||||
data = data.slice(m.index + m[0].length);
|
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) {
|
buf.forEach(function bufPart(s) {
|
||||||
var key = {
|
var key = {
|
||||||
seq : s,
|
seq : s,
|
||||||
name : undefined,
|
name : undefined,
|
||||||
ctrl : false,
|
ctrl : false,
|
||||||
meta : false,
|
meta : false,
|
||||||
shift : false,
|
shift : false,
|
||||||
};
|
};
|
||||||
|
|
||||||
var parts;
|
var parts;
|
||||||
|
|
||||||
if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
|
if((parts = RE_DSR_RESPONSE_ANYWHERE.exec(s))) {
|
||||||
if('R' === parts[2]) {
|
if('R' === parts[2]) {
|
||||||
const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) );
|
const cprArgs = parts[1].split(';').map(v => (parseInt(v, 10) || 0) );
|
||||||
if(2 === cprArgs.length) {
|
if(2 === cprArgs.length) {
|
||||||
if(self.cprOffset) {
|
if(self.cprOffset) {
|
||||||
cprArgs[0] = cprArgs[0] + self.cprOffset;
|
cprArgs[0] = cprArgs[0] + self.cprOffset;
|
||||||
cprArgs[1] = cprArgs[1] + self.cprOffset;
|
cprArgs[1] = cprArgs[1] + self.cprOffset;
|
||||||
}
|
}
|
||||||
self.emit('cursor position report', cprArgs);
|
self.emit('cursor position report', cprArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if((parts = RE_DEV_ATTR_RESPONSE_ANYWHERE.exec(s))) {
|
} else if((parts = RE_DEV_ATTR_RESPONSE_ANYWHERE.exec(s))) {
|
||||||
assert('c' === parts[2]);
|
assert('c' === parts[2]);
|
||||||
var termClient = self.getTermClient(parts[1]);
|
var termClient = self.getTermClient(parts[1]);
|
||||||
if(termClient) {
|
if(termClient) {
|
||||||
self.term.termClient = termClient;
|
self.term.termClient = termClient;
|
||||||
}
|
}
|
||||||
} else if('\r' === s) {
|
} else if('\r' === s) {
|
||||||
key.name = 'return';
|
key.name = 'return';
|
||||||
} else if('\n' === s) {
|
} else if('\n' === s) {
|
||||||
key.name = 'line feed';
|
key.name = 'line feed';
|
||||||
} else if('\t' === s) {
|
} else if('\t' === s) {
|
||||||
key.name = 'tab';
|
key.name = 'tab';
|
||||||
} else if('\x7f' === s) {
|
} else if('\x7f' === s) {
|
||||||
//
|
//
|
||||||
// Backspace vs delete is a crazy thing, especially in *nix.
|
// Backspace vs delete is a crazy thing, especially in *nix.
|
||||||
// - ANSI-BBS uses 0x7f for DEL
|
// - ANSI-BBS uses 0x7f for DEL
|
||||||
// - xterm et. al clients send 0x7f for backspace... ugg.
|
// - xterm et. al clients send 0x7f for backspace... ugg.
|
||||||
//
|
//
|
||||||
// See http://www.hypexr.org/linux_ruboff.php
|
// See http://www.hypexr.org/linux_ruboff.php
|
||||||
// And a great discussion @ https://lists.debian.org/debian-i18n/1998/04/msg00015.html
|
// And a great discussion @ https://lists.debian.org/debian-i18n/1998/04/msg00015.html
|
||||||
//
|
//
|
||||||
if(self.term.isNixTerm()) {
|
if(self.term.isNixTerm()) {
|
||||||
key.name = 'backspace';
|
key.name = 'backspace';
|
||||||
} else {
|
} else {
|
||||||
key.name = 'delete';
|
key.name = 'delete';
|
||||||
}
|
}
|
||||||
} else if ('\b' === s || '\x1b\x7f' === s || '\x1b\b' === s) {
|
} else if ('\b' === s || '\x1b\x7f' === s || '\x1b\b' === s) {
|
||||||
// backspace, CTRL-H
|
// backspace, CTRL-H
|
||||||
key.name = 'backspace';
|
key.name = 'backspace';
|
||||||
key.meta = ('\x1b' === s.charAt(0));
|
key.meta = ('\x1b' === s.charAt(0));
|
||||||
} else if('\x1b' === s || '\x1b\x1b' === s) {
|
} else if('\x1b' === s || '\x1b\x1b' === s) {
|
||||||
key.name = 'escape';
|
key.name = 'escape';
|
||||||
key.meta = (2 === s.length);
|
key.meta = (2 === s.length);
|
||||||
} else if (' ' === s || '\x1b ' === s) {
|
} else if (' ' === s || '\x1b ' === s) {
|
||||||
// rather annoying that space can come in other than just " "
|
// rather annoying that space can come in other than just " "
|
||||||
key.name = 'space';
|
key.name = 'space';
|
||||||
key.meta = (2 === s.length);
|
key.meta = (2 === s.length);
|
||||||
} else if(1 === s.length && s <= '\x1a') {
|
} else if(1 === s.length && s <= '\x1a') {
|
||||||
// CTRL-<letter>
|
// CTRL-<letter>
|
||||||
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
key.name = String.fromCharCode(s.charCodeAt(0) + 'a'.charCodeAt(0) - 1);
|
||||||
key.ctrl = true;
|
key.ctrl = true;
|
||||||
} else if(1 === s.length && s >= 'a' && s <= 'z') {
|
} else if(1 === s.length && s >= 'a' && s <= 'z') {
|
||||||
// normal, lowercased letter
|
// normal, lowercased letter
|
||||||
key.name = s;
|
key.name = s;
|
||||||
} else if(1 === s.length && s >= 'A' && s <= 'Z') {
|
} else if(1 === s.length && s >= 'A' && s <= 'Z') {
|
||||||
key.name = s.toLowerCase();
|
key.name = s.toLowerCase();
|
||||||
key.shift = true;
|
key.shift = true;
|
||||||
} else if ((parts = RE_META_KEYCODE.exec(s))) {
|
} else if ((parts = RE_META_KEYCODE.exec(s))) {
|
||||||
// meta with character key
|
// meta with character key
|
||||||
key.name = parts[1].toLowerCase();
|
key.name = parts[1].toLowerCase();
|
||||||
key.meta = true;
|
key.meta = true;
|
||||||
key.shift = /^[A-Z]$/.test(parts[1]);
|
key.shift = /^[A-Z]$/.test(parts[1]);
|
||||||
} else if((parts = RE_FUNCTION_KEYCODE.exec(s))) {
|
} else if((parts = RE_FUNCTION_KEYCODE.exec(s))) {
|
||||||
var code =
|
var code =
|
||||||
(parts[1] || '') + (parts[2] || '') +
|
(parts[1] || '') + (parts[2] || '') +
|
||||||
(parts[4] || '') + (parts[9] || '');
|
(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.ctrl = !!(modifier & 4);
|
||||||
key.meta = !!(modifier & 10);
|
key.meta = !!(modifier & 10);
|
||||||
key.shift = !!(modifier & 1);
|
key.shift = !!(modifier & 1);
|
||||||
key.code = code;
|
key.code = code;
|
||||||
|
|
||||||
_.assign(key, self.getKeyComponentsFromCode(code));
|
_.assign(key, self.getKeyComponentsFromCode(code));
|
||||||
}
|
}
|
||||||
|
|
||||||
var ch;
|
var ch;
|
||||||
if(1 === s.length) {
|
if(1 === s.length) {
|
||||||
ch = s;
|
ch = s;
|
||||||
} else if('space' === key.name) {
|
} else if('space' === key.name) {
|
||||||
// stupid hack to always get space as a regular char
|
// stupid hack to always get space as a regular char
|
||||||
ch = ' ';
|
ch = ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isUndefined(key.name)) {
|
if(_.isUndefined(key.name)) {
|
||||||
key = undefined;
|
key = undefined;
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
// Adjust name for CTRL/Shift/Meta modifiers
|
// Adjust name for CTRL/Shift/Meta modifiers
|
||||||
//
|
//
|
||||||
key.name =
|
key.name =
|
||||||
(key.ctrl ? 'ctrl + ' : '') +
|
(key.ctrl ? 'ctrl + ' : '') +
|
||||||
(key.meta ? 'meta + ' : '') +
|
(key.meta ? 'meta + ' : '') +
|
||||||
(key.shift ? 'shift + ' : '') +
|
(key.shift ? 'shift + ' : '') +
|
||||||
key.name;
|
key.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(key || ch) {
|
if(key || ch) {
|
||||||
if(Config().logging.traceUserKeyboardInput) {
|
if(Config().logging.traceUserKeyboardInput) {
|
||||||
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input'); // jshint ignore:line
|
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) {
|
if(!self.ignoreInput) {
|
||||||
self.emit('key press', ch, key);
|
self.emit('key press', ch, key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(Client, stream);
|
require('util').inherits(Client, stream);
|
||||||
|
|
||||||
Client.prototype.setInputOutput = function(input, output) {
|
Client.prototype.setInputOutput = function(input, output) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.output = output;
|
this.output = output;
|
||||||
|
|
||||||
this.term = new term.ClientTerminal(this.output);
|
this.term = new term.ClientTerminal(this.output);
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.setTermType = function(termType) {
|
Client.prototype.setTermType = function(termType) {
|
||||||
this.term.env.TERM = termType;
|
this.term.env.TERM = termType;
|
||||||
this.term.termType = 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() {
|
Client.prototype.startIdleMonitor = function() {
|
||||||
this.lastKeyPressMs = Date.now();
|
this.lastKeyPressMs = Date.now();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Every 1m, check for idle.
|
// Every 1m, check for idle.
|
||||||
//
|
//
|
||||||
this.idleCheck = setInterval( () => {
|
this.idleCheck = setInterval( () => {
|
||||||
const nowMs = Date.now();
|
const nowMs = Date.now();
|
||||||
|
|
||||||
const idleLogoutSeconds = this.user.isAuthenticated() ?
|
const idleLogoutSeconds = this.user.isAuthenticated() ?
|
||||||
Config().misc.idleLogoutSeconds :
|
Config().misc.idleLogoutSeconds :
|
||||||
Config().misc.preAuthIdleLogoutSeconds;
|
Config().misc.preAuthIdleLogoutSeconds;
|
||||||
|
|
||||||
if(nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000)) {
|
if(nowMs - this.lastKeyPressMs >= (idleLogoutSeconds * 1000)) {
|
||||||
this.emit('idle timeout');
|
this.emit('idle timeout');
|
||||||
}
|
}
|
||||||
}, 1000 * 60);
|
}, 1000 * 60);
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.stopIdleMonitor = function() {
|
Client.prototype.stopIdleMonitor = function() {
|
||||||
clearInterval(this.idleCheck);
|
clearInterval(this.idleCheck);
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.end = function () {
|
Client.prototype.end = function () {
|
||||||
if(this.term) {
|
if(this.term) {
|
||||||
this.term.disconnect();
|
this.term.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentModule = this.menuStack.getCurrentModule;
|
var currentModule = this.menuStack.getCurrentModule;
|
||||||
|
|
||||||
if(currentModule) {
|
if(currentModule) {
|
||||||
currentModule.leave();
|
currentModule.leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stopIdleMonitor();
|
this.stopIdleMonitor();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
//
|
//
|
||||||
// We can end up calling 'end' before TTY/etc. is established, e.g. with SSH
|
// We can end up calling 'end' before TTY/etc. is established, e.g. with SSH
|
||||||
//
|
//
|
||||||
// :TODO: is this OK?
|
// :TODO: is this OK?
|
||||||
return this.output.end.apply(this.output, arguments);
|
return this.output.end.apply(this.output, arguments);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// TypeError
|
// TypeError
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.destroy = function () {
|
Client.prototype.destroy = function () {
|
||||||
return this.output.destroy.apply(this.output, arguments);
|
return this.output.destroy.apply(this.output, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.destroySoon = function () {
|
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) {
|
Client.prototype.waitForKeyPress = function(cb) {
|
||||||
this.once('key press', function kp(ch, key) {
|
this.once('key press', function kp(ch, key) {
|
||||||
cb(ch, key);
|
cb(ch, key);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.isLocal = function() {
|
Client.prototype.isLocal = function() {
|
||||||
// :TODO: Handle ipv6 better
|
// :TODO: Handle ipv6 better
|
||||||
return [ '127.0.0.1', '::ffff:127.0.0.1' ].includes(this.remoteAddress);
|
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
|
// :TODO: getDefaultHandler(name) -- handlers in default_handlers.js or something
|
||||||
Client.prototype.defaultHandlerMissingMod = function() {
|
Client.prototype.defaultHandlerMissingMod = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
function handler(err) {
|
function handler(err) {
|
||||||
self.log.error(err);
|
self.log.error(err);
|
||||||
|
|
||||||
self.term.write(ansi.resetScreen());
|
self.term.write(ansi.resetScreen());
|
||||||
self.term.write('An unrecoverable error has been encountered!\n');
|
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('This has been logged for your SysOp to review.\n');
|
||||||
self.term.write('\nGoodbye!\n');
|
self.term.write('\nGoodbye!\n');
|
||||||
|
|
||||||
|
|
||||||
//self.term.write(err);
|
//self.term.write(err);
|
||||||
|
|
||||||
//if(miscUtil.isDevelopment() && err.stack) {
|
//if(miscUtil.isDevelopment() && err.stack) {
|
||||||
// self.term.write('\n' + err.stack + '\n');
|
// self.term.write('\n' + err.stack + '\n');
|
||||||
//}
|
//}
|
||||||
|
|
||||||
self.end();
|
self.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler;
|
return handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
Client.prototype.terminalSupports = function(query) {
|
Client.prototype.terminalSupports = function(query) {
|
||||||
const termClient = this.term.termClient;
|
const termClient = this.term.termClient;
|
||||||
|
|
||||||
switch(query) {
|
switch(query) {
|
||||||
case 'vtx_audio' :
|
case 'vtx_audio' :
|
||||||
// https://github.com/codewar65/VTX_ClientServer/blob/master/vtx.txt
|
// https://github.com/codewar65/VTX_ClientServer/blob/master/vtx.txt
|
||||||
return 'vtx' === termClient;
|
return 'vtx' === termClient;
|
||||||
|
|
||||||
case 'vtx_hyperlink' :
|
case 'vtx_hyperlink' :
|
||||||
return 'vtx' === termClient;
|
return 'vtx' === termClient;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,98 +23,98 @@ function getActiveConnections() { return clientConnections; }
|
||||||
|
|
||||||
function getActiveNodeList(authUsersOnly) {
|
function getActiveNodeList(authUsersOnly) {
|
||||||
|
|
||||||
if(!_.isBoolean(authUsersOnly)) {
|
if(!_.isBoolean(authUsersOnly)) {
|
||||||
authUsersOnly = true;
|
authUsersOnly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = moment();
|
const now = moment();
|
||||||
|
|
||||||
const activeConnections = getActiveConnections().filter(ac => {
|
const activeConnections = getActiveConnections().filter(ac => {
|
||||||
return ((authUsersOnly && ac.user.isAuthenticated()) || !authUsersOnly);
|
return ((authUsersOnly && ac.user.isAuthenticated()) || !authUsersOnly);
|
||||||
});
|
});
|
||||||
|
|
||||||
return _.map(activeConnections, ac => {
|
return _.map(activeConnections, ac => {
|
||||||
const entry = {
|
const entry = {
|
||||||
node : ac.node,
|
node : ac.node,
|
||||||
authenticated : ac.user.isAuthenticated(),
|
authenticated : ac.user.isAuthenticated(),
|
||||||
userId : ac.user.userId,
|
userId : ac.user.userId,
|
||||||
action : _.has(ac, 'currentMenuModule.menuConfig.desc') ? ac.currentMenuModule.menuConfig.desc : 'Unknown',
|
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
|
// There may be a connection, but not a logged in user as of yet
|
||||||
//
|
//
|
||||||
if(ac.user.isAuthenticated()) {
|
if(ac.user.isAuthenticated()) {
|
||||||
entry.userName = ac.user.username;
|
entry.userName = ac.user.username;
|
||||||
entry.realName = ac.user.properties.real_name;
|
entry.realName = ac.user.properties.real_name;
|
||||||
entry.location = ac.user.properties.location;
|
entry.location = ac.user.properties.location;
|
||||||
entry.affils = ac.user.properties.affiliation;
|
entry.affils = ac.user.properties.affiliation;
|
||||||
|
|
||||||
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
const diff = now.diff(moment(ac.user.properties.last_login_timestamp), 'minutes');
|
||||||
entry.timeOn = moment.duration(diff, 'minutes');
|
entry.timeOn = moment.duration(diff, 'minutes');
|
||||||
}
|
}
|
||||||
return entry;
|
return entry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewClient(client, clientSock) {
|
function addNewClient(client, clientSock) {
|
||||||
const id = client.session.id = clientConnections.push(client) - 1;
|
const id = client.session.id = clientConnections.push(client) - 1;
|
||||||
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
const remoteAddress = client.remoteAddress = clientSock.remoteAddress;
|
||||||
|
|
||||||
// create a uniqe identifier one-time ID for this session
|
// create a uniqe identifier one-time ID for this session
|
||||||
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
|
client.session.uniqueId = new hashids('ENiGMA½ClientSession').encode([ id, moment().valueOf() ]);
|
||||||
|
|
||||||
// Create a client specific logger
|
// Create a client specific logger
|
||||||
// Note that this will be updated @ login with additional information
|
// Note that this will be updated @ login with additional information
|
||||||
client.log = logger.log.child( { clientId : id, sessionId : client.session.uniqueId } );
|
client.log = logger.log.child( { clientId : id, sessionId : client.session.uniqueId } );
|
||||||
|
|
||||||
const connInfo = {
|
const connInfo = {
|
||||||
remoteAddress : remoteAddress,
|
remoteAddress : remoteAddress,
|
||||||
serverName : client.session.serverName,
|
serverName : client.session.serverName,
|
||||||
isSecure : client.session.isSecure,
|
isSecure : client.session.isSecure,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(client.log.debug()) {
|
if(client.log.debug()) {
|
||||||
connInfo.port = clientSock.localPort;
|
connInfo.port = clientSock.localPort;
|
||||||
connInfo.family = clientSock.localFamily;
|
connInfo.family = clientSock.localFamily;
|
||||||
}
|
}
|
||||||
|
|
||||||
client.log.info(connInfo, 'Client connected');
|
client.log.info(connInfo, 'Client connected');
|
||||||
|
|
||||||
Events.emit(
|
Events.emit(
|
||||||
Events.getSystemEvents().ClientConnected,
|
Events.getSystemEvents().ClientConnected,
|
||||||
{ client : client, connectionCount : clientConnections.length }
|
{ client : client, connectionCount : clientConnections.length }
|
||||||
);
|
);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeClient(client) {
|
function removeClient(client) {
|
||||||
client.end();
|
client.end();
|
||||||
|
|
||||||
const i = clientConnections.indexOf(client);
|
const i = clientConnections.indexOf(client);
|
||||||
if(i > -1) {
|
if(i > -1) {
|
||||||
clientConnections.splice(i, 1);
|
clientConnections.splice(i, 1);
|
||||||
|
|
||||||
logger.log.info(
|
logger.log.info(
|
||||||
{
|
{
|
||||||
connectionCount : clientConnections.length,
|
connectionCount : clientConnections.length,
|
||||||
clientId : client.session.id
|
clientId : client.session.id
|
||||||
},
|
},
|
||||||
'Client disconnected'
|
'Client disconnected'
|
||||||
);
|
);
|
||||||
|
|
||||||
if(client.user && client.user.isValid()) {
|
if(client.user && client.user.isValid()) {
|
||||||
Events.emit(Events.getSystemEvents().UserLogoff, { user : client.user } );
|
Events.emit(Events.getSystemEvents().UserLogoff, { user : client.user } );
|
||||||
}
|
}
|
||||||
|
|
||||||
Events.emit(
|
Events.emit(
|
||||||
Events.getSystemEvents().ClientDisconnected,
|
Events.getSystemEvents().ClientDisconnected,
|
||||||
{ client : client, connectionCount : clientConnections.length }
|
{ client : client, connectionCount : clientConnections.length }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConnectionByUserId(userId) {
|
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;
|
exports.ClientTerminal = ClientTerminal;
|
||||||
|
|
||||||
function ClientTerminal(output) {
|
function ClientTerminal(output) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
|
|
||||||
var outputEncoding = 'cp437';
|
var outputEncoding = 'cp437';
|
||||||
assert(iconv.encodingExists(outputEncoding));
|
assert(iconv.encodingExists(outputEncoding));
|
||||||
|
|
||||||
// convert line feeds such as \n -> \r\n
|
// convert line feeds such as \n -> \r\n
|
||||||
this.convertLF = true;
|
this.convertLF = true;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Some terminal we handle specially
|
// Some terminal we handle specially
|
||||||
// They can also be found in this.env{}
|
// They can also be found in this.env{}
|
||||||
//
|
//
|
||||||
var termType = 'unknown';
|
var termType = 'unknown';
|
||||||
var termHeight = 0;
|
var termHeight = 0;
|
||||||
var termWidth = 0;
|
var termWidth = 0;
|
||||||
var termClient = 'unknown';
|
var termClient = 'unknown';
|
||||||
|
|
||||||
this.currentSyncFont = 'not_set';
|
this.currentSyncFont = 'not_set';
|
||||||
|
|
||||||
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
// Raw values set by e.g. telnet NAWS, ENVIRONMENT, etc.
|
||||||
this.env = {};
|
this.env = {};
|
||||||
|
|
||||||
Object.defineProperty(this, 'outputEncoding', {
|
Object.defineProperty(this, 'outputEncoding', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return outputEncoding;
|
return outputEncoding;
|
||||||
},
|
},
|
||||||
set : function(enc) {
|
set : function(enc) {
|
||||||
if(iconv.encodingExists(enc)) {
|
if(iconv.encodingExists(enc)) {
|
||||||
outputEncoding = enc;
|
outputEncoding = enc;
|
||||||
} else {
|
} else {
|
||||||
Log.warn({ encoding : enc }, 'Unknown encoding');
|
Log.warn({ encoding : enc }, 'Unknown encoding');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, 'termType', {
|
Object.defineProperty(this, 'termType', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return termType;
|
return termType;
|
||||||
},
|
},
|
||||||
set : function(ttype) {
|
set : function(ttype) {
|
||||||
termType = ttype.toLowerCase();
|
termType = ttype.toLowerCase();
|
||||||
|
|
||||||
if(this.isANSI()) {
|
if(this.isANSI()) {
|
||||||
this.outputEncoding = 'cp437';
|
this.outputEncoding = 'cp437';
|
||||||
} else {
|
} else {
|
||||||
// :TODO: See how x84 does this -- only set if local/remote are binary
|
// :TODO: See how x84 does this -- only set if local/remote are binary
|
||||||
this.outputEncoding = 'utf8';
|
this.outputEncoding = 'utf8';
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: according to this: http://mud-dev.wikidot.com/article:telnet-client-identification
|
// :TODO: according to this: http://mud-dev.wikidot.com/article:telnet-client-identification
|
||||||
// Windows telnet will send "VTNT". If so, set termClient='windows'
|
// Windows telnet will send "VTNT". If so, set termClient='windows'
|
||||||
// there are some others on the page as well
|
// 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', {
|
Object.defineProperty(this, 'termWidth', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return termWidth;
|
return termWidth;
|
||||||
},
|
},
|
||||||
set : function(width) {
|
set : function(width) {
|
||||||
if(width > 0) {
|
if(width > 0) {
|
||||||
termWidth = width;
|
termWidth = width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, 'termHeight', {
|
Object.defineProperty(this, 'termHeight', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return termHeight;
|
return termHeight;
|
||||||
},
|
},
|
||||||
set : function(height) {
|
set : function(height) {
|
||||||
if(height > 0) {
|
if(height > 0) {
|
||||||
termHeight = height;
|
termHeight = height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, 'termClient', {
|
Object.defineProperty(this, 'termClient', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return termClient;
|
return termClient;
|
||||||
},
|
},
|
||||||
set : function(tc) {
|
set : function(tc) {
|
||||||
termClient = 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() {
|
ClientTerminal.prototype.disconnect = function() {
|
||||||
this.output = null;
|
this.output = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientTerminal.prototype.isNixTerm = function() {
|
ClientTerminal.prototype.isNixTerm = function() {
|
||||||
//
|
//
|
||||||
// Standard *nix type terminals
|
// Standard *nix type terminals
|
||||||
//
|
//
|
||||||
if(this.termType.startsWith('xterm')) {
|
if(this.termType.startsWith('xterm')) {
|
||||||
return true;
|
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() {
|
ClientTerminal.prototype.isANSI = function() {
|
||||||
//
|
//
|
||||||
// ANSI terminals should be encoded to CP437
|
// ANSI terminals should be encoded to CP437
|
||||||
//
|
//
|
||||||
// Some terminal types provided by Mercyful Fate / Enthral:
|
// Some terminal types provided by Mercyful Fate / Enthral:
|
||||||
// ANSI-BBS
|
// ANSI-BBS
|
||||||
// PC-ANSI
|
// PC-ANSI
|
||||||
// QANSI
|
// QANSI
|
||||||
// SCOANSI
|
// SCOANSI
|
||||||
// VT100
|
// VT100
|
||||||
// QNX
|
// QNX
|
||||||
//
|
//
|
||||||
// Reports from various terminals
|
// Reports from various terminals
|
||||||
//
|
//
|
||||||
// syncterm:
|
// syncterm:
|
||||||
// * SyncTERM
|
// * SyncTERM
|
||||||
//
|
//
|
||||||
// xterm:
|
// xterm:
|
||||||
// * PuTTY
|
// * PuTTY
|
||||||
//
|
//
|
||||||
// ansi-bbs:
|
// ansi-bbs:
|
||||||
// * fTelnet
|
// * fTelnet
|
||||||
//
|
//
|
||||||
// pcansi:
|
// pcansi:
|
||||||
// * ZOC
|
// * ZOC
|
||||||
//
|
//
|
||||||
// screen:
|
// screen:
|
||||||
// * ConnectBot (Android)
|
// * ConnectBot (Android)
|
||||||
//
|
//
|
||||||
// linux:
|
// linux:
|
||||||
// * JuiceSSH (note: TERM=linux also)
|
// * JuiceSSH (note: TERM=linux also)
|
||||||
//
|
//
|
||||||
return [ 'ansi', 'pcansi', 'pc-ansi', 'ansi-bbs', 'qansi', 'scoansi', 'syncterm' ].includes(this.termType);
|
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)
|
// :TODO: probably need to update these to convert IAC (0xff) -> IACIAC (escape it)
|
||||||
|
|
||||||
ClientTerminal.prototype.write = function(s, convertLineFeeds, cb) {
|
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) {
|
ClientTerminal.prototype.rawWrite = function(s, cb) {
|
||||||
if(this.output) {
|
if(this.output) {
|
||||||
this.output.write(s, err => {
|
this.output.write(s, err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.warn( { error : err.message }, 'Failed writing to socket');
|
Log.warn( { error : err.message }, 'Failed writing to socket');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientTerminal.prototype.pipeWrite = function(s, spec, cb) {
|
ClientTerminal.prototype.pipeWrite = function(s, spec, cb) {
|
||||||
spec = spec || 'renegade';
|
spec = spec || 'renegade';
|
||||||
|
|
||||||
var conv = {
|
var conv = {
|
||||||
enigma : enigmaToAnsi,
|
enigma : enigmaToAnsi,
|
||||||
renegade : renegadeToAnsi,
|
renegade : renegadeToAnsi,
|
||||||
}[spec] || 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) {
|
ClientTerminal.prototype.encode = function(s, convertLineFeeds) {
|
||||||
convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF;
|
convertLineFeeds = _.isBoolean(convertLineFeeds) ? convertLineFeeds : this.convertLF;
|
||||||
|
|
||||||
if(convertLineFeeds && _.isString(s)) {
|
if(convertLineFeeds && _.isString(s)) {
|
||||||
s = s.replace(/\n/g, '\r\n');
|
s = s.replace(/\n/g, '\r\n');
|
||||||
}
|
}
|
||||||
return iconv.encode(s, this.outputEncoding);
|
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...
|
// :TODO: rid of enigmaToAnsi() -- never really use. Instead, create bbsToAnsi() that supports renegade, PCB, WWIV, etc...
|
||||||
function enigmaToAnsi(s, client) {
|
function enigmaToAnsi(s, client) {
|
||||||
if(-1 == s.indexOf('|')) {
|
if(-1 == s.indexOf('|')) {
|
||||||
return s; // no pipe codes present
|
return s; // no pipe codes present
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = '';
|
var result = '';
|
||||||
var re = /\|([A-Z\d]{2}|\|)/g;
|
var re = /\|([A-Z\d]{2}|\|)/g;
|
||||||
var m;
|
var m;
|
||||||
var lastIndex = 0;
|
var lastIndex = 0;
|
||||||
while((m = re.exec(s))) {
|
while((m = re.exec(s))) {
|
||||||
var val = m[1];
|
var val = m[1];
|
||||||
|
|
||||||
if('|' == val) {
|
if('|' == val) {
|
||||||
result += '|';
|
result += '|';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert to number
|
// convert to number
|
||||||
val = parseInt(val, 10);
|
val = parseInt(val, 10);
|
||||||
if(isNaN(val)) {
|
if(isNaN(val)) {
|
||||||
//
|
//
|
||||||
// ENiGMA MCI code? Only available if |client|
|
// ENiGMA MCI code? Only available if |client|
|
||||||
// is supplied.
|
// is supplied.
|
||||||
//
|
//
|
||||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isString(val)) {
|
if(_.isString(val)) {
|
||||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||||
} else {
|
} else {
|
||||||
assert(val >= 0 && val <= 47);
|
assert(val >= 0 && val <= 47);
|
||||||
|
|
||||||
var attr = '';
|
var attr = '';
|
||||||
if(7 == val) {
|
if(7 == val) {
|
||||||
attr = ansi.sgr('normal');
|
attr = ansi.sgr('normal');
|
||||||
} else if (val < 7 || val >= 16) {
|
} else if (val < 7 || val >= 16) {
|
||||||
attr = ansi.sgr(['normal', val]);
|
attr = ansi.sgr(['normal', val]);
|
||||||
} else if (val <= 15) {
|
} else if (val <= 15) {
|
||||||
attr = ansi.sgr(['normal', val - 8, 'bold']);
|
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) {
|
function stripEnigmaCodes(s) {
|
||||||
return s.replace(/\|[A-Z\d]{2}/g, '');
|
return s.replace(/\|[A-Z\d]{2}/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function enigmaStrLen(s) {
|
function enigmaStrLen(s) {
|
||||||
return stripEnigmaCodes(s).length;
|
return stripEnigmaCodes(s).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ansiSgrFromRenegadeColorCode(cc) {
|
function ansiSgrFromRenegadeColorCode(cc) {
|
||||||
return ansi.sgr({
|
return ansi.sgr({
|
||||||
0 : [ 'reset', 'black' ],
|
0 : [ 'reset', 'black' ],
|
||||||
1 : [ 'reset', 'blue' ],
|
1 : [ 'reset', 'blue' ],
|
||||||
2 : [ 'reset', 'green' ],
|
2 : [ 'reset', 'green' ],
|
||||||
3 : [ 'reset', 'cyan' ],
|
3 : [ 'reset', 'cyan' ],
|
||||||
4 : [ 'reset', 'red' ],
|
4 : [ 'reset', 'red' ],
|
||||||
5 : [ 'reset', 'magenta' ],
|
5 : [ 'reset', 'magenta' ],
|
||||||
6 : [ 'reset', 'yellow' ],
|
6 : [ 'reset', 'yellow' ],
|
||||||
7 : [ 'reset', 'white' ],
|
7 : [ 'reset', 'white' ],
|
||||||
|
|
||||||
8 : [ 'bold', 'black' ],
|
8 : [ 'bold', 'black' ],
|
||||||
9 : [ 'bold', 'blue' ],
|
9 : [ 'bold', 'blue' ],
|
||||||
10 : [ 'bold', 'green' ],
|
10 : [ 'bold', 'green' ],
|
||||||
11 : [ 'bold', 'cyan' ],
|
11 : [ 'bold', 'cyan' ],
|
||||||
12 : [ 'bold', 'red' ],
|
12 : [ 'bold', 'red' ],
|
||||||
13 : [ 'bold', 'magenta' ],
|
13 : [ 'bold', 'magenta' ],
|
||||||
14 : [ 'bold', 'yellow' ],
|
14 : [ 'bold', 'yellow' ],
|
||||||
15 : [ 'bold', 'white' ],
|
15 : [ 'bold', 'white' ],
|
||||||
|
|
||||||
16 : [ 'blackBG' ],
|
16 : [ 'blackBG' ],
|
||||||
17 : [ 'blueBG' ],
|
17 : [ 'blueBG' ],
|
||||||
18 : [ 'greenBG' ],
|
18 : [ 'greenBG' ],
|
||||||
19 : [ 'cyanBG' ],
|
19 : [ 'cyanBG' ],
|
||||||
20 : [ 'redBG' ],
|
20 : [ 'redBG' ],
|
||||||
21 : [ 'magentaBG' ],
|
21 : [ 'magentaBG' ],
|
||||||
22 : [ 'yellowBG' ],
|
22 : [ 'yellowBG' ],
|
||||||
23 : [ 'whiteBG' ],
|
23 : [ 'whiteBG' ],
|
||||||
|
|
||||||
24 : [ 'blink', 'blackBG' ],
|
24 : [ 'blink', 'blackBG' ],
|
||||||
25 : [ 'blink', 'blueBG' ],
|
25 : [ 'blink', 'blueBG' ],
|
||||||
26 : [ 'blink', 'greenBG' ],
|
26 : [ 'blink', 'greenBG' ],
|
||||||
27 : [ 'blink', 'cyanBG' ],
|
27 : [ 'blink', 'cyanBG' ],
|
||||||
28 : [ 'blink', 'redBG' ],
|
28 : [ 'blink', 'redBG' ],
|
||||||
29 : [ 'blink', 'magentaBG' ],
|
29 : [ 'blink', 'magentaBG' ],
|
||||||
30 : [ 'blink', 'yellowBG' ],
|
30 : [ 'blink', 'yellowBG' ],
|
||||||
31 : [ 'blink', 'whiteBG' ],
|
31 : [ 'blink', 'whiteBG' ],
|
||||||
}[cc] || 'normal');
|
}[cc] || 'normal');
|
||||||
}
|
}
|
||||||
|
|
||||||
function renegadeToAnsi(s, client) {
|
function renegadeToAnsi(s, client) {
|
||||||
if(-1 == s.indexOf('|')) {
|
if(-1 == s.indexOf('|')) {
|
||||||
return s; // no pipe codes present
|
return s; // no pipe codes present
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = '';
|
var result = '';
|
||||||
var re = /\|([A-Z\d]{2}|\|)/g;
|
var re = /\|([A-Z\d]{2}|\|)/g;
|
||||||
var m;
|
var m;
|
||||||
var lastIndex = 0;
|
var lastIndex = 0;
|
||||||
while((m = re.exec(s))) {
|
while((m = re.exec(s))) {
|
||||||
var val = m[1];
|
var val = m[1];
|
||||||
|
|
||||||
if('|' == val) {
|
if('|' == val) {
|
||||||
result += '|';
|
result += '|';
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// convert to number
|
// convert to number
|
||||||
val = parseInt(val, 10);
|
val = parseInt(val, 10);
|
||||||
if(isNaN(val)) {
|
if(isNaN(val)) {
|
||||||
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
val = getPredefinedMCIValue(client, m[1]) || ('|' + m[1]); // value itself or literal
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isString(val)) {
|
if(_.isString(val)) {
|
||||||
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
result += s.substr(lastIndex, m.index - lastIndex) + val;
|
||||||
} else {
|
} else {
|
||||||
const attr = ansiSgrFromRenegadeColorCode(val);
|
const attr = ansiSgrFromRenegadeColorCode(val);
|
||||||
result += s.substr(lastIndex, m.index - lastIndex) + attr;
|
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
|
// * http://wiki.synchro.net/custom:colors
|
||||||
//
|
//
|
||||||
function controlCodesToAnsi(s, client) {
|
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 m;
|
||||||
let result = '';
|
let result = '';
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
let v;
|
let v;
|
||||||
let fg;
|
let fg;
|
||||||
let bg;
|
let bg;
|
||||||
|
|
||||||
while((m = RE.exec(s))) {
|
while((m = RE.exec(s))) {
|
||||||
switch(m[0].charAt(0)) {
|
switch(m[0].charAt(0)) {
|
||||||
case '|' :
|
case '|' :
|
||||||
// Renegade or ENiGMA MCI
|
// Renegade or ENiGMA MCI
|
||||||
v = parseInt(m[2], 10);
|
v = parseInt(m[2], 10);
|
||||||
|
|
||||||
if(isNaN(v)) {
|
if(isNaN(v)) {
|
||||||
v = getPredefinedMCIValue(client, m[2]) || m[0]; // value itself or literal
|
v = getPredefinedMCIValue(client, m[2]) || m[0]; // value itself or literal
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isString(v)) {
|
if(_.isString(v)) {
|
||||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||||
} else {
|
} else {
|
||||||
v = ansiSgrFromRenegadeColorCode(v);
|
v = ansiSgrFromRenegadeColorCode(v);
|
||||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '@' :
|
case '@' :
|
||||||
// PCBoard @X## or Wildcat! @##@
|
// PCBoard @X## or Wildcat! @##@
|
||||||
if('@' === m[0].substr(-1)) {
|
if('@' === m[0].substr(-1)) {
|
||||||
// Wildcat!
|
// Wildcat!
|
||||||
v = m[6];
|
v = m[6];
|
||||||
} else {
|
} else {
|
||||||
v = m[4];
|
v = m[4];
|
||||||
}
|
}
|
||||||
|
|
||||||
fg = {
|
fg = {
|
||||||
0 : [ 'reset', 'black' ],
|
0 : [ 'reset', 'black' ],
|
||||||
1 : [ 'reset', 'blue' ],
|
1 : [ 'reset', 'blue' ],
|
||||||
2 : [ 'reset', 'green' ],
|
2 : [ 'reset', 'green' ],
|
||||||
3 : [ 'reset', 'cyan' ],
|
3 : [ 'reset', 'cyan' ],
|
||||||
4 : [ 'reset', 'red' ],
|
4 : [ 'reset', 'red' ],
|
||||||
5 : [ 'reset', 'magenta' ],
|
5 : [ 'reset', 'magenta' ],
|
||||||
6 : [ 'reset', 'yellow' ],
|
6 : [ 'reset', 'yellow' ],
|
||||||
7 : [ 'reset', 'white' ],
|
7 : [ 'reset', 'white' ],
|
||||||
|
|
||||||
8 : [ 'blink', 'black' ],
|
8 : [ 'blink', 'black' ],
|
||||||
9 : [ 'blink', 'blue' ],
|
9 : [ 'blink', 'blue' ],
|
||||||
A : [ 'blink', 'green' ],
|
A : [ 'blink', 'green' ],
|
||||||
B : [ 'blink', 'cyan' ],
|
B : [ 'blink', 'cyan' ],
|
||||||
C : [ 'blink', 'red' ],
|
C : [ 'blink', 'red' ],
|
||||||
D : [ 'blink', 'magenta' ],
|
D : [ 'blink', 'magenta' ],
|
||||||
E : [ 'blink', 'yellow' ],
|
E : [ 'blink', 'yellow' ],
|
||||||
F : [ 'blink', 'white' ],
|
F : [ 'blink', 'white' ],
|
||||||
}[v.charAt(0)] || ['normal'];
|
}[v.charAt(0)] || ['normal'];
|
||||||
|
|
||||||
bg = {
|
bg = {
|
||||||
0 : [ 'blackBG' ],
|
0 : [ 'blackBG' ],
|
||||||
1 : [ 'blueBG' ],
|
1 : [ 'blueBG' ],
|
||||||
2 : [ 'greenBG' ],
|
2 : [ 'greenBG' ],
|
||||||
3 : [ 'cyanBG' ],
|
3 : [ 'cyanBG' ],
|
||||||
4 : [ 'redBG' ],
|
4 : [ 'redBG' ],
|
||||||
5 : [ 'magentaBG' ],
|
5 : [ 'magentaBG' ],
|
||||||
6 : [ 'yellowBG' ],
|
6 : [ 'yellowBG' ],
|
||||||
7 : [ 'whiteBG' ],
|
7 : [ 'whiteBG' ],
|
||||||
|
|
||||||
8 : [ 'bold', 'blackBG' ],
|
8 : [ 'bold', 'blackBG' ],
|
||||||
9 : [ 'bold', 'blueBG' ],
|
9 : [ 'bold', 'blueBG' ],
|
||||||
A : [ 'bold', 'greenBG' ],
|
A : [ 'bold', 'greenBG' ],
|
||||||
B : [ 'bold', 'cyanBG' ],
|
B : [ 'bold', 'cyanBG' ],
|
||||||
C : [ 'bold', 'redBG' ],
|
C : [ 'bold', 'redBG' ],
|
||||||
D : [ 'bold', 'magentaBG' ],
|
D : [ 'bold', 'magentaBG' ],
|
||||||
E : [ 'bold', 'yellowBG' ],
|
E : [ 'bold', 'yellowBG' ],
|
||||||
F : [ 'bold', 'whiteBG' ],
|
F : [ 'bold', 'whiteBG' ],
|
||||||
}[v.charAt(1)] || [ 'normal' ];
|
}[v.charAt(1)] || [ 'normal' ];
|
||||||
|
|
||||||
v = ansi.sgr(fg.concat(bg));
|
v = ansi.sgr(fg.concat(bg));
|
||||||
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
result += s.substr(lastIndex, m.index - lastIndex) + v;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '\x03' :
|
case '\x03' :
|
||||||
v = parseInt(m[8], 10);
|
v = parseInt(m[8], 10);
|
||||||
|
|
||||||
if(isNaN(v)) {
|
if(isNaN(v)) {
|
||||||
v += m[0];
|
v += m[0];
|
||||||
} else {
|
} else {
|
||||||
v = ansi.sgr({
|
v = ansi.sgr({
|
||||||
0 : [ 'reset', 'black' ],
|
0 : [ 'reset', 'black' ],
|
||||||
1 : [ 'bold', 'cyan' ],
|
1 : [ 'bold', 'cyan' ],
|
||||||
2 : [ 'bold', 'yellow' ],
|
2 : [ 'bold', 'yellow' ],
|
||||||
3 : [ 'reset', 'magenta' ],
|
3 : [ 'reset', 'magenta' ],
|
||||||
4 : [ 'bold', 'white', 'blueBG' ],
|
4 : [ 'bold', 'white', 'blueBG' ],
|
||||||
5 : [ 'reset', 'green' ],
|
5 : [ 'reset', 'green' ],
|
||||||
6 : [ 'bold', 'blink', 'red' ],
|
6 : [ 'bold', 'blink', 'red' ],
|
||||||
7 : [ 'bold', 'blue' ],
|
7 : [ 'bold', 'blue' ],
|
||||||
8 : [ 'reset', 'blue' ],
|
8 : [ 'reset', 'blue' ],
|
||||||
9 : [ 'reset', 'cyan' ],
|
9 : [ 'reset', 'cyan' ],
|
||||||
}[v] || 'normal');
|
}[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');
|
const RLogin = require('rlogin');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'CombatNet',
|
name : 'CombatNet',
|
||||||
desc : 'CombatNet Access Module',
|
desc : 'CombatNet Access Module',
|
||||||
author : 'Dave Stephens',
|
author : 'Dave Stephens',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class CombatNetModule extends MenuModule {
|
exports.getModule = class CombatNetModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
// establish defaults
|
// establish defaults
|
||||||
this.config = options.menuConfig.config;
|
this.config = options.menuConfig.config;
|
||||||
this.config.host = this.config.host || 'bbs.combatnet.us';
|
this.config.host = this.config.host || 'bbs.combatnet.us';
|
||||||
this.config.rloginPort = this.config.rloginPort || 4513;
|
this.config.rloginPort = this.config.rloginPort || 4513;
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function validateConfig(callback) {
|
function validateConfig(callback) {
|
||||||
if(!_.isString(self.config.password)) {
|
if(!_.isString(self.config.password)) {
|
||||||
return callback(new Error('Config requires "password"!'));
|
return callback(new Error('Config requires "password"!'));
|
||||||
}
|
}
|
||||||
if(!_.isString(self.config.bbsTag)) {
|
if(!_.isString(self.config.bbsTag)) {
|
||||||
return callback(new Error('Config requires "bbsTag"!'));
|
return callback(new Error('Config requires "bbsTag"!'));
|
||||||
}
|
}
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function establishRloginConnection(callback) {
|
function establishRloginConnection(callback) {
|
||||||
self.client.term.write(resetScreen());
|
self.client.term.write(resetScreen());
|
||||||
self.client.term.write('Connecting to CombatNet, please wait...\n');
|
self.client.term.write('Connecting to CombatNet, please wait...\n');
|
||||||
|
|
||||||
const restorePipeToNormal = function() {
|
const restorePipeToNormal = function() {
|
||||||
if(self.client.term.output) {
|
if(self.client.term.output) {
|
||||||
self.client.term.output.removeListener('data', sendToRloginBuffer);
|
self.client.term.output.removeListener('data', sendToRloginBuffer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rlogin = new RLogin(
|
const rlogin = new RLogin(
|
||||||
{ 'clientUsername' : self.config.password,
|
{ 'clientUsername' : self.config.password,
|
||||||
'serverUsername' : `${self.config.bbsTag}${self.client.user.username}`,
|
'serverUsername' : `${self.config.bbsTag}${self.client.user.username}`,
|
||||||
'host' : self.config.host,
|
'host' : self.config.host,
|
||||||
'port' : self.config.rloginPort,
|
'port' : self.config.rloginPort,
|
||||||
'terminalType' : self.client.term.termClient,
|
'terminalType' : self.client.term.termClient,
|
||||||
'terminalSpeed' : 57600
|
'terminalSpeed' : 57600
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// If there was an error ...
|
// If there was an error ...
|
||||||
rlogin.on('error', err => {
|
rlogin.on('error', err => {
|
||||||
self.client.log.info(`CombatNet rlogin client error: ${err.message}`);
|
self.client.log.info(`CombatNet rlogin client error: ${err.message}`);
|
||||||
restorePipeToNormal();
|
restorePipeToNormal();
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
|
|
||||||
// If we've been disconnected ...
|
// If we've been disconnected ...
|
||||||
rlogin.on('disconnect', () => {
|
rlogin.on('disconnect', () => {
|
||||||
self.client.log.info('Disconnected from CombatNet');
|
self.client.log.info('Disconnected from CombatNet');
|
||||||
restorePipeToNormal();
|
restorePipeToNormal();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
function sendToRloginBuffer(buffer) {
|
function sendToRloginBuffer(buffer) {
|
||||||
rlogin.send(buffer);
|
rlogin.send(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
rlogin.on('connect',
|
rlogin.on('connect',
|
||||||
/* The 'connect' event handler will be supplied with one argument,
|
/* The 'connect' event handler will be supplied with one argument,
|
||||||
a boolean indicating whether or not the connection was established. */
|
a boolean indicating whether or not the connection was established. */
|
||||||
|
|
||||||
function(state) {
|
function(state) {
|
||||||
if(state) {
|
if(state) {
|
||||||
self.client.log.info('Connected to CombatNet');
|
self.client.log.info('Connected to CombatNet');
|
||||||
self.client.term.output.on('data', sendToRloginBuffer);
|
self.client.term.output.on('data', sendToRloginBuffer);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return callback(new Error('Failed to establish establish CombatNet connection'));
|
return callback(new Error('Failed to establish establish CombatNet connection'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
// If data (a Buffer) has been received from the server ...
|
// If data (a Buffer) has been received from the server ...
|
||||||
rlogin.on('data', (data) => {
|
rlogin.on('data', (data) => {
|
||||||
self.client.term.rawWrite(data);
|
self.client.term.rawWrite(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
// connect...
|
// connect...
|
||||||
rlogin.connect();
|
rlogin.connect();
|
||||||
|
|
||||||
// note: no explicit callback() until we're finished!
|
// note: no explicit callback() until we're finished!
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.message }, 'CombatNet error');
|
self.client.log.warn( { error : err.message }, 'CombatNet error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the client is still here, go to previous
|
// if the client is still here, go to previous
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,19 +12,19 @@ exports.sortAreasOrConfs = sortAreasOrConfs;
|
||||||
// Otherwise, use a locale comparison on the sort key or name as a fallback
|
// Otherwise, use a locale comparison on the sort key or name as a fallback
|
||||||
//
|
//
|
||||||
function sortAreasOrConfs(areasOrConfs, type) {
|
function sortAreasOrConfs(areasOrConfs, type) {
|
||||||
let entryA;
|
let entryA;
|
||||||
let entryB;
|
let entryB;
|
||||||
|
|
||||||
areasOrConfs.sort((a, b) => {
|
areasOrConfs.sort((a, b) => {
|
||||||
entryA = type ? a[type] : a;
|
entryA = type ? a[type] : a;
|
||||||
entryB = type ? b[type] : b;
|
entryB = type ? b[type] : b;
|
||||||
|
|
||||||
if(_.isNumber(entryA.sort) && _.isNumber(entryB.sort)) {
|
if(_.isNumber(entryA.sort) && _.isNumber(entryB.sort)) {
|
||||||
return entryA.sort - entryB.sort;
|
return entryA.sort - entryB.sort;
|
||||||
} else {
|
} else {
|
||||||
const keyA = entryA.sort ? entryA.sort.toString() : entryA.name;
|
const keyA = entryA.sort ? entryA.sort.toString() : entryA.name;
|
||||||
const keyB = entryB.sort ? entryB.sort.toString() : entryB.name;
|
const keyB = entryB.sort ? entryB.sort.toString() : entryB.name;
|
||||||
return keyA.localeCompare(keyB, { sensitivity : false, numeric : true } ); // "natural" compare
|
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
|
module.exports = new class ConfigCache
|
||||||
{
|
{
|
||||||
constructor() {
|
constructor() {
|
||||||
this.cache = new Map(); // path->parsed config
|
this.cache = new Map(); // path->parsed config
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfigWithOptions(options, cb) {
|
getConfigWithOptions(options, cb) {
|
||||||
const cached = this.cache.has(options.filePath);
|
const cached = this.cache.has(options.filePath);
|
||||||
|
|
||||||
if(options.forceReCache || !cached) {
|
if(options.forceReCache || !cached) {
|
||||||
this.recacheConfigFromFile(options.filePath, (err, config) => {
|
this.recacheConfigFromFile(options.filePath, (err, config) => {
|
||||||
if(!err && !cached) {
|
if(!err && !cached) {
|
||||||
if(!options.noWatch) {
|
if(!options.noWatch) {
|
||||||
const watcher = sane(
|
const watcher = sane(
|
||||||
paths.dirname(options.filePath),
|
paths.dirname(options.filePath),
|
||||||
{
|
{
|
||||||
glob : `**/${paths.basename(options.filePath)}`
|
glob : `**/${paths.basename(options.filePath)}`
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
watcher.on('change', (fileName, fileRoot) => {
|
watcher.on('change', (fileName, fileRoot) => {
|
||||||
require('./logger.js').log.info( { fileName, fileRoot }, 'Configuration file changed; re-caching');
|
require('./logger.js').log.info( { fileName, fileRoot }, 'Configuration file changed; re-caching');
|
||||||
|
|
||||||
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => {
|
this.recacheConfigFromFile(paths.join(fileRoot, fileName), err => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
if(options.callback) {
|
if(options.callback) {
|
||||||
options.callback( { fileName, fileRoot } );
|
options.callback( { fileName, fileRoot } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cb(err, config, true);
|
return cb(err, config, true);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return cb(null, this.cache.get(options.filePath), false);
|
return cb(null, this.cache.get(options.filePath), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getConfig(filePath, cb) {
|
getConfig(filePath, cb) {
|
||||||
return this.getConfigWithOptions( { filePath }, cb);
|
return this.getConfigWithOptions( { filePath }, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
recacheConfigFromFile(path, cb) {
|
recacheConfigFromFile(path, cb) {
|
||||||
fs.readFile(path, { encoding : 'utf-8' }, (err, data) => {
|
fs.readFile(path, { encoding : 'utf-8' }, (err, data) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed;
|
let parsed;
|
||||||
try {
|
try {
|
||||||
parsed = hjson.parse(data);
|
parsed = hjson.parse(data);
|
||||||
this.cache.set(path, parsed);
|
this.cache.set(path, parsed);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
require('./logger.js').log.error( { filePath : path, error : e.message }, 'Failed to re-cache' );
|
require('./logger.js').log.error( { filePath : path, error : e.message }, 'Failed to re-cache' );
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null, parsed);
|
return cb(null, parsed);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,54 +13,54 @@ exports.init = init;
|
||||||
exports.getFullConfig = getFullConfig;
|
exports.getFullConfig = getFullConfig;
|
||||||
|
|
||||||
function getConfigPath(filePath) {
|
function getConfigPath(filePath) {
|
||||||
// |filePath| is assumed to be in the config path if it's only a file name
|
// |filePath| is assumed to be in the config path if it's only a file name
|
||||||
if('.' === paths.dirname(filePath)) {
|
if('.' === paths.dirname(filePath)) {
|
||||||
filePath = paths.join(Config().paths.config, filePath);
|
filePath = paths.join(Config().paths.config, filePath);
|
||||||
}
|
}
|
||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function init(cb) {
|
function init(cb) {
|
||||||
// pre-cache menu.hjson and prompt.hjson + establish events
|
// pre-cache menu.hjson and prompt.hjson + establish events
|
||||||
const changed = ( { fileName, fileRoot } ) => {
|
const changed = ( { fileName, fileRoot } ) => {
|
||||||
const reCachedPath = paths.join(fileRoot, fileName);
|
const reCachedPath = paths.join(fileRoot, fileName);
|
||||||
if(reCachedPath === getConfigPath(Config().general.menuFile)) {
|
if(reCachedPath === getConfigPath(Config().general.menuFile)) {
|
||||||
Events.emit(Events.getSystemEvents().MenusChanged);
|
Events.emit(Events.getSystemEvents().MenusChanged);
|
||||||
} else if(reCachedPath === getConfigPath(Config().general.promptFile)) {
|
} else if(reCachedPath === getConfigPath(Config().general.promptFile)) {
|
||||||
Events.emit(Events.getSystemEvents().PromptsChanged);
|
Events.emit(Events.getSystemEvents().PromptsChanged);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const config = Config();
|
const config = Config();
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function menu(callback) {
|
function menu(callback) {
|
||||||
return ConfigCache.getConfigWithOptions(
|
return ConfigCache.getConfigWithOptions(
|
||||||
{
|
{
|
||||||
filePath : getConfigPath(config.general.menuFile),
|
filePath : getConfigPath(config.general.menuFile),
|
||||||
callback : changed,
|
callback : changed,
|
||||||
},
|
},
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function prompt(callback) {
|
function prompt(callback) {
|
||||||
return ConfigCache.getConfigWithOptions(
|
return ConfigCache.getConfigWithOptions(
|
||||||
{
|
{
|
||||||
filePath : getConfigPath(config.general.promptFile),
|
filePath : getConfigPath(config.general.promptFile),
|
||||||
callback : changed,
|
callback : changed,
|
||||||
},
|
},
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFullConfig(filePath, cb) {
|
function getFullConfig(filePath, cb) {
|
||||||
ConfigCache.getConfig(getConfigPath(filePath), (err, config) => {
|
ConfigCache.getConfig(getConfigPath(filePath), (err, config) => {
|
||||||
return cb(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;
|
exports.connectEntry = connectEntry;
|
||||||
|
|
||||||
function ansiDiscoverHomePosition(client, cb) {
|
function ansiDiscoverHomePosition(client, cb) {
|
||||||
//
|
//
|
||||||
// We want to find the home position. ANSI-BBS and most terminals
|
// We want to find the home position. ANSI-BBS and most terminals
|
||||||
// utilize 1,1 as home. However, some terminals such as ConnectBot
|
// 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
|
// think of home as 0,0. If this is the case, we need to offset
|
||||||
// our positioning to accomodate for such.
|
// our positioning to accomodate for such.
|
||||||
//
|
//
|
||||||
const done = function(err) {
|
const done = function(err) {
|
||||||
client.removeListener('cursor position report', cprListener);
|
client.removeListener('cursor position report', cprListener);
|
||||||
clearTimeout(giveUpTimer);
|
clearTimeout(giveUpTimer);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cprListener = function(pos) {
|
const cprListener = function(pos) {
|
||||||
const h = pos[0];
|
const h = pos[0];
|
||||||
const w = pos[1];
|
const w = pos[1];
|
||||||
|
|
||||||
//
|
//
|
||||||
// We expect either 0,0, or 1,1. Anything else will be filed as bad data
|
// We expect either 0,0, or 1,1. Anything else will be filed as bad data
|
||||||
//
|
//
|
||||||
if(h > 1 || w > 1) {
|
if(h > 1 || w > 1) {
|
||||||
client.log.warn( { height : h, width : w }, 'Ignoring ANSI home position CPR due to unexpected values');
|
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'));
|
return done(new Error('Home position CPR expected to be 0,0, or 1,1'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(0 === h & 0 === w) {
|
if(0 === h & 0 === w) {
|
||||||
//
|
//
|
||||||
// Store a CPR offset in the client. All CPR's from this point on will offset by this amount
|
// 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.log.info('Setting CPR offset to 1');
|
||||||
client.cprOffset = 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( () => {
|
const giveUpTimer = setTimeout( () => {
|
||||||
return done(new Error('Giving up on home position CPR'));
|
return done(new Error('Giving up on home position CPR'));
|
||||||
}, 3000); // 3s
|
}, 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) {
|
function ansiQueryTermSizeIfNeeded(client, cb) {
|
||||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const done = function(err) {
|
const done = function(err) {
|
||||||
client.removeListener('cursor position report', cprListener);
|
client.removeListener('cursor position report', cprListener);
|
||||||
clearTimeout(giveUpTimer);
|
clearTimeout(giveUpTimer);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
};
|
};
|
||||||
|
|
||||||
const cprListener = function(pos) {
|
const cprListener = function(pos) {
|
||||||
//
|
//
|
||||||
// If we've already found out, disregard
|
// If we've already found out, disregard
|
||||||
//
|
//
|
||||||
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
if(client.term.termHeight > 0 || client.term.termWidth > 0) {
|
||||||
return done(null);
|
return done(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const h = pos[0];
|
const h = pos[0];
|
||||||
const w = pos[1];
|
const w = pos[1];
|
||||||
|
|
||||||
//
|
//
|
||||||
// Netrunner for example gives us 1x1 here. Not really useful. Ignore
|
// Netrunner for example gives us 1x1 here. Not really useful. Ignore
|
||||||
// values that seem obviously bad.
|
// values that seem obviously bad.
|
||||||
//
|
//
|
||||||
if(h < 10 || w < 10) {
|
if(h < 10 || w < 10) {
|
||||||
client.log.warn(
|
client.log.warn(
|
||||||
{ height : h, width : w },
|
{ height : h, width : w },
|
||||||
'Ignoring ANSI CPR screen size query response due to very small values');
|
'Ignoring ANSI CPR screen size query response due to very small values');
|
||||||
return done(new Error('Term size <= 10 considered invalid'));
|
return done(new Error('Term size <= 10 considered invalid'));
|
||||||
}
|
}
|
||||||
|
|
||||||
client.term.termHeight = h;
|
client.term.termHeight = h;
|
||||||
client.term.termWidth = w;
|
client.term.termWidth = w;
|
||||||
|
|
||||||
client.log.debug(
|
client.log.debug(
|
||||||
{
|
{
|
||||||
termWidth : client.term.termWidth,
|
termWidth : client.term.termWidth,
|
||||||
termHeight : client.term.termHeight,
|
termHeight : client.term.termHeight,
|
||||||
source : 'ANSI CPR'
|
source : 'ANSI CPR'
|
||||||
},
|
},
|
||||||
'Window size updated'
|
'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
|
// give up after 2s
|
||||||
const giveUpTimer = setTimeout( () => {
|
const giveUpTimer = setTimeout( () => {
|
||||||
return done(new Error('No term size established by CPR within timeout'));
|
return done(new Error('No term size established by CPR within timeout'));
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
// Start the process: Query for CPR
|
// Start the process: Query for CPR
|
||||||
client.term.rawWrite(ansi.queryScreenSize());
|
client.term.rawWrite(ansi.queryScreenSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareTerminal(term) {
|
function prepareTerminal(term) {
|
||||||
term.rawWrite(ansi.normal());
|
term.rawWrite(ansi.normal());
|
||||||
//term.rawWrite(ansi.disableVT100LineWrapping());
|
//term.rawWrite(ansi.disableVT100LineWrapping());
|
||||||
// :TODO: set xterm stuff -- see x84/others
|
// :TODO: set xterm stuff -- see x84/others
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayBanner(term) {
|
function displayBanner(term) {
|
||||||
// note: intentional formatting:
|
// note: intentional formatting:
|
||||||
term.pipeWrite(`
|
term.pipeWrite(`
|
||||||
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|
|06Connected to |02EN|10i|02GMA|10½ |06BBS version |12|VN
|
||||||
|06Copyright (c) 2014-2018 Bryan Ashby |14- |12http://l33t.codes/
|
|06Copyright (c) 2014-2018 Bryan Ashby |14- |12http://l33t.codes/
|
||||||
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|
|06Updates & source |14- |12https://github.com/NuSkooler/enigma-bbs/
|
||||||
|00`
|
|00`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectEntry(client, nextMenu) {
|
function connectEntry(client, nextMenu) {
|
||||||
const term = client.term;
|
const term = client.term;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function basicPrepWork(callback) {
|
function basicPrepWork(callback) {
|
||||||
term.rawWrite(ansi.queryDeviceAttributes(0));
|
term.rawWrite(ansi.queryDeviceAttributes(0));
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function discoverHomePosition(callback) {
|
function discoverHomePosition(callback) {
|
||||||
ansiDiscoverHomePosition(client, () => {
|
ansiDiscoverHomePosition(client, () => {
|
||||||
// :TODO: If CPR for home fully fails, we should bail out on the connection with an error, e.g. ANSI support required
|
// :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
|
return callback(null); // we try to continue anyway
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function queryTermSizeByNonStandardAnsi(callback) {
|
function queryTermSizeByNonStandardAnsi(callback) {
|
||||||
ansiQueryTermSizeIfNeeded(client, err => {
|
ansiQueryTermSizeIfNeeded(client, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
//
|
//
|
||||||
// Check again; We may have got via NAWS/similar before CPR completed.
|
// Check again; We may have got via NAWS/similar before CPR completed.
|
||||||
//
|
//
|
||||||
if(0 === term.termHeight || 0 === term.termWidth) {
|
if(0 === term.termHeight || 0 === term.termWidth) {
|
||||||
//
|
//
|
||||||
// We still don't have something good for term height/width.
|
// We still don't have something good for term height/width.
|
||||||
// Default to DOS size 80x25.
|
// Default to DOS size 80x25.
|
||||||
//
|
//
|
||||||
// :TODO: Netrunner is currenting hitting this and it feels wrong. Why is NAWS/ENV/CPR all failing???
|
// :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!');
|
client.log.warn( { reason : err.message }, 'Failed to negotiate term size; Defaulting to 80x25!');
|
||||||
|
|
||||||
term.termHeight = 25;
|
term.termHeight = 25;
|
||||||
term.termWidth = 80;
|
term.termWidth = 80;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
prepareTerminal(term);
|
prepareTerminal(term);
|
||||||
|
|
||||||
//
|
//
|
||||||
// Always show an ENiGMA½ banner
|
// Always show an ENiGMA½ banner
|
||||||
//
|
//
|
||||||
displayBanner(term);
|
displayBanner(term);
|
||||||
|
|
||||||
// fire event
|
// fire event
|
||||||
Events.emit(Events.getSystemEvents().TermDetected, { client : client } );
|
Events.emit(Events.getSystemEvents().TermDetected, { client : client } );
|
||||||
|
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return client.menuStack.goto(nextMenu);
|
return client.menuStack.goto(nextMenu);
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
154
core/crc.js
154
core/crc.js
|
@ -2,90 +2,90 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const CRC32_TABLE = new Int32Array([
|
const CRC32_TABLE = new Int32Array([
|
||||||
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535,
|
||||||
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
|
0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd,
|
||||||
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d,
|
||||||
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,
|
||||||
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
||||||
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
|
||||||
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
|
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac,
|
||||||
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
|
||||||
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab,
|
||||||
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
||||||
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
|
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb,
|
||||||
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
||||||
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea,
|
||||||
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
|
0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce,
|
||||||
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
||||||
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
|
||||||
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
|
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409,
|
||||||
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
|
||||||
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
|
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739,
|
||||||
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
||||||
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
|
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268,
|
||||||
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
|
0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0,
|
||||||
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
|
0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8,
|
||||||
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
||||||
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
||||||
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
|
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703,
|
||||||
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7,
|
||||||
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,
|
||||||
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
|
0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae,
|
||||||
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
||||||
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6,
|
||||||
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
|
||||||
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
|
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d,
|
||||||
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
|
0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5,
|
||||||
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
||||||
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
||||||
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
||||||
]);
|
]);
|
||||||
|
|
||||||
exports.CRC32 = class CRC32 {
|
exports.CRC32 = class CRC32 {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.crc = -1;
|
this.crc = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(input) {
|
update(input) {
|
||||||
input = Buffer.isBuffer(input) ? input : Buffer.from(input, 'binary');
|
input = Buffer.isBuffer(input) ? input : Buffer.from(input, 'binary');
|
||||||
return input.length > 10240 ? this.update_8(input) : this.update_4(input);
|
return input.length > 10240 ? this.update_8(input) : this.update_4(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
update_4(input) {
|
update_4(input) {
|
||||||
const len = input.length - 3;
|
const len = input.length - 3;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
for(i = 0; i < len;) {
|
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 + 3) {
|
while(i < len + 3) {
|
||||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_8(input) {
|
update_8(input) {
|
||||||
const len = input.length - 7;
|
const len = input.length - 7;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
for(i = 0; i < len;) {
|
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 ];
|
||||||
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) {
|
while(i < len + 7) {
|
||||||
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
this.crc = (this.crc >>> 8) ^ CRC32_TABLE[ (this.crc ^ input[i++] ) & 0xff ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
finalize() {
|
finalize() {
|
||||||
return (this.crc ^ (-1)) >>> 0;
|
return (this.crc ^ (-1)) >>> 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
330
core/database.js
330
core/database.js
|
@ -25,98 +25,98 @@ exports.initializeDatabases = initializeDatabases;
|
||||||
exports.dbs = dbs;
|
exports.dbs = dbs;
|
||||||
|
|
||||||
function getTransactionDatabase(db) {
|
function getTransactionDatabase(db) {
|
||||||
return sqlite3Trans.wrap(db);
|
return sqlite3Trans.wrap(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDatabasePath(name) {
|
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) {
|
function getModDatabasePath(moduleInfo, suffix) {
|
||||||
//
|
//
|
||||||
// Mods that use a database are stored in Config.paths.modsDb (e.g. enigma-bbs/db/mods)
|
// 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
|
// We expect that moduleInfo defines packageName which will be the base of the modules
|
||||||
// filename. An optional suffix may be supplied as well.
|
// 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])$/;
|
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(_.isObject(moduleInfo));
|
||||||
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
assert(_.isString(moduleInfo.packageName), 'moduleInfo must define "packageName"!');
|
||||||
|
|
||||||
let full = moduleInfo.packageName;
|
let full = moduleInfo.packageName;
|
||||||
if(suffix) {
|
if(suffix) {
|
||||||
full += `.${suffix}`;
|
full += `.${suffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
(full.split('.').length > 1 && HOST_RE.test(full)),
|
(full.split('.').length > 1 && HOST_RE.test(full)),
|
||||||
'packageName must follow Reverse Domain Name Notation - https://en.wikipedia.org/wiki/Reverse_domain_name_notation');
|
'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) {
|
function getISOTimestampString(ts) {
|
||||||
ts = ts || moment();
|
ts = ts || moment();
|
||||||
return ts.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
return ts.format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanatizeString(s) {
|
function sanatizeString(s) {
|
||||||
return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => { // eslint-disable-line no-control-regex
|
return s.replace(/[\0\x08\x09\x1a\n\r"'\\%]/g, c => { // eslint-disable-line no-control-regex
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '\0' : return '\\0';
|
case '\0' : return '\\0';
|
||||||
case '\x08' : return '\\b';
|
case '\x08' : return '\\b';
|
||||||
case '\x09' : return '\\t';
|
case '\x09' : return '\\t';
|
||||||
case '\x1a' : return '\\z';
|
case '\x1a' : return '\\z';
|
||||||
case '\n' : return '\\n';
|
case '\n' : return '\\n';
|
||||||
case '\r' : return '\\r';
|
case '\r' : return '\\r';
|
||||||
|
|
||||||
case '"' :
|
case '"' :
|
||||||
case '\'' :
|
case '\'' :
|
||||||
return `${c}${c}`;
|
return `${c}${c}`;
|
||||||
|
|
||||||
case '\\' :
|
case '\\' :
|
||||||
case '%' :
|
case '%' :
|
||||||
return `\\${c}`;
|
return `\\${c}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initializeDatabases(cb) {
|
function initializeDatabases(cb) {
|
||||||
async.eachSeries( [ 'system', 'user', 'message', 'file' ], (dbName, next) => {
|
async.eachSeries( [ 'system', 'user', 'message', 'file' ], (dbName, next) => {
|
||||||
dbs[dbName] = sqlite3Trans.wrap(new sqlite3.Database(getDatabasePath(dbName), err => {
|
dbs[dbName] = sqlite3Trans.wrap(new sqlite3.Database(getDatabasePath(dbName), err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
dbs[dbName].serialize( () => {
|
dbs[dbName].serialize( () => {
|
||||||
DB_INIT_TABLE[dbName]( () => {
|
DB_INIT_TABLE[dbName]( () => {
|
||||||
return next(null);
|
return next(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
}, err => {
|
}, err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableForeignKeys(db) {
|
function enableForeignKeys(db) {
|
||||||
db.run('PRAGMA foreign_keys = ON;');
|
db.run('PRAGMA foreign_keys = ON;');
|
||||||
}
|
}
|
||||||
|
|
||||||
const DB_INIT_TABLE = {
|
const DB_INIT_TABLE = {
|
||||||
system : (cb) => {
|
system : (cb) => {
|
||||||
enableForeignKeys(dbs.system);
|
enableForeignKeys(dbs.system);
|
||||||
|
|
||||||
// Various stat/event logging - see stat_log.js
|
// Various stat/event logging - see stat_log.js
|
||||||
dbs.system.run(
|
dbs.system.run(
|
||||||
`CREATE TABLE IF NOT EXISTS system_stat (
|
`CREATE TABLE IF NOT EXISTS system_stat (
|
||||||
stat_name VARCHAR PRIMARY KEY NOT NULL,
|
stat_name VARCHAR PRIMARY KEY NOT NULL,
|
||||||
stat_value VARCHAR NOT NULL
|
stat_value VARCHAR NOT NULL
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.system.run(
|
dbs.system.run(
|
||||||
`CREATE TABLE IF NOT EXISTS system_event_log (
|
`CREATE TABLE IF NOT EXISTS system_event_log (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
timestamp DATETIME NOT NULL,
|
timestamp DATETIME NOT NULL,
|
||||||
log_name VARCHAR NOT NULL,
|
log_name VARCHAR NOT NULL,
|
||||||
|
@ -124,10 +124,10 @@ const DB_INIT_TABLE = {
|
||||||
|
|
||||||
UNIQUE(timestamp, log_name)
|
UNIQUE(timestamp, log_name)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.system.run(
|
dbs.system.run(
|
||||||
`CREATE TABLE IF NOT EXISTS user_event_log (
|
`CREATE TABLE IF NOT EXISTS user_event_log (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
timestamp DATETIME NOT NULL,
|
timestamp DATETIME NOT NULL,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
|
@ -136,58 +136,58 @@ const DB_INIT_TABLE = {
|
||||||
|
|
||||||
UNIQUE(timestamp, user_id, log_name)
|
UNIQUE(timestamp, user_id, log_name)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
user : (cb) => {
|
user : (cb) => {
|
||||||
enableForeignKeys(dbs.user);
|
enableForeignKeys(dbs.user);
|
||||||
|
|
||||||
dbs.user.run(
|
dbs.user.run(
|
||||||
`CREATE TABLE IF NOT EXISTS user (
|
`CREATE TABLE IF NOT EXISTS user (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
user_name VARCHAR NOT NULL,
|
user_name VARCHAR NOT NULL,
|
||||||
UNIQUE(user_name)
|
UNIQUE(user_name)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
// :TODO: create FK on delete/etc.
|
// :TODO: create FK on delete/etc.
|
||||||
|
|
||||||
dbs.user.run(
|
dbs.user.run(
|
||||||
`CREATE TABLE IF NOT EXISTS user_property (
|
`CREATE TABLE IF NOT EXISTS user_property (
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
prop_name VARCHAR NOT NULL,
|
prop_name VARCHAR NOT NULL,
|
||||||
prop_value VARCHAR,
|
prop_value VARCHAR,
|
||||||
UNIQUE(user_id, prop_name),
|
UNIQUE(user_id, prop_name),
|
||||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.user.run(
|
dbs.user.run(
|
||||||
`CREATE TABLE IF NOT EXISTS user_group_member (
|
`CREATE TABLE IF NOT EXISTS user_group_member (
|
||||||
group_name VARCHAR NOT NULL,
|
group_name VARCHAR NOT NULL,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
UNIQUE(group_name, user_id)
|
UNIQUE(group_name, user_id)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.user.run(
|
dbs.user.run(
|
||||||
`CREATE TABLE IF NOT EXISTS user_login_history (
|
`CREATE TABLE IF NOT EXISTS user_login_history (
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
user_name VARCHAR NOT NULL,
|
user_name VARCHAR NOT NULL,
|
||||||
timestamp DATETIME NOT NULL
|
timestamp DATETIME NOT NULL
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
message : (cb) => {
|
message : (cb) => {
|
||||||
enableForeignKeys(dbs.message);
|
enableForeignKeys(dbs.message);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TABLE IF NOT EXISTS message (
|
`CREATE TABLE IF NOT EXISTS message (
|
||||||
message_id INTEGER PRIMARY KEY,
|
message_id INTEGER PRIMARY KEY,
|
||||||
area_tag VARCHAR NOT NULL,
|
area_tag VARCHAR NOT NULL,
|
||||||
message_uuid VARCHAR(36) NOT NULL,
|
message_uuid VARCHAR(36) NOT NULL,
|
||||||
|
@ -200,47 +200,47 @@ const DB_INIT_TABLE = {
|
||||||
view_count INTEGER NOT NULL DEFAULT 0,
|
view_count INTEGER NOT NULL DEFAULT 0,
|
||||||
UNIQUE(message_uuid)
|
UNIQUE(message_uuid)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE INDEX IF NOT EXISTS message_by_area_tag_index
|
`CREATE INDEX IF NOT EXISTS message_by_area_tag_index
|
||||||
ON message (area_tag);`
|
ON message (area_tag);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts4 (
|
`CREATE VIRTUAL TABLE IF NOT EXISTS message_fts USING fts4 (
|
||||||
content="message",
|
content="message",
|
||||||
subject,
|
subject,
|
||||||
message
|
message
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN
|
`CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN
|
||||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
||||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN
|
`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);
|
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN
|
`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);
|
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TABLE IF NOT EXISTS message_meta (
|
`CREATE TABLE IF NOT EXISTS message_meta (
|
||||||
message_id INTEGER NOT NULL,
|
message_id INTEGER NOT NULL,
|
||||||
meta_category INTEGER NOT NULL,
|
meta_category INTEGER NOT NULL,
|
||||||
meta_name VARCHAR NOT NULL,
|
meta_name VARCHAR NOT NULL,
|
||||||
|
@ -248,11 +248,11 @@ const DB_INIT_TABLE = {
|
||||||
UNIQUE(message_id, meta_category, meta_name, meta_value),
|
UNIQUE(message_id, meta_category, meta_name, meta_value),
|
||||||
FOREIGN KEY(message_id) REFERENCES message(message_id) ON DELETE CASCADE
|
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(
|
dbs.message.run(
|
||||||
`CREATE TABLE IF NOT EXISTS hash_tag (
|
`CREATE TABLE IF NOT EXISTS hash_tag (
|
||||||
hash_tag_id INTEGER PRIMARY KEY,
|
hash_tag_id INTEGER PRIMARY KEY,
|
||||||
|
@ -270,33 +270,33 @@ const DB_INIT_TABLE = {
|
||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TABLE IF NOT EXISTS user_message_area_last_read (
|
`CREATE TABLE IF NOT EXISTS user_message_area_last_read (
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
area_tag VARCHAR NOT NULL,
|
area_tag VARCHAR NOT NULL,
|
||||||
message_id INTEGER NOT NULL,
|
message_id INTEGER NOT NULL,
|
||||||
UNIQUE(user_id, area_tag)
|
UNIQUE(user_id, area_tag)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.message.run(
|
dbs.message.run(
|
||||||
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
`CREATE TABLE IF NOT EXISTS message_area_last_scan (
|
||||||
scan_toss VARCHAR NOT NULL,
|
scan_toss VARCHAR NOT NULL,
|
||||||
area_tag VARCHAR NOT NULL,
|
area_tag VARCHAR NOT NULL,
|
||||||
message_id INTEGER NOT NULL,
|
message_id INTEGER NOT NULL,
|
||||||
UNIQUE(scan_toss, area_tag)
|
UNIQUE(scan_toss, area_tag)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
file : (cb) => {
|
file : (cb) => {
|
||||||
enableForeignKeys(dbs.file);
|
enableForeignKeys(dbs.file);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
// :TODO: should any of this be unique -- file_sha256 unless dupes are allowed on the system
|
// :TODO: should any of this be unique -- file_sha256 unless dupes are allowed on the system
|
||||||
`CREATE TABLE IF NOT EXISTS file (
|
`CREATE TABLE IF NOT EXISTS file (
|
||||||
file_id INTEGER PRIMARY KEY,
|
file_id INTEGER PRIMARY KEY,
|
||||||
area_tag VARCHAR NOT NULL,
|
area_tag VARCHAR NOT NULL,
|
||||||
file_sha256 VARCHAR NOT NULL,
|
file_sha256 VARCHAR NOT NULL,
|
||||||
|
@ -306,105 +306,105 @@ const DB_INIT_TABLE = {
|
||||||
desc_long, /* FTS @ file_fts */
|
desc_long, /* FTS @ file_fts */
|
||||||
upload_timestamp DATETIME NOT NULL
|
upload_timestamp DATETIME NOT NULL
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE INDEX IF NOT EXISTS file_by_area_tag_index
|
`CREATE INDEX IF NOT EXISTS file_by_area_tag_index
|
||||||
ON file (area_tag);`
|
ON file (area_tag);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE INDEX IF NOT EXISTS file_by_sha256_index
|
`CREATE INDEX IF NOT EXISTS file_by_sha256_index
|
||||||
ON file (file_sha256);`
|
ON file (file_sha256);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts4 (
|
`CREATE VIRTUAL TABLE IF NOT EXISTS file_fts USING fts4 (
|
||||||
content="file",
|
content="file",
|
||||||
file_name,
|
file_name,
|
||||||
desc,
|
desc,
|
||||||
desc_long
|
desc_long
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
|
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
|
||||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
||||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
`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);
|
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TRIGGER IF NOT EXISTS file_after_insert AFTER INSERT ON file BEGIN
|
`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);
|
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS file_meta (
|
`CREATE TABLE IF NOT EXISTS file_meta (
|
||||||
file_id INTEGER NOT NULL,
|
file_id INTEGER NOT NULL,
|
||||||
meta_name VARCHAR NOT NULL,
|
meta_name VARCHAR NOT NULL,
|
||||||
meta_value VARCHAR NOT NULL,
|
meta_value VARCHAR NOT NULL,
|
||||||
UNIQUE(file_id, meta_name, meta_value),
|
UNIQUE(file_id, meta_name, meta_value),
|
||||||
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
FOREIGN KEY(file_id) REFERENCES file(file_id) ON DELETE CASCADE
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS hash_tag (
|
`CREATE TABLE IF NOT EXISTS hash_tag (
|
||||||
hash_tag_id INTEGER PRIMARY KEY,
|
hash_tag_id INTEGER PRIMARY KEY,
|
||||||
hash_tag VARCHAR NOT NULL,
|
hash_tag VARCHAR NOT NULL,
|
||||||
|
|
||||||
UNIQUE(hash_tag)
|
UNIQUE(hash_tag)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS file_hash_tag (
|
`CREATE TABLE IF NOT EXISTS file_hash_tag (
|
||||||
hash_tag_id INTEGER NOT NULL,
|
hash_tag_id INTEGER NOT NULL,
|
||||||
file_id INTEGER NOT NULL,
|
file_id INTEGER NOT NULL,
|
||||||
|
|
||||||
UNIQUE(hash_tag_id, file_id)
|
UNIQUE(hash_tag_id, file_id)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS file_user_rating (
|
`CREATE TABLE IF NOT EXISTS file_user_rating (
|
||||||
file_id INTEGER NOT NULL,
|
file_id INTEGER NOT NULL,
|
||||||
user_id INTEGER NOT NULL,
|
user_id INTEGER NOT NULL,
|
||||||
rating INTEGER NOT NULL,
|
rating INTEGER NOT NULL,
|
||||||
|
|
||||||
UNIQUE(file_id, user_id)
|
UNIQUE(file_id, user_id)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
`CREATE TABLE IF NOT EXISTS file_web_serve (
|
||||||
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
hash_id VARCHAR NOT NULL PRIMARY KEY,
|
||||||
expire_timestamp DATETIME NOT NULL
|
expire_timestamp DATETIME NOT NULL
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
dbs.file.run(
|
dbs.file.run(
|
||||||
`CREATE TABLE IF NOT EXISTS file_web_serve_batch (
|
`CREATE TABLE IF NOT EXISTS file_web_serve_batch (
|
||||||
hash_id VARCHAR NOT NULL,
|
hash_id VARCHAR NOT NULL,
|
||||||
file_id INTEGER NOT NULL,
|
file_id INTEGER NOT NULL,
|
||||||
|
|
||||||
UNIQUE(hash_id, file_id)
|
UNIQUE(hash_id, file_id)
|
||||||
);`
|
);`
|
||||||
);
|
);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -7,66 +7,66 @@ const iconv = require('iconv-lite');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
|
||||||
module.exports = class DescriptIonFile {
|
module.exports = class DescriptIonFile {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.entries = new Map();
|
this.entries = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
get(fileName) {
|
get(fileName) {
|
||||||
return this.entries.get(fileName);
|
return this.entries.get(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDescription(fileName) {
|
getDescription(fileName) {
|
||||||
const entry = this.get(fileName);
|
const entry = this.get(fileName);
|
||||||
if(entry) {
|
if(entry) {
|
||||||
return entry.desc;
|
return entry.desc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createFromFile(path, cb) {
|
static createFromFile(path, cb) {
|
||||||
fs.readFile(path, (err, descData) => {
|
fs.readFile(path, (err, descData) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const descIonFile = new DescriptIonFile();
|
const descIonFile = new DescriptIonFile();
|
||||||
|
|
||||||
// DESCRIPT.ION entries are terminated with a CR and/or LF
|
// DESCRIPT.ION entries are terminated with a CR and/or LF
|
||||||
const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g);
|
const lines = iconv.decode(descData, 'cp437').split(/\r?\n/g);
|
||||||
|
|
||||||
async.each(lines, (entryData, nextLine) => {
|
async.each(lines, (entryData, nextLine) => {
|
||||||
//
|
//
|
||||||
// We allow quoted (long) filenames or non-quoted filenames.
|
// We allow quoted (long) filenames or non-quoted filenames.
|
||||||
// FILENAME<SPC>DESC<0x04><program data><CR/LF>
|
// FILENAME<SPC>DESC<0x04><program data><CR/LF>
|
||||||
//
|
//
|
||||||
const parts = entryData.match(/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/); // eslint-disable-line no-control-regex
|
const parts = entryData.match(/^(?:(?:"([^"]+)" )|(?:([^ ]+) ))([^\x04]+)\x04(.)[^\r\n]*$/); // eslint-disable-line no-control-regex
|
||||||
if(!parts) {
|
if(!parts) {
|
||||||
return nextLine(null);
|
return nextLine(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = parts[1] || parts[2];
|
const fileName = parts[1] || parts[2];
|
||||||
|
|
||||||
//
|
//
|
||||||
// Un-escape CR/LF's
|
// Un-escape CR/LF's
|
||||||
// - escapped \r and/or \n
|
// - escapped \r and/or \n
|
||||||
// - BBBS style @n - See https://www.bbbs.net/sysop.html
|
// - BBBS style @n - See https://www.bbbs.net/sysop.html
|
||||||
//
|
//
|
||||||
const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n');
|
const desc = parts[3].replace(/\\r\\n|\\n|[^@]@n/g, '\r\n');
|
||||||
|
|
||||||
descIonFile.entries.set(
|
descIonFile.entries.set(
|
||||||
fileName,
|
fileName,
|
||||||
{
|
{
|
||||||
desc : desc,
|
desc : desc,
|
||||||
programId : parts[4],
|
programId : parts[4],
|
||||||
programData : parts[5],
|
programData : parts[5],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return nextLine(null);
|
return nextLine(null);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
return cb(null, descIonFile);
|
return cb(null, descIonFile);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
206
core/door.js
206
core/door.js
|
@ -13,137 +13,137 @@ const createServer = require('net').createServer;
|
||||||
exports.Door = Door;
|
exports.Door = Door;
|
||||||
|
|
||||||
function Door(client, exeInfo) {
|
function Door(client, exeInfo) {
|
||||||
events.EventEmitter.call(this);
|
events.EventEmitter.call(this);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.exeInfo = exeInfo;
|
this.exeInfo = exeInfo;
|
||||||
this.exeInfo.encoding = (this.exeInfo.encoding || 'cp437').toLowerCase();
|
this.exeInfo.encoding = (this.exeInfo.encoding || 'cp437').toLowerCase();
|
||||||
let restored = false;
|
let restored = false;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Members of exeInfo:
|
// Members of exeInfo:
|
||||||
// cmd
|
// cmd
|
||||||
// args[]
|
// args[]
|
||||||
// env{}
|
// env{}
|
||||||
// cwd
|
// cwd
|
||||||
// io
|
// io
|
||||||
// encoding
|
// encoding
|
||||||
// dropFile
|
// dropFile
|
||||||
// node
|
// node
|
||||||
// inhSocket
|
// inhSocket
|
||||||
//
|
//
|
||||||
|
|
||||||
this.doorDataHandler = function(data) {
|
this.doorDataHandler = function(data) {
|
||||||
self.client.term.write(decode(data, self.exeInfo.encoding));
|
self.client.term.write(decode(data, self.exeInfo.encoding));
|
||||||
};
|
};
|
||||||
|
|
||||||
this.restoreIo = function(piped) {
|
this.restoreIo = function(piped) {
|
||||||
if(!restored && self.client.term.output) {
|
if(!restored && self.client.term.output) {
|
||||||
self.client.term.output.unpipe(piped);
|
self.client.term.output.unpipe(piped);
|
||||||
self.client.term.output.resume();
|
self.client.term.output.resume();
|
||||||
restored = true;
|
restored = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.prepareSocketIoServer = function(cb) {
|
this.prepareSocketIoServer = function(cb) {
|
||||||
if('socket' === self.exeInfo.io) {
|
if('socket' === self.exeInfo.io) {
|
||||||
const sockServer = createServer(conn => {
|
const sockServer = createServer(conn => {
|
||||||
|
|
||||||
sockServer.getConnections( (err, count) => {
|
sockServer.getConnections( (err, count) => {
|
||||||
|
|
||||||
// We expect only one connection from our DOOR/emulator/etc.
|
// We expect only one connection from our DOOR/emulator/etc.
|
||||||
if(!err && count <= 1) {
|
if(!err && count <= 1) {
|
||||||
self.client.term.output.pipe(conn);
|
self.client.term.output.pipe(conn);
|
||||||
|
|
||||||
conn.on('data', self.doorDataHandler);
|
conn.on('data', self.doorDataHandler);
|
||||||
|
|
||||||
conn.once('end', () => {
|
conn.once('end', () => {
|
||||||
return self.restoreIo(conn);
|
return self.restoreIo(conn);
|
||||||
});
|
});
|
||||||
|
|
||||||
conn.once('error', err => {
|
conn.once('error', err => {
|
||||||
self.client.log.info( { error : err.toString() }, 'Door socket server connection');
|
self.client.log.info( { error : err.toString() }, 'Door socket server connection');
|
||||||
return self.restoreIo(conn);
|
return self.restoreIo(conn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
sockServer.listen(0, () => {
|
sockServer.listen(0, () => {
|
||||||
return cb(null, sockServer);
|
return cb(null, sockServer);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.doorExited = function() {
|
this.doorExited = function() {
|
||||||
self.emit('finished');
|
self.emit('finished');
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(Door, events.EventEmitter);
|
require('util').inherits(Door, events.EventEmitter);
|
||||||
|
|
||||||
Door.prototype.run = function() {
|
Door.prototype.run = function() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.prepareSocketIoServer( (err, sockServer) => {
|
this.prepareSocketIoServer( (err, sockServer) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
this.client.log.warn( { error : err.toString() }, 'Failed executing door');
|
this.client.log.warn( { error : err.toString() }, 'Failed executing door');
|
||||||
return self.doorExited();
|
return self.doorExited();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand arg strings, e.g. {dropFile} -> DOOR32.SYS
|
// Expand arg strings, e.g. {dropFile} -> DOOR32.SYS
|
||||||
// :TODO: Use .map() here
|
// :TODO: Use .map() here
|
||||||
let args = _.clone(self.exeInfo.args); // we need a copy so the original is not modified
|
let args = _.clone(self.exeInfo.args); // we need a copy so the original is not modified
|
||||||
|
|
||||||
for(let i = 0; i < args.length; ++i) {
|
for(let i = 0; i < args.length; ++i) {
|
||||||
args[i] = stringFormat(self.exeInfo.args[i], {
|
args[i] = stringFormat(self.exeInfo.args[i], {
|
||||||
dropFile : self.exeInfo.dropFile,
|
dropFile : self.exeInfo.dropFile,
|
||||||
node : self.exeInfo.node.toString(),
|
node : self.exeInfo.node.toString(),
|
||||||
srvPort : sockServer ? sockServer.address().port.toString() : '-1',
|
srvPort : sockServer ? sockServer.address().port.toString() : '-1',
|
||||||
userId : self.client.user.userId.toString(),
|
userId : self.client.user.userId.toString(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const door = pty.spawn(self.exeInfo.cmd, args, {
|
const door = pty.spawn(self.exeInfo.cmd, args, {
|
||||||
cols : self.client.term.termWidth,
|
cols : self.client.term.termWidth,
|
||||||
rows : self.client.term.termHeight,
|
rows : self.client.term.termHeight,
|
||||||
// :TODO: cwd
|
// :TODO: cwd
|
||||||
env : self.exeInfo.env,
|
env : self.exeInfo.env,
|
||||||
encoding : null, // we want to handle all encoding ourself
|
encoding : null, // we want to handle all encoding ourself
|
||||||
});
|
});
|
||||||
|
|
||||||
if('stdio' === self.exeInfo.io) {
|
if('stdio' === self.exeInfo.io) {
|
||||||
self.client.log.debug('Using stdio for door I/O');
|
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', () => {
|
door.once('close', () => {
|
||||||
return self.restoreIo(door);
|
return self.restoreIo(door);
|
||||||
});
|
});
|
||||||
} else if('socket' === self.exeInfo.io) {
|
} else if('socket' === self.exeInfo.io) {
|
||||||
self.client.log.debug( { port : sockServer.address().port }, 'Using temporary socket server for door I/O');
|
self.client.log.debug( { port : sockServer.address().port }, 'Using temporary socket server for door I/O');
|
||||||
}
|
}
|
||||||
|
|
||||||
door.once('exit', exitCode => {
|
door.once('exit', exitCode => {
|
||||||
self.client.log.info( { exitCode : exitCode }, 'Door exited');
|
self.client.log.info( { exitCode : exitCode }, 'Door exited');
|
||||||
|
|
||||||
if(sockServer) {
|
if(sockServer) {
|
||||||
sockServer.close();
|
sockServer.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// we may not get a close
|
// we may not get a close
|
||||||
if('stdio' === self.exeInfo.io) {
|
if('stdio' === self.exeInfo.io) {
|
||||||
self.restoreIo(door);
|
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;
|
const SSHClient = require('ssh2').Client;
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'DoorParty',
|
name : 'DoorParty',
|
||||||
desc : 'DoorParty Access Module',
|
desc : 'DoorParty Access Module',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class DoorPartyModule extends MenuModule {
|
exports.getModule = class DoorPartyModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
// establish defaults
|
// establish defaults
|
||||||
this.config = options.menuConfig.config;
|
this.config = options.menuConfig.config;
|
||||||
this.config.host = this.config.host || 'dp.throwbackbbs.com';
|
this.config.host = this.config.host || 'dp.throwbackbbs.com';
|
||||||
this.config.sshPort = this.config.sshPort || 2022;
|
this.config.sshPort = this.config.sshPort || 2022;
|
||||||
this.config.rloginPort = this.config.rloginPort || 513;
|
this.config.rloginPort = this.config.rloginPort || 513;
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
let clientTerminated;
|
let clientTerminated;
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function validateConfig(callback) {
|
function validateConfig(callback) {
|
||||||
if(!_.isString(self.config.username)) {
|
if(!_.isString(self.config.username)) {
|
||||||
return callback(new Error('Config requires "username"!'));
|
return callback(new Error('Config requires "username"!'));
|
||||||
}
|
}
|
||||||
if(!_.isString(self.config.password)) {
|
if(!_.isString(self.config.password)) {
|
||||||
return callback(new Error('Config requires "password"!'));
|
return callback(new Error('Config requires "password"!'));
|
||||||
}
|
}
|
||||||
if(!_.isString(self.config.bbsTag)) {
|
if(!_.isString(self.config.bbsTag)) {
|
||||||
return callback(new Error('Config requires "bbsTag"!'));
|
return callback(new Error('Config requires "bbsTag"!'));
|
||||||
}
|
}
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function establishSecureConnection(callback) {
|
function establishSecureConnection(callback) {
|
||||||
self.client.term.write(resetScreen());
|
self.client.term.write(resetScreen());
|
||||||
self.client.term.write('Connecting to DoorParty, please wait...\n');
|
self.client.term.write('Connecting to DoorParty, please wait...\n');
|
||||||
|
|
||||||
const sshClient = new SSHClient();
|
const sshClient = new SSHClient();
|
||||||
|
|
||||||
let pipeRestored = false;
|
let pipeRestored = false;
|
||||||
let pipedStream;
|
let pipedStream;
|
||||||
const restorePipe = function() {
|
const restorePipe = function() {
|
||||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||||
self.client.term.output.unpipe(pipedStream);
|
self.client.term.output.unpipe(pipedStream);
|
||||||
self.client.term.output.resume();
|
self.client.term.output.resume();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sshClient.on('ready', () => {
|
sshClient.on('ready', () => {
|
||||||
// track client termination so we can clean up early
|
// track client termination so we can clean up early
|
||||||
self.client.once('end', () => {
|
self.client.once('end', () => {
|
||||||
self.client.log.info('Connection ended. Terminating DoorParty connection');
|
self.client.log.info('Connection ended. Terminating DoorParty connection');
|
||||||
clientTerminated = true;
|
clientTerminated = true;
|
||||||
sshClient.end();
|
sshClient.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
// establish tunnel for rlogin
|
// establish tunnel for rlogin
|
||||||
sshClient.forwardOut('127.0.0.1', self.config.sshPort, self.config.host, self.config.rloginPort, (err, stream) => {
|
sshClient.forwardOut('127.0.0.1', self.config.sshPort, self.config.host, self.config.rloginPort, (err, stream) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(new Error('Failed to establish tunnel'));
|
return callback(new Error('Failed to establish tunnel'));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Send rlogin
|
// Send rlogin
|
||||||
// DoorParty wants the "server username" portion to be in the format of [BBS_TAG]USERNAME, e.g.
|
// DoorParty wants the "server username" portion to be in the format of [BBS_TAG]USERNAME, e.g.
|
||||||
// [XA]nuskooler
|
// [XA]nuskooler
|
||||||
//
|
//
|
||||||
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
const rlogin = `\x00${self.client.user.username}\x00[${self.config.bbsTag}]${self.client.user.username}\x00${self.client.term.termType}\x00`;
|
||||||
stream.write(rlogin);
|
stream.write(rlogin);
|
||||||
|
|
||||||
pipedStream = stream; // :TODO: this is hacky...
|
pipedStream = stream; // :TODO: this is hacky...
|
||||||
self.client.term.output.pipe(stream);
|
self.client.term.output.pipe(stream);
|
||||||
|
|
||||||
stream.on('data', d => {
|
stream.on('data', d => {
|
||||||
// :TODO: we should just pipe this...
|
// :TODO: we should just pipe this...
|
||||||
self.client.term.rawWrite(d);
|
self.client.term.rawWrite(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on('close', () => {
|
stream.on('close', () => {
|
||||||
restorePipe();
|
restorePipe();
|
||||||
sshClient.end();
|
sshClient.end();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
sshClient.on('error', err => {
|
sshClient.on('error', err => {
|
||||||
self.client.log.info(`DoorParty SSH client error: ${err.message}`);
|
self.client.log.info(`DoorParty SSH client error: ${err.message}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
sshClient.on('close', () => {
|
sshClient.on('close', () => {
|
||||||
restorePipe();
|
restorePipe();
|
||||||
callback(null);
|
callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
sshClient.connect( {
|
sshClient.connect( {
|
||||||
host : self.config.host,
|
host : self.config.host,
|
||||||
port : self.config.sshPort,
|
port : self.config.sshPort,
|
||||||
username : self.config.username,
|
username : self.config.username,
|
||||||
password : self.config.password,
|
password : self.config.password,
|
||||||
});
|
});
|
||||||
|
|
||||||
// note: no explicit callback() until we're finished!
|
// note: no explicit callback() until we're finished!
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.message }, 'DoorParty error');
|
self.client.log.warn( { error : err.message }, 'DoorParty error');
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the client is stil here, go to previous
|
// if the client is stil here, go to previous
|
||||||
if(!clientTerminated) {
|
if(!clientTerminated) {
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,72 +7,72 @@ const FileEntry = require('./file_entry.js');
|
||||||
const { partition } = require('lodash');
|
const { partition } = require('lodash');
|
||||||
|
|
||||||
module.exports = class DownloadQueue {
|
module.exports = class DownloadQueue {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
if(!Array.isArray(this.client.user.downloadQueue)) {
|
if(!Array.isArray(this.client.user.downloadQueue)) {
|
||||||
if(this.client.user.properties.dl_queue) {
|
if(this.client.user.properties.dl_queue) {
|
||||||
this.loadFromProperty(this.client.user.properties.dl_queue);
|
this.loadFromProperty(this.client.user.properties.dl_queue);
|
||||||
} else {
|
} else {
|
||||||
this.client.user.downloadQueue = [];
|
this.client.user.downloadQueue = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get items() {
|
get items() {
|
||||||
return this.client.user.downloadQueue;
|
return this.client.user.downloadQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.client.user.downloadQueue = [];
|
this.client.user.downloadQueue = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(fileEntry, systemFile=false) {
|
toggle(fileEntry, systemFile=false) {
|
||||||
if(this.isQueued(fileEntry)) {
|
if(this.isQueued(fileEntry)) {
|
||||||
this.client.user.downloadQueue = this.client.user.downloadQueue.filter(e => fileEntry.fileId !== e.fileId);
|
this.client.user.downloadQueue = this.client.user.downloadQueue.filter(e => fileEntry.fileId !== e.fileId);
|
||||||
} else {
|
} else {
|
||||||
this.add(fileEntry, systemFile);
|
this.add(fileEntry, systemFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add(fileEntry, systemFile=false) {
|
add(fileEntry, systemFile=false) {
|
||||||
this.client.user.downloadQueue.push({
|
this.client.user.downloadQueue.push({
|
||||||
fileId : fileEntry.fileId,
|
fileId : fileEntry.fileId,
|
||||||
areaTag : fileEntry.areaTag,
|
areaTag : fileEntry.areaTag,
|
||||||
fileName : fileEntry.fileName,
|
fileName : fileEntry.fileName,
|
||||||
path : fileEntry.filePath,
|
path : fileEntry.filePath,
|
||||||
byteSize : fileEntry.meta.byte_size || 0,
|
byteSize : fileEntry.meta.byte_size || 0,
|
||||||
systemFile : systemFile,
|
systemFile : systemFile,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItems(fileIds) {
|
removeItems(fileIds) {
|
||||||
if(!Array.isArray(fileIds)) {
|
if(!Array.isArray(fileIds)) {
|
||||||
fileIds = [ fileIds ];
|
fileIds = [ fileIds ];
|
||||||
}
|
}
|
||||||
|
|
||||||
const [ remain, removed ] = partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
|
const [ remain, removed ] = partition(this.client.user.downloadQueue, e => ( -1 === fileIds.indexOf(e.fileId) ));
|
||||||
this.client.user.downloadQueue = remain;
|
this.client.user.downloadQueue = remain;
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
isQueued(entryOrId) {
|
isQueued(entryOrId) {
|
||||||
if(entryOrId instanceof FileEntry) {
|
if(entryOrId instanceof FileEntry) {
|
||||||
entryOrId = entryOrId.fileId;
|
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) {
|
loadFromProperty(prop) {
|
||||||
try {
|
try {
|
||||||
this.client.user.downloadQueue = JSON.parse(prop);
|
this.client.user.downloadQueue = JSON.parse(prop);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.client.user.downloadQueue = [];
|
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) {
|
function DropFile(client, fileType) {
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.fileType = (fileType || 'DORINFO').toUpperCase();
|
this.fileType = (fileType || 'DORINFO').toUpperCase();
|
||||||
|
|
||||||
Object.defineProperty(this, 'fullPath', {
|
Object.defineProperty(this, 'fullPath', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return paths.join(Config().paths.dropFiles, ('node' + self.client.node), self.fileName);
|
return paths.join(Config().paths.dropFiles, ('node' + self.client.node), self.fileName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, 'fileName', {
|
Object.defineProperty(this, 'fileName', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return {
|
return {
|
||||||
DOOR : 'DOOR.SYS', // GAP BBS, many others
|
DOOR : 'DOOR.SYS', // GAP BBS, many others
|
||||||
DOOR32 : 'DOOR32.SYS', // EleBBS / Mystic, Syncronet, Maximus, Telegard, AdeptXBBS, ...
|
DOOR32 : 'DOOR32.SYS', // EleBBS / Mystic, Syncronet, Maximus, Telegard, AdeptXBBS, ...
|
||||||
CALLINFO : 'CALLINFO.BBS', // Citadel?
|
CALLINFO : 'CALLINFO.BBS', // Citadel?
|
||||||
DORINFO : self.getDoorInfoFileName(), // RBBS, RemoteAccess, QBBS, ...
|
DORINFO : self.getDoorInfoFileName(), // RBBS, RemoteAccess, QBBS, ...
|
||||||
CHAIN : 'CHAIN.TXT', // WWIV
|
CHAIN : 'CHAIN.TXT', // WWIV
|
||||||
CURRUSER : 'CURRUSER.BBS', // RyBBS
|
CURRUSER : 'CURRUSER.BBS', // RyBBS
|
||||||
SFDOORS : 'SFDOORS.DAT', // Spitfire
|
SFDOORS : 'SFDOORS.DAT', // Spitfire
|
||||||
PCBOARD : 'PCBOARD.SYS', // PCBoard
|
PCBOARD : 'PCBOARD.SYS', // PCBoard
|
||||||
TRIBBS : 'TRIBBS.SYS', // TriBBS
|
TRIBBS : 'TRIBBS.SYS', // TriBBS
|
||||||
USERINFO : 'USERINFO.DAT', // Wildcat! 3.0+
|
USERINFO : 'USERINFO.DAT', // Wildcat! 3.0+
|
||||||
JUMPER : 'JUMPER.DAT', // 2AM BBS
|
JUMPER : 'JUMPER.DAT', // 2AM BBS
|
||||||
SXDOOR : // System/X, dESiRE
|
SXDOOR : // System/X, dESiRE
|
||||||
'SXDOOR.' + _.pad(self.client.node.toString(), 3, '0'),
|
'SXDOOR.' + _.pad(self.client.node.toString(), 3, '0'),
|
||||||
INFO : 'INFO.BBS', // Phoenix BBS
|
INFO : 'INFO.BBS', // Phoenix BBS
|
||||||
}[self.fileType];
|
}[self.fileType];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(this, 'dropFileContents', {
|
Object.defineProperty(this, 'dropFileContents', {
|
||||||
get : function() {
|
get : function() {
|
||||||
return {
|
return {
|
||||||
DOOR : self.getDoorSysBuffer(),
|
DOOR : self.getDoorSysBuffer(),
|
||||||
DOOR32 : self.getDoor32Buffer(),
|
DOOR32 : self.getDoor32Buffer(),
|
||||||
DORINFO : self.getDoorInfoDefBuffer(),
|
DORINFO : self.getDoorInfoDefBuffer(),
|
||||||
}[self.fileType];
|
}[self.fileType];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.getDoorInfoFileName = function() {
|
this.getDoorInfoFileName = function() {
|
||||||
var x;
|
var x;
|
||||||
var node = self.client.node;
|
var node = self.client.node;
|
||||||
if(10 === node) {
|
if(10 === node) {
|
||||||
x = 0;
|
x = 0;
|
||||||
} else if(node < 10) {
|
} else if(node < 10) {
|
||||||
x = node;
|
x = node;
|
||||||
} else {
|
} else {
|
||||||
x = String.fromCharCode('a'.charCodeAt(0) + (node - 11));
|
x = String.fromCharCode('a'.charCodeAt(0) + (node - 11));
|
||||||
}
|
}
|
||||||
return 'DORINFO' + x + '.DEF';
|
return 'DORINFO' + x + '.DEF';
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDoorSysBuffer = function() {
|
this.getDoorSysBuffer = function() {
|
||||||
var up = self.client.user.properties;
|
var up = self.client.user.properties;
|
||||||
var now = moment();
|
var now = moment();
|
||||||
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
||||||
|
|
||||||
// :TODO: fix time remaining
|
// :TODO: fix time remaining
|
||||||
// :TODO: fix default protocol -- user prop: transfer_protocol
|
// :TODO: fix default protocol -- user prop: transfer_protocol
|
||||||
|
|
||||||
return iconv.encode( [
|
return iconv.encode( [
|
||||||
'COM1:', // "Comm Port - COM0: = LOCAL MODE"
|
'COM1:', // "Comm Port - COM0: = LOCAL MODE"
|
||||||
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
|
'57600', // "Baud Rate - 300 to 38400" (Note: set as 57600 instead!)
|
||||||
'8', // "Parity - 7 or 8"
|
'8', // "Parity - 7 or 8"
|
||||||
self.client.node.toString(), // "Node Number - 1 to 99"
|
self.client.node.toString(), // "Node Number - 1 to 99"
|
||||||
'57600', // "DTE Rate. Actual BPS rate to use. (kg)"
|
'57600', // "DTE Rate. Actual BPS rate to use. (kg)"
|
||||||
'Y', // "Screen Display - Y=On N=Off (Default to Y)"
|
'Y', // "Screen Display - Y=On N=Off (Default to Y)"
|
||||||
'Y', // "Printer Toggle - 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', // "Page Bell - Y=On N=Off (Default to Y)"
|
||||||
'Y', // "Caller Alarm - 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.real_name || self.client.user.username, // "User Full Name"
|
||||||
up.location || 'Anywhere', // "Calling From"
|
up.location || 'Anywhere', // "Calling From"
|
||||||
'123-456-7890', // "Home Phone"
|
'123-456-7890', // "Home Phone"
|
||||||
'123-456-7890', // "Work/Data Phone"
|
'123-456-7890', // "Work/Data Phone"
|
||||||
'NOPE', // "Password" (Note: this is never given out or even stored plaintext)
|
'NOPE', // "Password" (Note: this is never given out or even stored plaintext)
|
||||||
secLevel, // "Security Level"
|
secLevel, // "Security Level"
|
||||||
up.login_count.toString(), // "Total Times On"
|
up.login_count.toString(), // "Total Times On"
|
||||||
now.format('MM/DD/YY'), // "Last Date Called"
|
now.format('MM/DD/YY'), // "Last Date Called"
|
||||||
'15360', // "Seconds Remaining THIS call (for those that particular)"
|
'15360', // "Seconds Remaining THIS call (for those that particular)"
|
||||||
'256', // "Minutes Remaining THIS call"
|
'256', // "Minutes Remaining THIS call"
|
||||||
'GR', // "Graphics Mode - GR=Graph, NG=Non-Graph, 7E=7,E Caller"
|
'GR', // "Graphics Mode - GR=Graph, NG=Non-Graph, 7E=7,E Caller"
|
||||||
self.client.term.termHeight.toString(), // "Page Length"
|
self.client.term.termHeight.toString(), // "Page Length"
|
||||||
'N', // "User Mode - Y = Expert, N = Novice"
|
'N', // "User Mode - Y = Expert, N = Novice"
|
||||||
'1,2,3,4,5,6,7', // "Conferences/Forums Registered In (ABCDEFG)"
|
'1,2,3,4,5,6,7', // "Conferences/Forums Registered In (ABCDEFG)"
|
||||||
'1', // "Conference Exited To DOOR From (G)"
|
'1', // "Conference Exited To DOOR From (G)"
|
||||||
'01/01/99', // "User Expiration Date (mm/dd/yy)"
|
'01/01/99', // "User Expiration Date (mm/dd/yy)"
|
||||||
self.client.user.userId.toString(), // "User File's Record Number"
|
self.client.user.userId.toString(), // "User File's Record Number"
|
||||||
'Z', // "Default Protocol - X, C, Y, G, I, N, Etc."
|
'Z', // "Default Protocol - X, C, Y, G, I, N, Etc."
|
||||||
// :TODO: fix up, down, etc. form user properties
|
// :TODO: fix up, down, etc. form user properties
|
||||||
'0', // "Total Uploads"
|
'0', // "Total Uploads"
|
||||||
'0', // "Total Downloads"
|
'0', // "Total Downloads"
|
||||||
'0', // "Daily Download "K" Total"
|
'0', // "Daily Download "K" Total"
|
||||||
'999999', // "Daily Download Max. "K" Limit"
|
'999999', // "Daily Download Max. "K" Limit"
|
||||||
moment(up.birthdate).format('MM/DD/YY'), // "Caller's Birthdate"
|
moment(up.birthdate).format('MM/DD/YY'), // "Caller's Birthdate"
|
||||||
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
|
'X:\\MAIN\\', // "Path to the MAIN directory (where User File is)"
|
||||||
'X:\\GEN\\', // "Path to the GEN directory"
|
'X:\\GEN\\', // "Path to the GEN directory"
|
||||||
StatLog.getSystemStat('sysop_username'), // "Sysop's Name (name BBS refers to Sysop as)"
|
StatLog.getSystemStat('sysop_username'), // "Sysop's Name (name BBS refers to Sysop as)"
|
||||||
self.client.user.username, // "Alias name"
|
self.client.user.username, // "Alias name"
|
||||||
'00:05', // "Event time (hh:mm)" (note: wat?)
|
'00:05', // "Event time (hh:mm)" (note: wat?)
|
||||||
'Y', // "If its an error correcting connection (Y/N)"
|
'Y', // "If its an error correcting connection (Y/N)"
|
||||||
'Y', // "ANSI supported & caller using NG mode (Y/N)"
|
'Y', // "ANSI supported & caller using NG mode (Y/N)"
|
||||||
'Y', // "Use Record Locking (Y/N)"
|
'Y', // "Use Record Locking (Y/N)"
|
||||||
'7', // "BBS Default Color (Standard IBM color code, ie, 1-15)"
|
'7', // "BBS Default Color (Standard IBM color code, ie, 1-15)"
|
||||||
// :TODO: fix minutes here also:
|
// :TODO: fix minutes here also:
|
||||||
'256', // "Time Credits In Minutes (positive/negative)"
|
'256', // "Time Credits In Minutes (positive/negative)"
|
||||||
'07/07/90', // "Last New Files Scan Date (mm/dd/yy)"
|
'07/07/90', // "Last New Files Scan Date (mm/dd/yy)"
|
||||||
// :TODO: fix last vs now times:
|
// :TODO: fix last vs now times:
|
||||||
now.format('hh:mm'), // "Time of This Call"
|
now.format('hh:mm'), // "Time of This Call"
|
||||||
now.format('hh:mm'), // "Time of Last Call (hh:mm)"
|
now.format('hh:mm'), // "Time of Last Call (hh:mm)"
|
||||||
'9999', // "Maximum daily files available"
|
'9999', // "Maximum daily files available"
|
||||||
// :TODO: fix these stats:
|
// :TODO: fix these stats:
|
||||||
'0', // "Files d/led so far today"
|
'0', // "Files d/led so far today"
|
||||||
'0', // "Total "K" Bytes Uploaded"
|
'0', // "Total "K" Bytes Uploaded"
|
||||||
'0', // "Total "K" Bytes Downloaded"
|
'0', // "Total "K" Bytes Downloaded"
|
||||||
up.user_comment || 'None', // "User Comment"
|
up.user_comment || 'None', // "User Comment"
|
||||||
'0', // "Total Doors Opened"
|
'0', // "Total Doors Opened"
|
||||||
'0', // "Total Messages Left"
|
'0', // "Total Messages Left"
|
||||||
|
|
||||||
].join('\r\n') + '\r\n', 'cp437');
|
].join('\r\n') + '\r\n', 'cp437');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDoor32Buffer = function() {
|
this.getDoor32Buffer = function() {
|
||||||
//
|
//
|
||||||
// Resources:
|
// Resources:
|
||||||
// * http://wiki.bbses.info/index.php/DOOR32.SYS
|
// * http://wiki.bbses.info/index.php/DOOR32.SYS
|
||||||
//
|
//
|
||||||
// :TODO: local/serial/telnet need to be configurable -- which also changes socket handle!
|
// :TODO: local/serial/telnet need to be configurable -- which also changes socket handle!
|
||||||
return iconv.encode([
|
return iconv.encode([
|
||||||
'2', // :TODO: This needs to be configurable!
|
'2', // :TODO: This needs to be configurable!
|
||||||
// :TODO: Completely broken right now -- This need to be configurable & come from temp socket server most likely
|
// :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!
|
'-1', // self.client.output._handle.fd.toString(), // :TODO: ALWAYS -1 on Windows!
|
||||||
'57600',
|
'57600',
|
||||||
Config().general.boardName,
|
Config().general.boardName,
|
||||||
self.client.user.userId.toString(),
|
self.client.user.userId.toString(),
|
||||||
self.client.user.properties.real_name || self.client.user.username,
|
self.client.user.properties.real_name || self.client.user.username,
|
||||||
self.client.user.username,
|
self.client.user.username,
|
||||||
self.client.user.getLegacySecurityLevel().toString(),
|
self.client.user.getLegacySecurityLevel().toString(),
|
||||||
'546', // :TODO: Minutes left!
|
'546', // :TODO: Minutes left!
|
||||||
'1', // ANSI
|
'1', // ANSI
|
||||||
self.client.node.toString(),
|
self.client.node.toString(),
|
||||||
].join('\r\n') + '\r\n', 'cp437');
|
].join('\r\n') + '\r\n', 'cp437');
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getDoorInfoDefBuffer = function() {
|
this.getDoorInfoDefBuffer = function() {
|
||||||
// :TODO: fix time remaining
|
// :TODO: fix time remaining
|
||||||
|
|
||||||
//
|
//
|
||||||
// Resources:
|
// Resources:
|
||||||
// * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm
|
// * http://goldfndr.home.mindspring.com/dropfile/dorinfo.htm
|
||||||
//
|
//
|
||||||
// Note that usernames are just used for first/last names here
|
// Note that usernames are just used for first/last names here
|
||||||
//
|
//
|
||||||
var opUn = /[^\s]*/.exec(StatLog.getSystemStat('sysop_username'))[0];
|
var opUn = /[^\s]*/.exec(StatLog.getSystemStat('sysop_username'))[0];
|
||||||
var un = /[^\s]*/.exec(self.client.user.username)[0];
|
var un = /[^\s]*/.exec(self.client.user.username)[0];
|
||||||
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
var secLevel = self.client.user.getLegacySecurityLevel().toString();
|
||||||
|
|
||||||
return iconv.encode( [
|
return iconv.encode( [
|
||||||
Config().general.boardName, // "The name of the system."
|
Config().general.boardName, // "The name of the system."
|
||||||
opUn, // "The sysop's name up to the first space."
|
opUn, // "The sysop's name up to the first space."
|
||||||
opUn, // "The sysop's name following 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."
|
'COM1', // "The serial port the modem is connected to, or 0 if logged in on console."
|
||||||
'57600', // "The current port (DTE) rate."
|
'57600', // "The current port (DTE) rate."
|
||||||
'0', // "The number "0""
|
'0', // "The number "0""
|
||||||
un, // "The current user's name, up to the first space."
|
un, // "The current user's name, up to the first space."
|
||||||
un, // "The current user's name, following 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."
|
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."
|
'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."
|
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."
|
'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."
|
'-1' // "The number "-1" if using an external serial driver or "0" if using internal serial routines."
|
||||||
].join('\r\n') + '\r\n', 'cp437');
|
].join('\r\n') + '\r\n', 'cp437');
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DropFile.fileTypes = [ 'DORINFO' ];
|
DropFile.fileTypes = [ 'DORINFO' ];
|
||||||
|
|
||||||
DropFile.prototype.createFile = function(cb) {
|
DropFile.prototype.createFile = function(cb) {
|
||||||
fs.writeFile(this.fullPath, this.dropFileContents, function written(err) {
|
fs.writeFile(this.fullPath, this.dropFileContents, function written(err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,79 +12,79 @@ const _ = require('lodash');
|
||||||
exports.EditTextView = EditTextView;
|
exports.EditTextView = EditTextView;
|
||||||
|
|
||||||
function EditTextView(options) {
|
function EditTextView(options) {
|
||||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||||
options.resizable = false;
|
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() {
|
this.clientBackspace = function() {
|
||||||
const fillCharSGR = this.getStyleSGR(1) || this.getSGR();
|
const fillCharSGR = this.getStyleSGR(1) || this.getSGR();
|
||||||
this.client.term.write(`\b${fillCharSGR}${this.fillChar}\b${this.getFocusSGR()}`);
|
this.client.term.write(`\b${fillCharSGR}${this.fillChar}\b${this.getFocusSGR()}`);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(EditTextView, TextView);
|
require('util').inherits(EditTextView, TextView);
|
||||||
|
|
||||||
EditTextView.prototype.onKeyPress = function(ch, key) {
|
EditTextView.prototype.onKeyPress = function(ch, key) {
|
||||||
if(key) {
|
if(key) {
|
||||||
if(this.isKeyMapped('backspace', key.name)) {
|
if(this.isKeyMapped('backspace', key.name)) {
|
||||||
if(this.text.length > 0) {
|
if(this.text.length > 0) {
|
||||||
this.text = this.text.substr(0, this.text.length - 1);
|
this.text = this.text.substr(0, this.text.length - 1);
|
||||||
|
|
||||||
if(this.text.length >= this.dimens.width) {
|
if(this.text.length >= this.dimens.width) {
|
||||||
this.redraw();
|
this.redraw();
|
||||||
} else {
|
} else {
|
||||||
this.cursorPos.col -= 1;
|
this.cursorPos.col -= 1;
|
||||||
if(this.cursorPos.col >= 0) {
|
if(this.cursorPos.col >= 0) {
|
||||||
this.clientBackspace();
|
this.clientBackspace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
return EditTextView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.cursorPos.col = 0;
|
this.cursorPos.col = 0;
|
||||||
this.setFocus(true); // resetting focus will redraw & adjust cursor
|
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(ch && strUtil.isPrintable(ch)) {
|
||||||
if(this.text.length < this.maxLength) {
|
if(this.text.length < this.maxLength) {
|
||||||
ch = strUtil.stylizeString(ch, this.textStyle);
|
ch = strUtil.stylizeString(ch, this.textStyle);
|
||||||
|
|
||||||
this.text += ch;
|
this.text += ch;
|
||||||
|
|
||||||
if(this.text.length > this.dimens.width) {
|
if(this.text.length > this.dimens.width) {
|
||||||
// no shortcuts - redraw the view
|
// no shortcuts - redraw the view
|
||||||
this.redraw();
|
this.redraw();
|
||||||
} else {
|
} else {
|
||||||
this.cursorPos.col += 1;
|
this.cursorPos.col += 1;
|
||||||
|
|
||||||
if(_.isString(this.textMaskChar)) {
|
if(_.isString(this.textMaskChar)) {
|
||||||
if(this.textMaskChar.length > 0) {
|
if(this.textMaskChar.length > 0) {
|
||||||
this.client.term.write(this.textMaskChar);
|
this.client.term.write(this.textMaskChar);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.client.term.write(ch);
|
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) {
|
EditTextView.prototype.setText = function(text) {
|
||||||
// draw & set |text|
|
// draw & set |text|
|
||||||
EditTextView.super_.prototype.setText.call(this, text);
|
EditTextView.super_.prototype.setText.call(this, text);
|
||||||
|
|
||||||
// adjust local cursor tracking
|
// adjust local cursor tracking
|
||||||
this.cursorPos = { row : 0, col : text.length };
|
this.cursorPos = { row : 0, col : text.length };
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,20 +13,20 @@ const nodeMailer = require('nodemailer');
|
||||||
exports.sendMail = sendMail;
|
exports.sendMail = sendMail;
|
||||||
|
|
||||||
function sendMail(message, cb) {
|
function sendMail(message, cb) {
|
||||||
const config = Config();
|
const config = Config();
|
||||||
if(!_.has(config, 'email.transport')) {
|
if(!_.has(config, 'email.transport')) {
|
||||||
return cb(Errors.MissingConfig('Email "email::transport" configuration missing'));
|
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, {
|
const transportOptions = Object.assign( {}, config.email.transport, {
|
||||||
logger : Log,
|
logger : Log,
|
||||||
});
|
});
|
||||||
|
|
||||||
const transport = nodeMailer.createTransport(transportOptions);
|
const transport = nodeMailer.createTransport(transportOptions);
|
||||||
|
|
||||||
transport.sendMail(message, (err, info) => {
|
transport.sendMail(message, (err, info) => {
|
||||||
return cb(err, info);
|
return cb(err, info);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,45 +2,45 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
class EnigError extends Error {
|
class EnigError extends Error {
|
||||||
constructor(message, code, reason, reasonCode) {
|
constructor(message, code, reason, reasonCode) {
|
||||||
super(message);
|
super(message);
|
||||||
|
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.reason = reason;
|
this.reason = reason;
|
||||||
this.reasonCode = reasonCode;
|
this.reasonCode = reasonCode;
|
||||||
|
|
||||||
if(this.reason) {
|
if(this.reason) {
|
||||||
this.message += `: ${this.reason}`;
|
this.message += `: ${this.reason}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(typeof Error.captureStackTrace === 'function') {
|
if(typeof Error.captureStackTrace === 'function') {
|
||||||
Error.captureStackTrace(this, this.constructor);
|
Error.captureStackTrace(this, this.constructor);
|
||||||
} else {
|
} else {
|
||||||
this.stack = (new Error(message)).stack;
|
this.stack = (new Error(message)).stack;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.EnigError = EnigError;
|
exports.EnigError = EnigError;
|
||||||
|
|
||||||
exports.Errors = {
|
exports.Errors = {
|
||||||
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
|
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
|
||||||
MenuStack : (reason, reasonCode) => new EnigError('Menu stack error', -33001, 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),
|
DoesNotExist : (reason, reasonCode) => new EnigError('Object does not exist', -33002, reason, reasonCode),
|
||||||
AccessDenied : (reason, reasonCode) => new EnigError('Access denied', -32003, reason, reasonCode),
|
AccessDenied : (reason, reasonCode) => new EnigError('Access denied', -32003, reason, reasonCode),
|
||||||
Invalid : (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode),
|
Invalid : (reason, reasonCode) => new EnigError('Invalid', -32004, reason, reasonCode),
|
||||||
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
ExternalProcess : (reason, reasonCode) => new EnigError('External process error', -32005, reason, reasonCode),
|
||||||
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
MissingConfig : (reason, reasonCode) => new EnigError('Missing configuration', -32006, reason, reasonCode),
|
||||||
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode),
|
UnexpectedState : (reason, reasonCode) => new EnigError('Unexpected state', -32007, reason, reasonCode),
|
||||||
MissingParam : (reason, reasonCode) => new EnigError('Missing paramater(s)', -32008, reason, reasonCode),
|
MissingParam : (reason, reasonCode) => new EnigError('Missing paramater(s)', -32008, reason, reasonCode),
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.ErrorReasons = {
|
exports.ErrorReasons = {
|
||||||
AlreadyThere : 'ALREADYTHERE',
|
AlreadyThere : 'ALREADYTHERE',
|
||||||
InvalidNextMenu : 'BADNEXT',
|
InvalidNextMenu : 'BADNEXT',
|
||||||
NoPreviousMenu : 'NOPREV',
|
NoPreviousMenu : 'NOPREV',
|
||||||
NoConditionMatch : 'NOCONDMATCH',
|
NoConditionMatch : 'NOCONDMATCH',
|
||||||
NotEnabled : 'NOTENABLED',
|
NotEnabled : 'NOTENABLED',
|
||||||
};
|
};
|
|
@ -9,10 +9,10 @@ const Log = require('./logger.js').log;
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
module.exports = function(condition, message) {
|
module.exports = function(condition, message) {
|
||||||
if(Config().debug.assertsEnabled) {
|
if(Config().debug.assertsEnabled) {
|
||||||
assert.apply(this, arguments);
|
assert.apply(this, arguments);
|
||||||
} else if(!(condition)) {
|
} else if(!(condition)) {
|
||||||
const stack = new Error().stack;
|
const stack = new Error().stack;
|
||||||
Log.error( { condition : condition, stack : stack }, message || 'Assertion failed' );
|
Log.error( { condition : condition, stack : stack }, message || 'Assertion failed' );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -23,157 +23,157 @@ const net = require('net');
|
||||||
exports.getModule = ErcClientModule;
|
exports.getModule = ErcClientModule;
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'ENiGMA Relay Chat Client',
|
name : 'ENiGMA Relay Chat Client',
|
||||||
desc : 'Chat with other ENiGMA BBSes',
|
desc : 'Chat with other ENiGMA BBSes',
|
||||||
author : 'Andrew Pamment',
|
author : 'Andrew Pamment',
|
||||||
};
|
};
|
||||||
|
|
||||||
var MciViewIds = {
|
var MciViewIds = {
|
||||||
ChatDisplay : 1,
|
ChatDisplay : 1,
|
||||||
InputArea : 3,
|
InputArea : 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
// :TODO: needs converted to ES6 MenuModule subclass
|
// :TODO: needs converted to ES6 MenuModule subclass
|
||||||
function ErcClientModule(options) {
|
function ErcClientModule(options) {
|
||||||
MenuModule.prototype.ctorShim.call(this, options);
|
MenuModule.prototype.ctorShim.call(this, options);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.config = options.menuConfig.config;
|
this.config = options.menuConfig.config;
|
||||||
|
|
||||||
this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}';
|
this.chatEntryFormat = this.config.chatEntryFormat || '[{bbsTag}] {userName}: {message}';
|
||||||
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
|
this.systemEntryFormat = this.config.systemEntryFormat || '[*SYSTEM*] {message}';
|
||||||
|
|
||||||
this.finishedLoading = function() {
|
this.finishedLoading = function() {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function validateConfig(callback) {
|
function validateConfig(callback) {
|
||||||
if(_.isString(self.config.host) &&
|
if(_.isString(self.config.host) &&
|
||||||
_.isNumber(self.config.port) &&
|
_.isNumber(self.config.port) &&
|
||||||
_.isString(self.config.bbsTag))
|
_.isString(self.config.bbsTag))
|
||||||
{
|
{
|
||||||
return callback(null);
|
return callback(null);
|
||||||
} else {
|
} else {
|
||||||
return callback(new Error('Configuration is missing required option(s)'));
|
return callback(new Error('Configuration is missing required option(s)'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function connectToServer(callback) {
|
function connectToServer(callback) {
|
||||||
const connectOpts = {
|
const connectOpts = {
|
||||||
port : self.config.port,
|
port : self.config.port,
|
||||||
host : self.config.host,
|
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.setText('Connecting to server...');
|
||||||
chatMessageView.redraw();
|
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
|
// :TODO: Track actual client->enig connection for optional prevMenu @ final CB
|
||||||
self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host);
|
self.chatConnection = net.createConnection(connectOpts.port, connectOpts.host);
|
||||||
|
|
||||||
self.chatConnection.on('data', data => {
|
self.chatConnection.on('data', data => {
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
|
|
||||||
if(data.startsWith('ERCHANDSHAKE')) {
|
if(data.startsWith('ERCHANDSHAKE')) {
|
||||||
self.chatConnection.write(`ERCMAGIC|${self.config.bbsTag}|${self.client.user.username}\r\n`);
|
self.chatConnection.write(`ERCMAGIC|${self.config.bbsTag}|${self.client.user.username}\r\n`);
|
||||||
} else if(data.startsWith('{')) {
|
} else if(data.startsWith('{')) {
|
||||||
try {
|
try {
|
||||||
data = JSON.parse(data);
|
data = JSON.parse(data);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return self.client.log.warn( { error : e.message }, 'ERC: Error parsing ERC data from server');
|
return self.client.log.warn( { error : e.message }, 'ERC: Error parsing ERC data from server');
|
||||||
}
|
}
|
||||||
|
|
||||||
let text;
|
let text;
|
||||||
try {
|
try {
|
||||||
if(data.userName) {
|
if(data.userName) {
|
||||||
// user message
|
// user message
|
||||||
text = stringFormat(self.chatEntryFormat, data);
|
text = stringFormat(self.chatEntryFormat, data);
|
||||||
} else {
|
} else {
|
||||||
// system message
|
// system message
|
||||||
text = stringFormat(self.systemEntryFormat, data);
|
text = stringFormat(self.systemEntryFormat, data);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return self.client.log.warn( { error : e.message }, 'ERC: chatEntryFormat error');
|
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?
|
if(chatMessageView.getLineCount() > 30) { // :TODO: should probably be ChatDisplay.height?
|
||||||
chatMessageView.deleteLine(0);
|
chatMessageView.deleteLine(0);
|
||||||
chatMessageView.scrollDown();
|
chatMessageView.scrollDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
chatMessageView.redraw();
|
chatMessageView.redraw();
|
||||||
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
self.viewControllers.menu.switchFocus(MciViewIds.InputArea);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.chatConnection.once('end', () => {
|
self.chatConnection.once('end', () => {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.chatConnection.once('error', err => {
|
self.chatConnection.once('error', err => {
|
||||||
self.client.log.info(`ERC connection error: ${err.message}`);
|
self.client.log.info(`ERC connection error: ${err.message}`);
|
||||||
return callback(new Error('Failed connecting to ERC server!'));
|
return callback(new Error('Failed connecting to ERC server!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.message }, 'ERC error');
|
self.client.log.warn( { error : err.message }, 'ERC error');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.scrollHandler = function(keyName) {
|
this.scrollHandler = function(keyName) {
|
||||||
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
||||||
const chatDisplayView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
const chatDisplayView = self.viewControllers.menu.getView(MciViewIds.ChatDisplay);
|
||||||
|
|
||||||
if('up arrow' === keyName) {
|
if('up arrow' === keyName) {
|
||||||
chatDisplayView.scrollUp();
|
chatDisplayView.scrollUp();
|
||||||
} else {
|
} else {
|
||||||
chatDisplayView.scrollDown();
|
chatDisplayView.scrollDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
chatDisplayView.redraw();
|
chatDisplayView.redraw();
|
||||||
inputAreaView.setFocus(true);
|
inputAreaView.setFocus(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
inputAreaSubmit : function(formData, extraArgs, cb) {
|
inputAreaSubmit : function(formData, extraArgs, cb) {
|
||||||
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
const inputAreaView = self.viewControllers.menu.getView(MciViewIds.InputArea);
|
||||||
const inputData = inputAreaView.getData();
|
const inputData = inputAreaView.getData();
|
||||||
|
|
||||||
if('/quit' === inputData.toLowerCase()) {
|
if('/quit' === inputData.toLowerCase()) {
|
||||||
self.chatConnection.end();
|
self.chatConnection.end();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
self.chatConnection.write(`${inputData}\r\n`);
|
self.chatConnection.write(`${inputData}\r\n`);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
self.client.log.warn( { error : e.message }, 'ERC error');
|
self.client.log.warn( { error : e.message }, 'ERC error');
|
||||||
}
|
}
|
||||||
inputAreaView.clearText();
|
inputAreaView.clearText();
|
||||||
}
|
}
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
scrollUp : function(formData, extraArgs, cb) {
|
scrollUp : function(formData, extraArgs, cb) {
|
||||||
self.scrollHandler(formData.key.name);
|
self.scrollHandler(formData.key.name);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
scrollDown : function(formData, extraArgs, cb) {
|
scrollDown : function(formData, extraArgs, cb) {
|
||||||
self.scrollHandler(formData.key.name);
|
self.scrollHandler(formData.key.name);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(ErcClientModule, MenuModule);
|
require('util').inherits(ErcClientModule, MenuModule);
|
||||||
|
|
||||||
ErcClientModule.prototype.mciReady = function(mciData, cb) {
|
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.EventSchedulerModule = EventSchedulerModule; // allow for loadAndStart
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Event Scheduler',
|
name : 'Event Scheduler',
|
||||||
desc : 'Support for scheduling arbritary events',
|
desc : 'Support for scheduling arbritary events',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SCHEDULE_REGEXP = /(?:^|or )?(@watch:)([^\0]+)?$/;
|
const SCHEDULE_REGEXP = /(?:^|or )?(@watch:)([^\0]+)?$/;
|
||||||
const ACTION_REGEXP = /@(method|execute):([^\0]+)?$/;
|
const ACTION_REGEXP = /@(method|execute):([^\0]+)?$/;
|
||||||
|
|
||||||
class ScheduledEvent {
|
class ScheduledEvent {
|
||||||
constructor(events, name) {
|
constructor(events, name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.schedule = this.parseScheduleString(events[name].schedule);
|
this.schedule = this.parseScheduleString(events[name].schedule);
|
||||||
this.action = this.parseActionSpec(events[name].action);
|
this.action = this.parseActionSpec(events[name].action);
|
||||||
if(this.action) {
|
if(this.action) {
|
||||||
this.action.args = events[name].args || [];
|
this.action.args = events[name].args || [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get isValid() {
|
get isValid() {
|
||||||
if((!this.schedule || (!this.schedule.sched && !this.schedule.watchFile)) || !this.action) {
|
if((!this.schedule || (!this.schedule.sched && !this.schedule.watchFile)) || !this.action) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if('method' === this.action.type && !this.action.location) {
|
if('method' === this.action.type && !this.action.location) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
parseScheduleString(schedStr) {
|
parseScheduleString(schedStr) {
|
||||||
if(!schedStr) {
|
if(!schedStr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let schedule = {};
|
let schedule = {};
|
||||||
|
|
||||||
const m = SCHEDULE_REGEXP.exec(schedStr);
|
const m = SCHEDULE_REGEXP.exec(schedStr);
|
||||||
if(m) {
|
if(m) {
|
||||||
schedStr = schedStr.substr(0, m.index).trim();
|
schedStr = schedStr.substr(0, m.index).trim();
|
||||||
|
|
||||||
if('@watch:' === m[1]) {
|
if('@watch:' === m[1]) {
|
||||||
schedule.watchFile = m[2];
|
schedule.watchFile = m[2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(schedStr.length > 0) {
|
if(schedStr.length > 0) {
|
||||||
const sched = later.parse.text(schedStr);
|
const sched = later.parse.text(schedStr);
|
||||||
if(-1 === sched.error) {
|
if(-1 === sched.error) {
|
||||||
schedule.sched = sched;
|
schedule.sched = sched;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return undefined if we couldn't parse out anything useful
|
// return undefined if we couldn't parse out anything useful
|
||||||
if(!_.isEmpty(schedule)) {
|
if(!_.isEmpty(schedule)) {
|
||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parseActionSpec(actionSpec) {
|
parseActionSpec(actionSpec) {
|
||||||
if(actionSpec) {
|
if(actionSpec) {
|
||||||
if('@' === actionSpec[0]) {
|
if('@' === actionSpec[0]) {
|
||||||
const m = ACTION_REGEXP.exec(actionSpec);
|
const m = ACTION_REGEXP.exec(actionSpec);
|
||||||
if(m) {
|
if(m) {
|
||||||
if(m[2].indexOf(':') > -1) {
|
if(m[2].indexOf(':') > -1) {
|
||||||
const parts = m[2].split(':');
|
const parts = m[2].split(':');
|
||||||
return {
|
return {
|
||||||
type : m[1],
|
type : m[1],
|
||||||
location : parts[0],
|
location : parts[0],
|
||||||
what : parts[1],
|
what : parts[1],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type : m[1],
|
type : m[1],
|
||||||
what : m[2],
|
what : m[2],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type : 'execute',
|
type : 'execute',
|
||||||
what : actionSpec,
|
what : actionSpec,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
executeAction(reason, cb) {
|
executeAction(reason, cb) {
|
||||||
Log.info( { eventName : this.name, action : this.action, reason : reason }, 'Executing scheduled event action...');
|
Log.info( { eventName : this.name, action : this.action, reason : reason }, 'Executing scheduled event action...');
|
||||||
|
|
||||||
if('method' === this.action.type) {
|
if('method' === this.action.type) {
|
||||||
const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js')
|
const modulePath = path.join(__dirname, '../', this.action.location); // enigma-bbs base + supplied location (path/file.js')
|
||||||
try {
|
try {
|
||||||
const methodModule = require(modulePath);
|
const methodModule = require(modulePath);
|
||||||
methodModule[this.action.what](this.action.args, err => {
|
methodModule[this.action.what](this.action.args, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.debug(
|
Log.debug(
|
||||||
{ error : err.toString(), eventName : this.name, action : this.action },
|
{ error : err.toString(), eventName : this.name, action : this.action },
|
||||||
'Error performing scheduled event action');
|
'Error performing scheduled event action');
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Log.warn(
|
Log.warn(
|
||||||
{ error : e.toString(), eventName : this.name, action : this.action },
|
{ error : e.toString(), eventName : this.name, action : this.action },
|
||||||
'Failed to perform scheduled event action');
|
'Failed to perform scheduled event action');
|
||||||
|
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
} else if('execute' === this.action.type) {
|
} else if('execute' === this.action.type) {
|
||||||
const opts = {
|
const opts = {
|
||||||
// :TODO: cwd
|
// :TODO: cwd
|
||||||
name : this.name,
|
name : this.name,
|
||||||
cols : 80,
|
cols : 80,
|
||||||
rows : 24,
|
rows : 24,
|
||||||
env : process.env,
|
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 => {
|
proc.once('exit', exitCode => {
|
||||||
if(exitCode) {
|
if(exitCode) {
|
||||||
Log.warn(
|
Log.warn(
|
||||||
{ eventName : this.name, action : this.action, exitCode : exitCode },
|
{ eventName : this.name, action : this.action, exitCode : exitCode },
|
||||||
'Bad exit code while performing scheduled event action');
|
'Bad exit code while performing scheduled event action');
|
||||||
}
|
}
|
||||||
return cb(exitCode ? new Error(`Bad exit code while performing scheduled event action: ${exitCode}`) : null);
|
return cb(exitCode ? new Error(`Bad exit code while performing scheduled event action: ${exitCode}`) : null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventSchedulerModule(options) {
|
function EventSchedulerModule(options) {
|
||||||
PluginModule.call(this, options);
|
PluginModule.call(this, options);
|
||||||
|
|
||||||
const config = Config();
|
const config = Config();
|
||||||
if(_.has(config, 'eventScheduler')) {
|
if(_.has(config, 'eventScheduler')) {
|
||||||
this.moduleConfig = config.eventScheduler;
|
this.moduleConfig = config.eventScheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.runningActions = new Set();
|
this.runningActions = new Set();
|
||||||
|
|
||||||
this.performAction = function(schedEvent, reason) {
|
this.performAction = function(schedEvent, reason) {
|
||||||
if(self.runningActions.has(schedEvent.name)) {
|
if(self.runningActions.has(schedEvent.name)) {
|
||||||
return; // already running
|
return; // already running
|
||||||
}
|
}
|
||||||
|
|
||||||
self.runningActions.add(schedEvent.name);
|
self.runningActions.add(schedEvent.name);
|
||||||
|
|
||||||
schedEvent.executeAction(reason, () => {
|
schedEvent.executeAction(reason, () => {
|
||||||
self.runningActions.delete(schedEvent.name);
|
self.runningActions.delete(schedEvent.name);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// convienence static method for direct load + start
|
// convienence static method for direct load + start
|
||||||
EventSchedulerModule.loadAndStart = function(cb) {
|
EventSchedulerModule.loadAndStart = function(cb) {
|
||||||
const loadModuleEx = require('./module_util.js').loadModuleEx;
|
const loadModuleEx = require('./module_util.js').loadModuleEx;
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
name : path.basename(__filename, '.js'),
|
name : path.basename(__filename, '.js'),
|
||||||
path : __dirname,
|
path : __dirname,
|
||||||
};
|
};
|
||||||
|
|
||||||
loadModuleEx(loadOpts, (err, mod) => {
|
loadModuleEx(loadOpts, (err, mod) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const modInst = new mod.getModule();
|
const modInst = new mod.getModule();
|
||||||
modInst.startup( err => {
|
modInst.startup( err => {
|
||||||
return cb(err, modInst);
|
return cb(err, modInst);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
EventSchedulerModule.prototype.startup = function(cb) {
|
EventSchedulerModule.prototype.startup = function(cb) {
|
||||||
|
|
||||||
this.eventTimers = [];
|
this.eventTimers = [];
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if(this.moduleConfig && _.has(this.moduleConfig, 'events')) {
|
if(this.moduleConfig && _.has(this.moduleConfig, 'events')) {
|
||||||
const events = Object.keys(this.moduleConfig.events).map( name => {
|
const events = Object.keys(this.moduleConfig.events).map( name => {
|
||||||
return new ScheduledEvent(this.moduleConfig.events, name);
|
return new ScheduledEvent(this.moduleConfig.events, name);
|
||||||
});
|
});
|
||||||
|
|
||||||
events.forEach( schedEvent => {
|
events.forEach( schedEvent => {
|
||||||
if(!schedEvent.isValid) {
|
if(!schedEvent.isValid) {
|
||||||
Log.warn( { eventName : schedEvent.name }, 'Invalid scheduled event entry');
|
Log.warn( { eventName : schedEvent.name }, 'Invalid scheduled event entry');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.debug(
|
Log.debug(
|
||||||
{
|
{
|
||||||
eventName : schedEvent.name,
|
eventName : schedEvent.name,
|
||||||
schedule : this.moduleConfig.events[schedEvent.name].schedule,
|
schedule : this.moduleConfig.events[schedEvent.name].schedule,
|
||||||
action : schedEvent.action,
|
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',
|
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'
|
'Scheduled event loaded'
|
||||||
);
|
);
|
||||||
|
|
||||||
if(schedEvent.schedule.sched) {
|
if(schedEvent.schedule.sched) {
|
||||||
this.eventTimers.push(later.setInterval( () => {
|
this.eventTimers.push(later.setInterval( () => {
|
||||||
self.performAction(schedEvent, 'Schedule');
|
self.performAction(schedEvent, 'Schedule');
|
||||||
}, schedEvent.schedule.sched));
|
}, schedEvent.schedule.sched));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(schedEvent.schedule.watchFile) {
|
if(schedEvent.schedule.watchFile) {
|
||||||
const watcher = sane(
|
const watcher = sane(
|
||||||
paths.dirname(schedEvent.schedule.watchFile),
|
paths.dirname(schedEvent.schedule.watchFile),
|
||||||
{
|
{
|
||||||
glob : `**/${paths.basename(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 => {
|
[ 'change', 'add', 'delete' ].forEach(event => {
|
||||||
watcher.on(event, (fileName, fileRoot) => {
|
watcher.on(event, (fileName, fileRoot) => {
|
||||||
const eventPath = paths.join(fileRoot, fileName);
|
const eventPath = paths.join(fileRoot, fileName);
|
||||||
if(schedEvent.schedule.watchFile === eventPath) {
|
if(schedEvent.schedule.watchFile === eventPath) {
|
||||||
self.performAction(schedEvent, `Watch file: ${eventPath}`);
|
self.performAction(schedEvent, `Watch file: ${eventPath}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
fse.exists(schedEvent.schedule.watchFile, exists => {
|
fse.exists(schedEvent.schedule.watchFile, exists => {
|
||||||
if(exists) {
|
if(exists) {
|
||||||
self.performAction(schedEvent, `Watch file: ${schedEvent.schedule.watchFile}`);
|
self.performAction(schedEvent, `Watch file: ${schedEvent.schedule.watchFile}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(null);
|
cb(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
EventSchedulerModule.prototype.shutdown = function(cb) {
|
EventSchedulerModule.prototype.shutdown = function(cb) {
|
||||||
if(this.eventTimers) {
|
if(this.eventTimers) {
|
||||||
this.eventTimers.forEach( et => et.clear() );
|
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');
|
const glob = require('glob');
|
||||||
|
|
||||||
module.exports = new class Events extends events.EventEmitter {
|
module.exports = new class Events extends events.EventEmitter {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.setMaxListeners(32); // :TODO: play with this...
|
this.setMaxListeners(32); // :TODO: play with this...
|
||||||
}
|
}
|
||||||
|
|
||||||
getSystemEvents() {
|
getSystemEvents() {
|
||||||
return SystemEvents;
|
return SystemEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
addListener(event, listener) {
|
addListener(event, listener) {
|
||||||
Log.trace( { event : event }, 'Registering event listener');
|
Log.trace( { event : event }, 'Registering event listener');
|
||||||
return super.addListener(event, listener);
|
return super.addListener(event, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(event, ...args) {
|
emit(event, ...args) {
|
||||||
Log.trace( { event : event }, 'Emitting event');
|
Log.trace( { event : event }, 'Emitting event');
|
||||||
return super.emit(event, ...args);
|
return super.emit(event, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
on(event, listener) {
|
on(event, listener) {
|
||||||
Log.trace( { event : event }, 'Registering event listener');
|
Log.trace( { event : event }, 'Registering event listener');
|
||||||
return super.on(event, listener);
|
return super.on(event, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
once(event, listener) {
|
once(event, listener) {
|
||||||
Log.trace( { event : event }, 'Registering single use event listener');
|
Log.trace( { event : event }, 'Registering single use event listener');
|
||||||
return super.once(event, listener);
|
return super.once(event, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeListener(event, listener) {
|
removeListener(event, listener) {
|
||||||
Log.trace( { event : event }, 'Removing listener');
|
Log.trace( { event : event }, 'Removing listener');
|
||||||
return super.removeListener(event, listener);
|
return super.removeListener(event, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
startup(cb) {
|
startup(cb) {
|
||||||
async.each(require('./module_util.js').getModulePaths(), (modulePath, nextPath) => {
|
async.each(require('./module_util.js').getModulePaths(), (modulePath, nextPath) => {
|
||||||
glob('*{.js,/*.js}', { cwd : modulePath }, (err, files) => {
|
glob('*{.js,/*.js}', { cwd : modulePath }, (err, files) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return nextPath(err);
|
return nextPath(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.each(files, (moduleName, nextModule) => {
|
async.each(files, (moduleName, nextModule) => {
|
||||||
const fullModulePath = paths.join(modulePath, moduleName);
|
const fullModulePath = paths.join(modulePath, moduleName);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const mod = require(fullModulePath);
|
const mod = require(fullModulePath);
|
||||||
|
|
||||||
if(_.isFunction(mod.registerEvents)) {
|
if(_.isFunction(mod.registerEvents)) {
|
||||||
// :TODO: ... or just systemInit() / systemShutdown() & mods could call Events.on() / Events.removeListener() ?
|
// :TODO: ... or just systemInit() / systemShutdown() & mods could call Events.on() / Events.removeListener() ?
|
||||||
mod.registerEvents(this);
|
mod.registerEvents(this);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Log.warn( { error : e }, 'Exception during module "registerEvents"');
|
Log.warn( { error : e }, 'Exception during module "registerEvents"');
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextModule(null);
|
return nextModule(null);
|
||||||
}, err => {
|
}, err => {
|
||||||
return nextPath(err);
|
return nextPath(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
298
core/exodus.js
298
core/exodus.js
|
@ -49,183 +49,183 @@ const SSHClient = require('ssh2').Client;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Exodus',
|
name : 'Exodus',
|
||||||
desc : 'Exodus Door Server Access Module - https://oddnetwork.org/exodus/',
|
desc : 'Exodus Door Server Access Module - https://oddnetwork.org/exodus/',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class ExodusModule extends MenuModule {
|
exports.getModule = class ExodusModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.config = options.menuConfig.config || {};
|
this.config = options.menuConfig.config || {};
|
||||||
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
|
this.config.ticketHost = this.config.ticketHost || 'oddnetwork.org';
|
||||||
this.config.ticketPort = this.config.ticketPort || 1984,
|
this.config.ticketPort = this.config.ticketPort || 1984,
|
||||||
this.config.ticketPath = this.config.ticketPath || '/exodus';
|
this.config.ticketPath = this.config.ticketPath || '/exodus';
|
||||||
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
|
this.config.rejectUnauthorized = _.get(this.config, 'rejectUnauthorized', true);
|
||||||
this.config.sshHost = this.config.sshHost || this.config.ticketHost;
|
this.config.sshHost = this.config.sshHost || this.config.ticketHost;
|
||||||
this.config.sshPort = this.config.sshPort || 22;
|
this.config.sshPort = this.config.sshPort || 22;
|
||||||
this.config.sshUser = this.config.sshUser || 'exodus_server';
|
this.config.sshUser = this.config.sshUser || 'exodus_server';
|
||||||
this.config.sshKeyPem = this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa');
|
this.config.sshKeyPem = this.config.sshKeyPem || joinPath(Config().paths.misc, 'exodus.id_rsa');
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
let clientTerminated = false;
|
let clientTerminated = false;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function validateConfig(callback) {
|
function validateConfig(callback) {
|
||||||
// very basic validation on optionals
|
// very basic validation on optionals
|
||||||
async.each( [ 'board', 'key', 'door' ], (key, next) => {
|
async.each( [ 'board', 'key', 'door' ], (key, next) => {
|
||||||
return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`));
|
return _.isString(self.config[key]) ? next(null) : next(Errors.MissingConfig(`Config requires "${key}"!`));
|
||||||
}, callback);
|
}, callback);
|
||||||
},
|
},
|
||||||
function loadCertAuthorities(callback) {
|
function loadCertAuthorities(callback) {
|
||||||
if(!_.isString(self.config.caPem)) {
|
if(!_.isString(self.config.caPem)) {
|
||||||
return callback(null, null);
|
return callback(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.readFile(self.config.caPem, (err, certAuthorities) => {
|
fs.readFile(self.config.caPem, (err, certAuthorities) => {
|
||||||
return callback(err, certAuthorities);
|
return callback(err, certAuthorities);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getTicket(certAuthorities, callback) {
|
function getTicket(certAuthorities, callback) {
|
||||||
const now = moment.utc().unix();
|
const now = moment.utc().unix();
|
||||||
const sha256 = crypto.createHash('sha256').update(`${self.config.key}${now}`).digest('hex');
|
const sha256 = crypto.createHash('sha256').update(`${self.config.key}${now}`).digest('hex');
|
||||||
const token = `${sha256}|${now}`;
|
const token = `${sha256}|${now}`;
|
||||||
|
|
||||||
const postData = querystring.stringify({
|
const postData = querystring.stringify({
|
||||||
token : token,
|
token : token,
|
||||||
board : self.config.board,
|
board : self.config.board,
|
||||||
user : self.client.user.username,
|
user : self.client.user.username,
|
||||||
door : self.config.door,
|
door : self.config.door,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reqOptions = {
|
const reqOptions = {
|
||||||
hostname : self.config.ticketHost,
|
hostname : self.config.ticketHost,
|
||||||
port : self.config.ticketPort,
|
port : self.config.ticketPort,
|
||||||
path : self.config.ticketPath,
|
path : self.config.ticketPath,
|
||||||
rejectUnauthorized : self.config.rejectUnauthorized,
|
rejectUnauthorized : self.config.rejectUnauthorized,
|
||||||
method : 'POST',
|
method : 'POST',
|
||||||
headers : {
|
headers : {
|
||||||
'Content-Type' : 'application/x-www-form-urlencoded',
|
'Content-Type' : 'application/x-www-form-urlencoded',
|
||||||
'Content-Length' : postData.length,
|
'Content-Length' : postData.length,
|
||||||
'User-Agent' : getEnigmaUserAgent(),
|
'User-Agent' : getEnigmaUserAgent(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(certAuthorities) {
|
if(certAuthorities) {
|
||||||
reqOptions.ca = certAuthorities;
|
reqOptions.ca = certAuthorities;
|
||||||
}
|
}
|
||||||
|
|
||||||
let ticket = '';
|
let ticket = '';
|
||||||
const req = https.request(reqOptions, res => {
|
const req = https.request(reqOptions, res => {
|
||||||
res.on('data', data => {
|
res.on('data', data => {
|
||||||
ticket += data;
|
ticket += data;
|
||||||
});
|
});
|
||||||
|
|
||||||
res.on('end', () => {
|
res.on('end', () => {
|
||||||
if(ticket.length !== 36) {
|
if(ticket.length !== 36) {
|
||||||
return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`));
|
return callback(Errors.Invalid(`Invalid Exodus ticket: ${ticket}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, ticket);
|
return callback(null, ticket);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
req.on('error', err => {
|
req.on('error', err => {
|
||||||
return callback(Errors.General(`Exodus error: ${err.message}`));
|
return callback(Errors.General(`Exodus error: ${err.message}`));
|
||||||
});
|
});
|
||||||
|
|
||||||
req.write(postData);
|
req.write(postData);
|
||||||
req.end();
|
req.end();
|
||||||
},
|
},
|
||||||
function loadPrivateKey(ticket, callback) {
|
function loadPrivateKey(ticket, callback) {
|
||||||
fs.readFile(self.config.sshKeyPem, (err, privateKey) => {
|
fs.readFile(self.config.sshKeyPem, (err, privateKey) => {
|
||||||
return callback(err, ticket, privateKey);
|
return callback(err, ticket, privateKey);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function establishSecureConnection(ticket, privateKey, callback) {
|
function establishSecureConnection(ticket, privateKey, callback) {
|
||||||
|
|
||||||
let pipeRestored = false;
|
let pipeRestored = false;
|
||||||
let pipedStream;
|
let pipedStream;
|
||||||
|
|
||||||
function restorePipe() {
|
function restorePipe() {
|
||||||
if(pipedStream && !pipeRestored && !clientTerminated) {
|
if(pipedStream && !pipeRestored && !clientTerminated) {
|
||||||
self.client.term.output.unpipe(pipedStream);
|
self.client.term.output.unpipe(pipedStream);
|
||||||
self.client.term.output.resume();
|
self.client.term.output.resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.term.write(resetScreen());
|
self.client.term.write(resetScreen());
|
||||||
self.client.term.write('Connecting to Exodus server, please wait...\n');
|
self.client.term.write('Connecting to Exodus server, please wait...\n');
|
||||||
|
|
||||||
const sshClient = new SSHClient();
|
const sshClient = new SSHClient();
|
||||||
|
|
||||||
const window = {
|
const window = {
|
||||||
rows : self.client.term.termHeight,
|
rows : self.client.term.termHeight,
|
||||||
cols : self.client.term.termWidth,
|
cols : self.client.term.termWidth,
|
||||||
width : 0,
|
width : 0,
|
||||||
height : 0,
|
height : 0,
|
||||||
term : 'vt100', // Want to pass |self.client.term.termClient| here, but we end up getting hung up on :(
|
term : 'vt100', // Want to pass |self.client.term.termClient| here, but we end up getting hung up on :(
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
env : {
|
env : {
|
||||||
exodus : ticket,
|
exodus : ticket,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
sshClient.on('ready', () => {
|
sshClient.on('ready', () => {
|
||||||
self.client.once('end', () => {
|
self.client.once('end', () => {
|
||||||
self.client.log.info('Connection ended. Terminating Exodus connection');
|
self.client.log.info('Connection ended. Terminating Exodus connection');
|
||||||
clientTerminated = true;
|
clientTerminated = true;
|
||||||
return sshClient.end();
|
return sshClient.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
sshClient.shell(window, options, (err, stream) => {
|
sshClient.shell(window, options, (err, stream) => {
|
||||||
pipedStream = stream; // :TODO: ewwwwwwwww hack
|
pipedStream = stream; // :TODO: ewwwwwwwww hack
|
||||||
self.client.term.output.pipe(stream);
|
self.client.term.output.pipe(stream);
|
||||||
|
|
||||||
stream.on('data', d => {
|
stream.on('data', d => {
|
||||||
return self.client.term.rawWrite(d);
|
return self.client.term.rawWrite(d);
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on('close', () => {
|
stream.on('close', () => {
|
||||||
restorePipe();
|
restorePipe();
|
||||||
return sshClient.end();
|
return sshClient.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
stream.on('error', err => {
|
stream.on('error', err => {
|
||||||
Log.warn( { error : err.message }, 'Exodus SSH client stream error');
|
Log.warn( { error : err.message }, 'Exodus SSH client stream error');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
sshClient.on('close', () => {
|
sshClient.on('close', () => {
|
||||||
restorePipe();
|
restorePipe();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
sshClient.connect({
|
sshClient.connect({
|
||||||
host : self.config.sshHost,
|
host : self.config.sshHost,
|
||||||
port : self.config.sshPort,
|
port : self.config.sshPort,
|
||||||
username : self.config.sshUser,
|
username : self.config.sshUser,
|
||||||
privateKey : privateKey,
|
privateKey : privateKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.message }, 'Exodus error');
|
self.client.log.warn( { error : err.message }, 'Exodus error');
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!clientTerminated) {
|
if(!clientTerminated) {
|
||||||
self.prevMenu();
|
self.prevMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,328 +12,328 @@ const stringFormat = require('./string_format.js');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File Area Filter Editor',
|
name : 'File Area Filter Editor',
|
||||||
desc : 'Module for adding, deleting, and modifying file base filters',
|
desc : 'Module for adding, deleting, and modifying file base filters',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
editor : {
|
editor : {
|
||||||
searchTerms : 1,
|
searchTerms : 1,
|
||||||
tags : 2,
|
tags : 2,
|
||||||
area : 3,
|
area : 3,
|
||||||
sort : 4,
|
sort : 4,
|
||||||
order : 5,
|
order : 5,
|
||||||
filterName : 6,
|
filterName : 6,
|
||||||
navMenu : 7,
|
navMenu : 7,
|
||||||
|
|
||||||
// :TODO: use the customs new standard thing - filter obj can have active/selected, etc.
|
// :TODO: use the customs new standard thing - filter obj can have active/selected, etc.
|
||||||
selectedFilterInfo : 10, // { ...filter object ... }
|
selectedFilterInfo : 10, // { ...filter object ... }
|
||||||
activeFilterInfo : 11, // { ...filter object ... }
|
activeFilterInfo : 11, // { ...filter object ... }
|
||||||
error : 12, // validation errors
|
error : 12, // validation errors
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
exports.getModule = class FileAreaFilterEdit extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.filtersArray = new FileBaseFilters(this.client).toArray(); // ordered, such that we can index into them
|
this.filtersArray = new FileBaseFilters(this.client).toArray(); // ordered, such that we can index into them
|
||||||
this.currentFilterIndex = 0; // into |filtersArray|
|
this.currentFilterIndex = 0; // into |filtersArray|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Lexical sort + keep currently active filter (if any) as the first item in |filtersArray|
|
// Lexical sort + keep currently active filter (if any) as the first item in |filtersArray|
|
||||||
//
|
//
|
||||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||||
this.filtersArray.sort( (filterA, filterB) => {
|
this.filtersArray.sort( (filterA, filterB) => {
|
||||||
if(activeFilter) {
|
if(activeFilter) {
|
||||||
if(filterA.uuid === activeFilter.uuid) {
|
if(filterA.uuid === activeFilter.uuid) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if(filterB.uuid === activeFilter.uuid) {
|
if(filterB.uuid === activeFilter.uuid) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterA.name.localeCompare(filterB.name, { sensitivity : false, numeric : true } );
|
return filterA.name.localeCompare(filterB.name, { sensitivity : false, numeric : true } );
|
||||||
});
|
});
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
saveFilter : (formData, extraArgs, cb) => {
|
saveFilter : (formData, extraArgs, cb) => {
|
||||||
return this.saveCurrentFilter(formData, cb);
|
return this.saveCurrentFilter(formData, cb);
|
||||||
},
|
},
|
||||||
prevFilter : (formData, extraArgs, cb) => {
|
prevFilter : (formData, extraArgs, cb) => {
|
||||||
this.currentFilterIndex -= 1;
|
this.currentFilterIndex -= 1;
|
||||||
if(this.currentFilterIndex < 0) {
|
if(this.currentFilterIndex < 0) {
|
||||||
this.currentFilterIndex = this.filtersArray.length - 1;
|
this.currentFilterIndex = this.filtersArray.length - 1;
|
||||||
}
|
}
|
||||||
this.loadDataForFilter(this.currentFilterIndex);
|
this.loadDataForFilter(this.currentFilterIndex);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
nextFilter : (formData, extraArgs, cb) => {
|
nextFilter : (formData, extraArgs, cb) => {
|
||||||
this.currentFilterIndex += 1;
|
this.currentFilterIndex += 1;
|
||||||
if(this.currentFilterIndex >= this.filtersArray.length) {
|
if(this.currentFilterIndex >= this.filtersArray.length) {
|
||||||
this.currentFilterIndex = 0;
|
this.currentFilterIndex = 0;
|
||||||
}
|
}
|
||||||
this.loadDataForFilter(this.currentFilterIndex);
|
this.loadDataForFilter(this.currentFilterIndex);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
makeFilterActive : (formData, extraArgs, cb) => {
|
makeFilterActive : (formData, extraArgs, cb) => {
|
||||||
const filters = new FileBaseFilters(this.client);
|
const filters = new FileBaseFilters(this.client);
|
||||||
filters.setActive(this.filtersArray[this.currentFilterIndex].uuid);
|
filters.setActive(this.filtersArray[this.currentFilterIndex].uuid);
|
||||||
|
|
||||||
this.updateActiveLabel();
|
this.updateActiveLabel();
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
newFilter : (formData, extraArgs, cb) => {
|
newFilter : (formData, extraArgs, cb) => {
|
||||||
this.currentFilterIndex = this.filtersArray.length; // next avail slot
|
this.currentFilterIndex = this.filtersArray.length; // next avail slot
|
||||||
this.clearForm(MciViewIds.editor.searchTerms);
|
this.clearForm(MciViewIds.editor.searchTerms);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
deleteFilter : (formData, extraArgs, cb) => {
|
deleteFilter : (formData, extraArgs, cb) => {
|
||||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||||
const filterUuid = selectedFilter.uuid;
|
const filterUuid = selectedFilter.uuid;
|
||||||
|
|
||||||
// cannot delete built-in/system filters
|
// cannot delete built-in/system filters
|
||||||
if(true === selectedFilter.system) {
|
if(true === selectedFilter.system) {
|
||||||
this.showError('Cannot delete built in filters!');
|
this.showError('Cannot delete built in filters!');
|
||||||
return cb(null);
|
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
|
// remove from stored properties
|
||||||
const filters = new FileBaseFilters(this.client);
|
const filters = new FileBaseFilters(this.client);
|
||||||
filters.remove(filterUuid);
|
filters.remove(filterUuid);
|
||||||
filters.persist( () => {
|
filters.persist( () => {
|
||||||
|
|
||||||
//
|
//
|
||||||
// If the item was also the active filter, we need to make a new one active
|
// 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) {
|
if(filterUuid === this.client.user.properties.file_base_filter_active_uuid) {
|
||||||
const newActive = this.filtersArray[this.currentFilterIndex];
|
const newActive = this.filtersArray[this.currentFilterIndex];
|
||||||
if(newActive) {
|
if(newActive) {
|
||||||
filters.setActive(newActive.uuid);
|
filters.setActive(newActive.uuid);
|
||||||
} else {
|
} else {
|
||||||
// nothing to set active to
|
// nothing to set active to
|
||||||
this.client.user.removeProperty('file_base_filter_active_uuid');
|
this.client.user.removeProperty('file_base_filter_active_uuid');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update UI
|
// update UI
|
||||||
this.updateActiveLabel();
|
this.updateActiveLabel();
|
||||||
|
|
||||||
if(this.filtersArray.length > 0) {
|
if(this.filtersArray.length > 0) {
|
||||||
this.loadDataForFilter(this.currentFilterIndex);
|
this.loadDataForFilter(this.currentFilterIndex);
|
||||||
} else {
|
} else {
|
||||||
this.clearForm();
|
this.clearForm();
|
||||||
}
|
}
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
viewValidationListener : (err, cb) => {
|
viewValidationListener : (err, cb) => {
|
||||||
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
||||||
let newFocusId;
|
let newFocusId;
|
||||||
|
|
||||||
if(errorView) {
|
if(errorView) {
|
||||||
if(err) {
|
if(err) {
|
||||||
errorView.setText(err.message);
|
errorView.setText(err.message);
|
||||||
err.view.clearText(); // clear out the invalid data
|
err.view.clearText(); // clear out the invalid data
|
||||||
} else {
|
} else {
|
||||||
errorView.clearText();
|
errorView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(newFocusId);
|
return cb(newFocusId);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(errMsg) {
|
showError(errMsg) {
|
||||||
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
const errorView = this.viewControllers.editor.getView(MciViewIds.editor.error);
|
||||||
if(errorView) {
|
if(errorView) {
|
||||||
if(errMsg) {
|
if(errMsg) {
|
||||||
errorView.setText(errMsg);
|
errorView.setText(errMsg);
|
||||||
} else {
|
} else {
|
||||||
errorView.clearText();
|
errorView.clearText();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.addViewController( 'editor', new ViewController( { client : this.client } ) );
|
const vc = self.addViewController( 'editor', new ViewController( { client : this.client } ) );
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||||
},
|
},
|
||||||
function populateAreas(callback) {
|
function populateAreas(callback) {
|
||||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||||
|
|
||||||
const areasView = vc.getView(MciViewIds.editor.area);
|
const areasView = vc.getView(MciViewIds.editor.area);
|
||||||
if(areasView) {
|
if(areasView) {
|
||||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
self.updateActiveLabel();
|
self.updateActiveLabel();
|
||||||
self.loadDataForFilter(self.currentFilterIndex);
|
self.loadDataForFilter(self.currentFilterIndex);
|
||||||
self.viewControllers.editor.resetInitialFocus();
|
self.viewControllers.editor.resetInitialFocus();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentFilter() {
|
getCurrentFilter() {
|
||||||
return this.filtersArray[this.currentFilterIndex];
|
return this.filtersArray[this.currentFilterIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
setText(mciId, text) {
|
setText(mciId, text) {
|
||||||
const view = this.viewControllers.editor.getView(mciId);
|
const view = this.viewControllers.editor.getView(mciId);
|
||||||
if(view) {
|
if(view) {
|
||||||
view.setText(text);
|
view.setText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateActiveLabel() {
|
updateActiveLabel() {
|
||||||
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
const activeFilter = FileBaseFilters.getActiveFilter(this.client);
|
||||||
if(activeFilter) {
|
if(activeFilter) {
|
||||||
const activeFormat = this.menuConfig.config.activeFormat || '{name}';
|
const activeFormat = this.menuConfig.config.activeFormat || '{name}';
|
||||||
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
|
this.setText(MciViewIds.editor.activeFilterInfo, stringFormat(activeFormat, activeFilter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocusItemIndex(mciId, index) {
|
setFocusItemIndex(mciId, index) {
|
||||||
const view = this.viewControllers.editor.getView(mciId);
|
const view = this.viewControllers.editor.getView(mciId);
|
||||||
if(view) {
|
if(view) {
|
||||||
view.setFocusItemIndex(index);
|
view.setFocusItemIndex(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clearForm(newFocusId) {
|
clearForm(newFocusId) {
|
||||||
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => {
|
[ MciViewIds.editor.searchTerms, MciViewIds.editor.tags, MciViewIds.editor.filterName ].forEach(mciId => {
|
||||||
this.setText(mciId, '');
|
this.setText(mciId, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
[ MciViewIds.editor.area, MciViewIds.editor.order, MciViewIds.editor.sort ].forEach(mciId => {
|
[ MciViewIds.editor.area, MciViewIds.editor.order, MciViewIds.editor.sort ].forEach(mciId => {
|
||||||
this.setFocusItemIndex(mciId, 0);
|
this.setFocusItemIndex(mciId, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(newFocusId) {
|
if(newFocusId) {
|
||||||
this.viewControllers.editor.switchFocus(newFocusId);
|
this.viewControllers.editor.switchFocus(newFocusId);
|
||||||
} else {
|
} else {
|
||||||
this.viewControllers.editor.resetInitialFocus();
|
this.viewControllers.editor.resetInitialFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedAreaTag(index) {
|
getSelectedAreaTag(index) {
|
||||||
if(0 === index) {
|
if(0 === index) {
|
||||||
return ''; // -ALL-
|
return ''; // -ALL-
|
||||||
}
|
}
|
||||||
const area = this.availAreas[index];
|
const area = this.availAreas[index];
|
||||||
if(!area) {
|
if(!area) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return area.areaTag;
|
return area.areaTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderBy(index) {
|
getOrderBy(index) {
|
||||||
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
setAreaIndexFromCurrentFilter() {
|
setAreaIndexFromCurrentFilter() {
|
||||||
let index;
|
let index;
|
||||||
const filter = this.getCurrentFilter();
|
const filter = this.getCurrentFilter();
|
||||||
if(filter) {
|
if(filter) {
|
||||||
// special treatment: areaTag saved as blank ("") if -ALL-
|
// special treatment: areaTag saved as blank ("") if -ALL-
|
||||||
index = (filter.areaTag && this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) || 0;
|
index = (filter.areaTag && this.availAreas.findIndex(area => filter.areaTag === area.areaTag)) || 0;
|
||||||
} else {
|
} else {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
this.setFocusItemIndex(MciViewIds.editor.area, index);
|
this.setFocusItemIndex(MciViewIds.editor.area, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
setOrderByFromCurrentFilter() {
|
setOrderByFromCurrentFilter() {
|
||||||
let index;
|
let index;
|
||||||
const filter = this.getCurrentFilter();
|
const filter = this.getCurrentFilter();
|
||||||
if(filter) {
|
if(filter) {
|
||||||
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0;
|
index = FileBaseFilters.OrderByValues.findIndex( ob => filter.order === ob ) || 0;
|
||||||
} else {
|
} else {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
this.setFocusItemIndex(MciViewIds.editor.order, index);
|
this.setFocusItemIndex(MciViewIds.editor.order, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortByFromCurrentFilter() {
|
setSortByFromCurrentFilter() {
|
||||||
let index;
|
let index;
|
||||||
const filter = this.getCurrentFilter();
|
const filter = this.getCurrentFilter();
|
||||||
if(filter) {
|
if(filter) {
|
||||||
index = FileBaseFilters.SortByValues.findIndex( sb => filter.sort === sb ) || 0;
|
index = FileBaseFilters.SortByValues.findIndex( sb => filter.sort === sb ) || 0;
|
||||||
} else {
|
} else {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
this.setFocusItemIndex(MciViewIds.editor.sort, index);
|
this.setFocusItemIndex(MciViewIds.editor.sort, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSortBy(index) {
|
getSortBy(index) {
|
||||||
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
setFilterValuesFromFormData(filter, formData) {
|
setFilterValuesFromFormData(filter, formData) {
|
||||||
filter.name = formData.value.name;
|
filter.name = formData.value.name;
|
||||||
filter.areaTag = this.getSelectedAreaTag(formData.value.areaIndex);
|
filter.areaTag = this.getSelectedAreaTag(formData.value.areaIndex);
|
||||||
filter.terms = formData.value.searchTerms;
|
filter.terms = formData.value.searchTerms;
|
||||||
filter.tags = formData.value.tags;
|
filter.tags = formData.value.tags;
|
||||||
filter.order = this.getOrderBy(formData.value.orderByIndex);
|
filter.order = this.getOrderBy(formData.value.orderByIndex);
|
||||||
filter.sort = this.getSortBy(formData.value.sortByIndex);
|
filter.sort = this.getSortBy(formData.value.sortByIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveCurrentFilter(formData, cb) {
|
saveCurrentFilter(formData, cb) {
|
||||||
const filters = new FileBaseFilters(this.client);
|
const filters = new FileBaseFilters(this.client);
|
||||||
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
const selectedFilter = this.filtersArray[this.currentFilterIndex];
|
||||||
|
|
||||||
if(selectedFilter) {
|
if(selectedFilter) {
|
||||||
// *update* currently selected filter
|
// *update* currently selected filter
|
||||||
this.setFilterValuesFromFormData(selectedFilter, formData);
|
this.setFilterValuesFromFormData(selectedFilter, formData);
|
||||||
filters.replace(selectedFilter.uuid, selectedFilter);
|
filters.replace(selectedFilter.uuid, selectedFilter);
|
||||||
} else {
|
} else {
|
||||||
// add a new entry; note that UUID will be generated
|
// add a new entry; note that UUID will be generated
|
||||||
const newFilter = {};
|
const newFilter = {};
|
||||||
this.setFilterValuesFromFormData(newFilter, formData);
|
this.setFilterValuesFromFormData(newFilter, formData);
|
||||||
|
|
||||||
// set current to what we just saved
|
// set current to what we just saved
|
||||||
newFilter.uuid = filters.add(newFilter);
|
newFilter.uuid = filters.add(newFilter);
|
||||||
|
|
||||||
// add to our array (at current index position)
|
// add to our array (at current index position)
|
||||||
this.filtersArray[this.currentFilterIndex] = newFilter;
|
this.filtersArray[this.currentFilterIndex] = newFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
return filters.persist(cb);
|
return filters.persist(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadDataForFilter(filterIndex) {
|
loadDataForFilter(filterIndex) {
|
||||||
const filter = this.filtersArray[filterIndex];
|
const filter = this.filtersArray[filterIndex];
|
||||||
if(filter) {
|
if(filter) {
|
||||||
this.setText(MciViewIds.editor.searchTerms, filter.terms);
|
this.setText(MciViewIds.editor.searchTerms, filter.terms);
|
||||||
this.setText(MciViewIds.editor.tags, filter.tags);
|
this.setText(MciViewIds.editor.tags, filter.tags);
|
||||||
this.setText(MciViewIds.editor.filterName, filter.name);
|
this.setText(MciViewIds.editor.filterName, filter.name);
|
||||||
|
|
||||||
this.setAreaIndexFromCurrentFilter();
|
this.setAreaIndexFromCurrentFilter();
|
||||||
this.setSortByFromCurrentFilter();
|
this.setSortByFromCurrentFilter();
|
||||||
this.setOrderByFromCurrentFilter();
|
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');
|
const yazl = require('yazl');
|
||||||
|
|
||||||
function notEnabledError() {
|
function notEnabledError() {
|
||||||
return Errors.General('Web server is not enabled', ErrNotEnabled);
|
return Errors.General('Web server is not enabled', ErrNotEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileAreaWebAccess {
|
class FileAreaWebAccess {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.hashids = new hashids(Config().general.boardName);
|
this.hashids = new hashids(Config().general.boardName);
|
||||||
this.expireTimers = {}; // hashId->timer
|
this.expireTimers = {}; // hashId->timer
|
||||||
}
|
}
|
||||||
|
|
||||||
startup(cb) {
|
startup(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function initFromDb(callback) {
|
function initFromDb(callback) {
|
||||||
return self.load(callback);
|
return self.load(callback);
|
||||||
},
|
},
|
||||||
function addWebRoute(callback) {
|
function addWebRoute(callback) {
|
||||||
self.webServer = getServer(webServerPackageName);
|
self.webServer = getServer(webServerPackageName);
|
||||||
if(!self.webServer) {
|
if(!self.webServer) {
|
||||||
return callback(Errors.DoesNotExist(`Server with package name "${webServerPackageName}" does not exist`));
|
return callback(Errors.DoesNotExist(`Server with package name "${webServerPackageName}" does not exist`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(self.isEnabled()) {
|
if(self.isEnabled()) {
|
||||||
const routeAdded = self.webServer.instance.addRoute({
|
const routeAdded = self.webServer.instance.addRoute({
|
||||||
method : 'GET',
|
method : 'GET',
|
||||||
path : Config().fileBase.web.routePath,
|
path : Config().fileBase.web.routePath,
|
||||||
handler : self.routeWebRequest.bind(self),
|
handler : self.routeWebRequest.bind(self),
|
||||||
});
|
});
|
||||||
return callback(routeAdded ? null : Errors.General('Failed adding route'));
|
return callback(routeAdded ? null : Errors.General('Failed adding route'));
|
||||||
} else {
|
} else {
|
||||||
return callback(null); // not enabled, but no error
|
return callback(null); // not enabled, but no error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
shutdown(cb) {
|
shutdown(cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
isEnabled() {
|
isEnabled() {
|
||||||
return this.webServer.instance.isEnabled();
|
return this.webServer.instance.isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getHashIdTypes() {
|
static getHashIdTypes() {
|
||||||
return {
|
return {
|
||||||
SingleFile : 0,
|
SingleFile : 0,
|
||||||
BatchArchive : 1,
|
BatchArchive : 1,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
load(cb) {
|
load(cb) {
|
||||||
//
|
//
|
||||||
// Load entries, register expiration timers
|
// Load entries, register expiration timers
|
||||||
//
|
//
|
||||||
FileDb.each(
|
FileDb.each(
|
||||||
`SELECT hash_id, expire_timestamp
|
`SELECT hash_id, expire_timestamp
|
||||||
FROM file_web_serve;`,
|
FROM file_web_serve;`,
|
||||||
(err, row) => {
|
(err, row) => {
|
||||||
if(row) {
|
if(row) {
|
||||||
this.scheduleExpire(row.hash_id, moment(row.expire_timestamp));
|
this.scheduleExpire(row.hash_id, moment(row.expire_timestamp));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeEntry(hashId) {
|
removeEntry(hashId) {
|
||||||
//
|
//
|
||||||
// Delete record from DB, and our timer
|
// Delete record from DB, and our timer
|
||||||
//
|
//
|
||||||
FileDb.run(
|
FileDb.run(
|
||||||
`DELETE FROM file_web_serve
|
`DELETE FROM file_web_serve
|
||||||
WHERE hash_id = ?;`,
|
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
|
// remove any previous entry for this hashId
|
||||||
const previous = this.expireTimers[hashId];
|
const previous = this.expireTimers[hashId];
|
||||||
if(previous) {
|
if(previous) {
|
||||||
clearTimeout(previous);
|
clearTimeout(previous);
|
||||||
delete this.expireTimers[hashId];
|
delete this.expireTimers[hashId];
|
||||||
}
|
}
|
||||||
|
|
||||||
const timeoutMs = expireTime.diff(moment());
|
const timeoutMs = expireTime.diff(moment());
|
||||||
|
|
||||||
if(timeoutMs <= 0) {
|
if(timeoutMs <= 0) {
|
||||||
setImmediate( () => {
|
setImmediate( () => {
|
||||||
this.removeEntry(hashId);
|
this.removeEntry(hashId);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.expireTimers[hashId] = setTimeout( () => {
|
this.expireTimers[hashId] = setTimeout( () => {
|
||||||
this.removeEntry(hashId);
|
this.removeEntry(hashId);
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadServedHashId(hashId, cb) {
|
loadServedHashId(hashId, cb) {
|
||||||
FileDb.get(
|
FileDb.get(
|
||||||
`SELECT expire_timestamp FROM
|
`SELECT expire_timestamp FROM
|
||||||
file_web_serve
|
file_web_serve
|
||||||
WHERE hash_id = ?`,
|
WHERE hash_id = ?`,
|
||||||
[ hashId ],
|
[ hashId ],
|
||||||
(err, result) => {
|
(err, result) => {
|
||||||
if(err || !result) {
|
if(err || !result) {
|
||||||
return cb(err ? err : Errors.DoesNotExist('Invalid or missing hash ID'));
|
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, ... ]
|
// decode() should provide an array of [ userId, hashIdType, id, ... ]
|
||||||
if(!Array.isArray(decoded) || decoded.length < 3) {
|
if(!Array.isArray(decoded) || decoded.length < 3) {
|
||||||
return cb(Errors.Invalid('Invalid or unknown hash ID'));
|
return cb(Errors.Invalid('Invalid or unknown hash ID'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const servedItem = {
|
const servedItem = {
|
||||||
hashId : hashId,
|
hashId : hashId,
|
||||||
userId : decoded[0],
|
userId : decoded[0],
|
||||||
hashIdType : decoded[1],
|
hashIdType : decoded[1],
|
||||||
expireTimestamp : moment(result.expire_timestamp),
|
expireTimestamp : moment(result.expire_timestamp),
|
||||||
};
|
};
|
||||||
|
|
||||||
if(FileAreaWebAccess.getHashIdTypes().SingleFile === servedItem.hashIdType) {
|
if(FileAreaWebAccess.getHashIdTypes().SingleFile === servedItem.hashIdType) {
|
||||||
servedItem.fileIds = decoded.slice(2);
|
servedItem.fileIds = decoded.slice(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null, servedItem);
|
return cb(null, servedItem);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSingleFileHashId(client, fileEntry) {
|
getSingleFileHashId(client, fileEntry) {
|
||||||
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [ fileEntry.fileId ] );
|
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().SingleFile, [ fileEntry.fileId ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
getBatchArchiveHashId(client, batchId) {
|
getBatchArchiveHashId(client, batchId) {
|
||||||
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().BatchArchive, batchId);
|
return this.getHashId(client, FileAreaWebAccess.getHashIdTypes().BatchArchive, batchId);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHashId(client, hashIdType, identifier) {
|
getHashId(client, hashIdType, identifier) {
|
||||||
return this.hashids.encode(client.user.userId, hashIdType, identifier);
|
return this.hashids.encode(client.user.userId, hashIdType, identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildSingleFileTempDownloadLink(client, fileEntry, hashId) {
|
buildSingleFileTempDownloadLink(client, fileEntry, hashId) {
|
||||||
hashId = hashId || this.getSingleFileHashId(client, fileEntry);
|
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) {
|
buildBatchArchiveTempDownloadLink(client, hashId) {
|
||||||
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
return this.webServer.instance.buildUrl(`${Config().fileBase.web.path}${hashId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getExistingTempDownloadServeItem(client, fileEntry, cb) {
|
getExistingTempDownloadServeItem(client, fileEntry, cb) {
|
||||||
if(!this.isEnabled()) {
|
if(!this.isEnabled()) {
|
||||||
return cb(notEnabledError());
|
return cb(notEnabledError());
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||||
this.loadServedHashId(hashId, (err, servedItem) => {
|
this.loadServedHashId(hashId, (err, servedItem) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(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) {
|
_addOrUpdateHashIdRecord(dbOrTrans, hashId, expireTime, cb) {
|
||||||
// add/update rec with hash id and (latest) timestamp
|
// add/update rec with hash id and (latest) timestamp
|
||||||
dbOrTrans.run(
|
dbOrTrans.run(
|
||||||
`REPLACE INTO file_web_serve (hash_id, expire_timestamp)
|
`REPLACE INTO file_web_serve (hash_id, expire_timestamp)
|
||||||
VALUES (?, ?);`,
|
VALUES (?, ?);`,
|
||||||
[ hashId, getISOTimestampString(expireTime) ],
|
[ hashId, getISOTimestampString(expireTime) ],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scheduleExpire(hashId, expireTime);
|
this.scheduleExpire(hashId, expireTime);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndServeTempDownload(client, fileEntry, options, cb) {
|
createAndServeTempDownload(client, fileEntry, options, cb) {
|
||||||
if(!this.isEnabled()) {
|
if(!this.isEnabled()) {
|
||||||
return cb(notEnabledError());
|
return cb(notEnabledError());
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashId = this.getSingleFileHashId(client, fileEntry);
|
const hashId = this.getSingleFileHashId(client, fileEntry);
|
||||||
const url = this.buildSingleFileTempDownloadLink(client, fileEntry, hashId);
|
const url = this.buildSingleFileTempDownloadLink(client, fileEntry, hashId);
|
||||||
options.expireTime = options.expireTime || moment().add(2, 'days');
|
options.expireTime = options.expireTime || moment().add(2, 'days');
|
||||||
|
|
||||||
this._addOrUpdateHashIdRecord(FileDb, hashId, options.expireTime, err => {
|
this._addOrUpdateHashIdRecord(FileDb, hashId, options.expireTime, err => {
|
||||||
return cb(err, url);
|
return cb(err, url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndServeTempBatchDownload(client, fileEntries, options, cb) {
|
createAndServeTempBatchDownload(client, fileEntries, options, cb) {
|
||||||
if(!this.isEnabled()) {
|
if(!this.isEnabled()) {
|
||||||
return cb(notEnabledError());
|
return cb(notEnabledError());
|
||||||
}
|
}
|
||||||
|
|
||||||
const batchId = moment().utc().unix();
|
const batchId = moment().utc().unix();
|
||||||
const hashId = this.getBatchArchiveHashId(client, batchId);
|
const hashId = this.getBatchArchiveHashId(client, batchId);
|
||||||
const url = this.buildBatchArchiveTempDownloadLink(client, hashId);
|
const url = this.buildBatchArchiveTempDownloadLink(client, hashId);
|
||||||
options.expireTime = options.expireTime || moment().add(2, 'days');
|
options.expireTime = options.expireTime || moment().add(2, 'days');
|
||||||
|
|
||||||
FileDb.beginTransaction( (err, trans) => {
|
FileDb.beginTransaction( (err, trans) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._addOrUpdateHashIdRecord(trans, hashId, options.expireTime, err => {
|
this._addOrUpdateHashIdRecord(trans, hashId, options.expireTime, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return trans.rollback( () => {
|
return trans.rollback( () => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async.eachSeries(fileEntries, (entry, nextEntry) => {
|
async.eachSeries(fileEntries, (entry, nextEntry) => {
|
||||||
trans.run(
|
trans.run(
|
||||||
`INSERT INTO file_web_serve_batch (hash_id, file_id)
|
`INSERT INTO file_web_serve_batch (hash_id, file_id)
|
||||||
VALUES (?, ?);`,
|
VALUES (?, ?);`,
|
||||||
[ hashId, entry.fileId ],
|
[ hashId, entry.fileId ],
|
||||||
err => {
|
err => {
|
||||||
return nextEntry(err);
|
return nextEntry(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}, err => {
|
}, err => {
|
||||||
trans[err ? 'rollback' : 'commit']( () => {
|
trans[err ? 'rollback' : 'commit']( () => {
|
||||||
return cb(err, url);
|
return cb(err, url);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fileNotFound(resp) {
|
fileNotFound(resp) {
|
||||||
return this.webServer.instance.fileNotFound(resp);
|
return this.webServer.instance.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
routeWebRequest(req, resp) {
|
routeWebRequest(req, resp) {
|
||||||
const hashId = paths.basename(req.url);
|
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) {
|
if(err) {
|
||||||
return this.fileNotFound(resp);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashIdTypes = FileAreaWebAccess.getHashIdTypes();
|
const hashIdTypes = FileAreaWebAccess.getHashIdTypes();
|
||||||
switch(servedItem.hashIdType) {
|
switch(servedItem.hashIdType) {
|
||||||
case hashIdTypes.SingleFile :
|
case hashIdTypes.SingleFile :
|
||||||
return this.routeWebRequestForSingleFile(servedItem, req, resp);
|
return this.routeWebRequestForSingleFile(servedItem, req, resp);
|
||||||
|
|
||||||
case hashIdTypes.BatchArchive :
|
case hashIdTypes.BatchArchive :
|
||||||
return this.routeWebRequestForBatchArchive(servedItem, req, resp);
|
return this.routeWebRequestForBatchArchive(servedItem, req, resp);
|
||||||
|
|
||||||
default :
|
default :
|
||||||
return this.fileNotFound(resp);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
routeWebRequestForSingleFile(servedItem, req, resp) {
|
routeWebRequestForSingleFile(servedItem, req, resp) {
|
||||||
Log.debug( { servedItem : servedItem }, 'Single file web request');
|
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 => {
|
fileEntry.load(servedItem.fileId, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return this.fileNotFound(resp);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = fileEntry.filePath;
|
const filePath = fileEntry.filePath;
|
||||||
if(!filePath) {
|
if(!filePath) {
|
||||||
return this.fileNotFound(resp);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.stat(filePath, (err, stats) => {
|
fs.stat(filePath, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return this.fileNotFound(resp);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.on('close', () => {
|
resp.on('close', () => {
|
||||||
// connection closed *before* the response was fully sent
|
// connection closed *before* the response was fully sent
|
||||||
// :TODO: Log and such
|
// :TODO: Log and such
|
||||||
});
|
});
|
||||||
|
|
||||||
resp.on('finish', () => {
|
resp.on('finish', () => {
|
||||||
// transfer completed fully
|
// transfer completed fully
|
||||||
this.updateDownloadStatsForUserIdAndSystem(servedItem.userId, stats.size, [ fileEntry ]);
|
this.updateDownloadStatsForUserIdAndSystem(servedItem.userId, stats.size, [ fileEntry ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'),
|
'Content-Type' : mimeTypes.contentType(filePath) || mimeTypes.contentType('.bin'),
|
||||||
'Content-Length' : stats.size,
|
'Content-Length' : stats.size,
|
||||||
'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`,
|
'Content-Disposition' : `attachment; filename="${fileEntry.fileName}"`,
|
||||||
};
|
};
|
||||||
|
|
||||||
const readStream = fs.createReadStream(filePath);
|
const readStream = fs.createReadStream(filePath);
|
||||||
resp.writeHead(200, headers);
|
resp.writeHead(200, headers);
|
||||||
return readStream.pipe(resp);
|
return readStream.pipe(resp);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
routeWebRequestForBatchArchive(servedItem, req, resp) {
|
routeWebRequestForBatchArchive(servedItem, req, resp) {
|
||||||
Log.debug( { servedItem : servedItem }, 'Batch file web request');
|
Log.debug( { servedItem : servedItem }, 'Batch file web request');
|
||||||
|
|
||||||
//
|
//
|
||||||
// We are going to build an on-the-fly zip file stream of 1:n
|
// We are going to build an on-the-fly zip file stream of 1:n
|
||||||
// files in the batch.
|
// files in the batch.
|
||||||
//
|
//
|
||||||
// First, collect all file IDs
|
// First, collect all file IDs
|
||||||
//
|
//
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function fetchFileIds(callback) {
|
function fetchFileIds(callback) {
|
||||||
FileDb.all(
|
FileDb.all(
|
||||||
`SELECT file_id
|
`SELECT file_id
|
||||||
FROM file_web_serve_batch
|
FROM file_web_serve_batch
|
||||||
WHERE hash_id = ?;`,
|
WHERE hash_id = ?;`,
|
||||||
[ servedItem.hashId ],
|
[ servedItem.hashId ],
|
||||||
(err, fileIdRows) => {
|
(err, fileIdRows) => {
|
||||||
if(err || !Array.isArray(fileIdRows) || 0 === fileIdRows.length) {
|
if(err || !Array.isArray(fileIdRows) || 0 === fileIdRows.length) {
|
||||||
return callback(Errors.DoesNotExist('Could not get file IDs for batch'));
|
return callback(Errors.DoesNotExist('Could not get file IDs for batch'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, fileIdRows.map(r => r.file_id));
|
return callback(null, fileIdRows.map(r => r.file_id));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function loadFileEntries(fileIds, callback) {
|
function loadFileEntries(fileIds, callback) {
|
||||||
async.map(fileIds, (fileId, nextFileId) => {
|
async.map(fileIds, (fileId, nextFileId) => {
|
||||||
const fileEntry = new FileEntry();
|
const fileEntry = new FileEntry();
|
||||||
fileEntry.load(fileId, err => {
|
fileEntry.load(fileId, err => {
|
||||||
return nextFileId(err, fileEntry);
|
return nextFileId(err, fileEntry);
|
||||||
});
|
});
|
||||||
}, (err, fileEntries) => {
|
}, (err, fileEntries) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(Errors.DoesNotExist('Could not load file IDs for batch'));
|
return callback(Errors.DoesNotExist('Could not load file IDs for batch'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, fileEntries);
|
return callback(null, fileEntries);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function createAndServeStream(fileEntries, callback) {
|
function createAndServeStream(fileEntries, callback) {
|
||||||
const filePaths = fileEntries.map(fe => fe.filePath);
|
const filePaths = fileEntries.map(fe => fe.filePath);
|
||||||
Log.trace( { filePaths : filePaths }, 'Creating zip archive for batch web request');
|
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 => {
|
zipFile.on('error', err => {
|
||||||
Log.warn( { error : err.message }, 'Error adding file to batch web request archive');
|
Log.warn( { error : err.message }, 'Error adding file to batch web request archive');
|
||||||
});
|
});
|
||||||
|
|
||||||
filePaths.forEach(fp => {
|
filePaths.forEach(fp => {
|
||||||
zipFile.addFile(
|
zipFile.addFile(
|
||||||
fp, // path to physical file
|
fp, // path to physical file
|
||||||
paths.basename(fp), // filename/path *stored in archive*
|
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.
|
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 => {
|
zipFile.end( finalZipSize => {
|
||||||
if(-1 === finalZipSize) {
|
if(-1 === finalZipSize) {
|
||||||
return callback(Errors.UnexpectedState('Unable to acquire final zip size'));
|
return callback(Errors.UnexpectedState('Unable to acquire final zip size'));
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.on('close', () => {
|
resp.on('close', () => {
|
||||||
// connection closed *before* the response was fully sent
|
// connection closed *before* the response was fully sent
|
||||||
// :TODO: Log and such
|
// :TODO: Log and such
|
||||||
});
|
});
|
||||||
|
|
||||||
resp.on('finish', () => {
|
resp.on('finish', () => {
|
||||||
// transfer completed fully
|
// transfer completed fully
|
||||||
self.updateDownloadStatsForUserIdAndSystem(servedItem.userId, finalZipSize, fileEntries);
|
self.updateDownloadStatsForUserIdAndSystem(servedItem.userId, finalZipSize, fileEntries);
|
||||||
});
|
});
|
||||||
|
|
||||||
const batchFileName = `batch_${servedItem.hashId}.zip`;
|
const batchFileName = `batch_${servedItem.hashId}.zip`;
|
||||||
|
|
||||||
const headers = {
|
const headers = {
|
||||||
'Content-Type' : mimeTypes.contentType(batchFileName) || mimeTypes.contentType('.bin'),
|
'Content-Type' : mimeTypes.contentType(batchFileName) || mimeTypes.contentType('.bin'),
|
||||||
'Content-Length' : finalZipSize,
|
'Content-Length' : finalZipSize,
|
||||||
'Content-Disposition' : `attachment; filename="${batchFileName}"`,
|
'Content-Disposition' : `attachment; filename="${batchFileName}"`,
|
||||||
};
|
};
|
||||||
|
|
||||||
resp.writeHead(200, headers);
|
resp.writeHead(200, headers);
|
||||||
return zipFile.outputStream.pipe(resp);
|
return zipFile.outputStream.pipe(resp);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO: Log me!
|
// :TODO: Log me!
|
||||||
return this.fileNotFound(resp);
|
return this.fileNotFound(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...otherwise, we would have called resp() already.
|
// ...otherwise, we would have called resp() already.
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) {
|
updateDownloadStatsForUserIdAndSystem(userId, dlBytes, fileEntries) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function fetchActiveUser(callback) {
|
function fetchActiveUser(callback) {
|
||||||
const clientForUserId = getConnectionByUserId(userId);
|
const clientForUserId = getConnectionByUserId(userId);
|
||||||
if(clientForUserId) {
|
if(clientForUserId) {
|
||||||
return callback(null, clientForUserId.user);
|
return callback(null, clientForUserId.user);
|
||||||
}
|
}
|
||||||
|
|
||||||
// not online now - look 'em up
|
// not online now - look 'em up
|
||||||
User.getUser(userId, (err, assocUser) => {
|
User.getUser(userId, (err, assocUser) => {
|
||||||
return callback(err, assocUser);
|
return callback(err, assocUser);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function updateStats(user, callback) {
|
function updateStats(user, callback) {
|
||||||
StatLog.incrementUserStat(user, 'dl_total_count', 1);
|
StatLog.incrementUserStat(user, 'dl_total_count', 1);
|
||||||
StatLog.incrementUserStat(user, 'dl_total_bytes', dlBytes);
|
StatLog.incrementUserStat(user, 'dl_total_bytes', dlBytes);
|
||||||
StatLog.incrementSystemStat('dl_total_count', 1);
|
StatLog.incrementSystemStat('dl_total_count', 1);
|
||||||
StatLog.incrementSystemStat('dl_total_bytes', dlBytes);
|
StatLog.incrementSystemStat('dl_total_bytes', dlBytes);
|
||||||
|
|
||||||
return callback(null, user);
|
return callback(null, user);
|
||||||
},
|
},
|
||||||
function sendEvent(user, callback) {
|
function sendEvent(user, callback) {
|
||||||
Events.emit(
|
Events.emit(
|
||||||
Events.getSystemEvents().UserDownload,
|
Events.getSystemEvents().UserDownload,
|
||||||
{
|
{
|
||||||
user : user,
|
user : user,
|
||||||
files : fileEntries,
|
files : fileEntries,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new FileAreaWebAccess();
|
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');
|
const async = require('async');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File Area Selector',
|
name : 'File Area Selector',
|
||||||
desc : 'Select from available file areas',
|
desc : 'Select from available file areas',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
areaList : 1,
|
areaList : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileAreaSelectModule extends MenuModule {
|
exports.getModule = class FileAreaSelectModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
selectArea : (formData, extraArgs, cb) => {
|
selectArea : (formData, extraArgs, cb) => {
|
||||||
const filterCriteria = {
|
const filterCriteria = {
|
||||||
areaTag : formData.value.areaTag,
|
areaTag : formData.value.areaTag,
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuOpts = {
|
const menuOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
filterCriteria : filterCriteria,
|
filterCriteria : filterCriteria,
|
||||||
},
|
},
|
||||||
menuFlags : [ 'popParent', 'mergeFlags' ],
|
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) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function mergeAreaStats(callback) {
|
function mergeAreaStats(callback) {
|
||||||
const areaStats = StatLog.getSystemStat('file_base_area_stats') || { areas : {} };
|
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
|
// we could use 'sort' alone, but area/conf sorting has some special properties; user can still override
|
||||||
const availAreas = getSortedAvailableFileAreas(self.client);
|
const availAreas = getSortedAvailableFileAreas(self.client);
|
||||||
availAreas.forEach(area => {
|
availAreas.forEach(area => {
|
||||||
const stats = areaStats.areas[area.areaTag];
|
const stats = areaStats.areas[area.areaTag];
|
||||||
area.totalFiles = stats ? stats.files : 0;
|
area.totalFiles = stats ? stats.files : 0;
|
||||||
area.totalBytes = stats ? stats.bytes : 0;
|
area.totalBytes = stats ? stats.bytes : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
return callback(null, availAreas);
|
return callback(null, availAreas);
|
||||||
},
|
},
|
||||||
function prepView(availAreas, callback) {
|
function prepView(availAreas, callback) {
|
||||||
self.prepViewController('allViews', 0, mciData.menu, (err, vc) => {
|
self.prepViewController('allViews', 0, mciData.menu, (err, vc) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const areaListView = vc.getView(MciViewIds.areaList);
|
const areaListView = vc.getView(MciViewIds.areaList);
|
||||||
areaListView.setItems(availAreas.map(area => Object.assign(area, { text : area.name, data : area.areaTag } )));
|
areaListView.setItems(availAreas.map(area => Object.assign(area, { text : area.name, data : area.areaTag } )));
|
||||||
areaListView.redraw();
|
areaListView.redraw();
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,226 +17,226 @@ const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File Base Download Queue Manager',
|
name : 'File Base Download Queue Manager',
|
||||||
desc : 'Module for interacting with download queue/batch',
|
desc : 'Module for interacting with download queue/batch',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormIds = {
|
const FormIds = {
|
||||||
queueManager : 0,
|
queueManager : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
queueManager : {
|
queueManager : {
|
||||||
queue : 1,
|
queue : 1,
|
||||||
navMenu : 2,
|
navMenu : 2,
|
||||||
|
|
||||||
customRangeStart : 10,
|
customRangeStart : 10,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
exports.getModule = class FileBaseDownloadQueueManager extends MenuModule {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.dlQueue = new DownloadQueue(this.client);
|
this.dlQueue = new DownloadQueue(this.client);
|
||||||
|
|
||||||
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||||
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fallbackOnly = options.lastMenuResult ? true : false;
|
this.fallbackOnly = options.lastMenuResult ? true : false;
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
downloadAll : (formData, extraArgs, cb) => {
|
downloadAll : (formData, extraArgs, cb) => {
|
||||||
const modOpts = {
|
const modOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
sendQueue : this.dlQueue.items,
|
sendQueue : this.dlQueue.items,
|
||||||
direction : 'send',
|
direction : 'send',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
|
return this.gotoMenu(this.menuConfig.config.fileTransferProtocolSelection || 'fileTransferProtocolSelection', modOpts, cb);
|
||||||
},
|
},
|
||||||
removeItem : (formData, extraArgs, cb) => {
|
removeItem : (formData, extraArgs, cb) => {
|
||||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||||
if(!selectedItem) {
|
if(!selectedItem) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dlQueue.removeItems(selectedItem.fileId);
|
this.dlQueue.removeItems(selectedItem.fileId);
|
||||||
|
|
||||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||||
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
||||||
},
|
},
|
||||||
clearQueue : (formData, extraArgs, cb) => {
|
clearQueue : (formData, extraArgs, cb) => {
|
||||||
this.dlQueue.clear();
|
this.dlQueue.clear();
|
||||||
|
|
||||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
if(0 === this.dlQueue.items.length) {
|
if(0 === this.dlQueue.items.length) {
|
||||||
if(this.sendFileIds) {
|
if(this.sendFileIds) {
|
||||||
// we've finished everything up - just fall back
|
// we've finished everything up - just fall back
|
||||||
return this.prevMenu();
|
return this.prevMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simply an empty D/L queue: Present a specialized "empty queue" page
|
// Simply an empty D/L queue: Present a specialized "empty queue" page
|
||||||
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function beforeArt(callback) {
|
function beforeArt(callback) {
|
||||||
return self.beforeArt(callback);
|
return self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function display(callback) {
|
function display(callback) {
|
||||||
return self.displayQueueManagerPage(false, callback);
|
return self.displayQueueManagerPage(false, callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
return self.finishedLoading();
|
return self.finishedLoading();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
||||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||||
if(!queueView) {
|
if(!queueView) {
|
||||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if('all' === itemIndex) {
|
if('all' === itemIndex) {
|
||||||
queueView.setItems([]);
|
queueView.setItems([]);
|
||||||
queueView.setFocusItems([]);
|
queueView.setFocusItems([]);
|
||||||
} else {
|
} else {
|
||||||
queueView.removeItem(itemIndex);
|
queueView.removeItem(itemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
queueView.redraw();
|
queueView.redraw();
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayWebDownloadLinkForFileEntry(fileEntry) {
|
displayWebDownloadLinkForFileEntry(fileEntry) {
|
||||||
FileAreaWeb.getExistingTempDownloadServeItem(this.client, fileEntry, (err, serveItem) => {
|
FileAreaWeb.getExistingTempDownloadServeItem(this.client, fileEntry, (err, serveItem) => {
|
||||||
if(serveItem && serveItem.url) {
|
if(serveItem && serveItem.url) {
|
||||||
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
const webDlExpireTimeFormat = this.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||||
|
|
||||||
fileEntry.webDlLink = ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
|
fileEntry.webDlLink = ansi.vtxHyperlink(this.client, serveItem.url) + serveItem.url;
|
||||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||||
} else {
|
} else {
|
||||||
fileEntry.webDlLink = '';
|
fileEntry.webDlLink = '';
|
||||||
fileEntry.webDlExpire = '';
|
fileEntry.webDlExpire = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateCustomViewTextsWithFilter(
|
this.updateCustomViewTextsWithFilter(
|
||||||
'queueManager',
|
'queueManager',
|
||||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||||
{ filter : [ '{webDlLink}', '{webDlExpire}' ] }
|
{ filter : [ '{webDlLink}', '{webDlExpire}' ] }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDownloadQueueView(cb) {
|
updateDownloadQueueView(cb) {
|
||||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||||
if(!queueView) {
|
if(!queueView) {
|
||||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{fileName} {byteSize}';
|
const queueListFormat = this.menuConfig.config.queueListFormat || '{fileName} {byteSize}';
|
||||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||||
|
|
||||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||||
|
|
||||||
queueView.on('index update', idx => {
|
queueView.on('index update', idx => {
|
||||||
const fileEntry = this.dlQueue.items[idx];
|
const fileEntry = this.dlQueue.items[idx];
|
||||||
this.displayWebDownloadLinkForFileEntry(fileEntry);
|
this.displayWebDownloadLinkForFileEntry(fileEntry);
|
||||||
});
|
});
|
||||||
|
|
||||||
queueView.redraw();
|
queueView.redraw();
|
||||||
this.displayWebDownloadLinkForFileEntry(this.dlQueue.items[0]);
|
this.displayWebDownloadLinkForFileEntry(this.dlQueue.items[0]);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayQueueManagerPage(clearScreen, cb) {
|
displayQueueManagerPage(clearScreen, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function prepArtAndViewController(callback) {
|
function prepArtAndViewController(callback) {
|
||||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||||
},
|
},
|
||||||
function populateViews(callback) {
|
function populateViews(callback) {
|
||||||
return self.updateDownloadQueueView(callback);
|
return self.updateDownloadQueueView(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayArtAndPrepViewController(name, options, cb) {
|
displayArtAndPrepViewController(name, options, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const config = this.menuConfig.config;
|
const config = this.menuConfig.config;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function readyAndDisplayArt(callback) {
|
function readyAndDisplayArt(callback) {
|
||||||
if(options.clearScreen) {
|
if(options.clearScreen) {
|
||||||
self.client.term.rawWrite(ansi.resetScreen());
|
self.client.term.rawWrite(ansi.resetScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
config.art[name],
|
config.art[name],
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font, trailingLF : false },
|
{ font : self.menuConfig.font, trailingLF : false },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function prepeareViewController(artData, callback) {
|
function prepeareViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers[name])) {
|
if(_.isUndefined(self.viewControllers[name])) {
|
||||||
const vcOpts = {
|
const vcOpts = {
|
||||||
client : self.client,
|
client : self.client,
|
||||||
formId : FormIds[name],
|
formId : FormIds[name],
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!_.isUndefined(options.noInput)) {
|
if(!_.isUndefined(options.noInput)) {
|
||||||
vcOpts.noInput = options.noInput;
|
vcOpts.noInput = options.noInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds[name],
|
formId : FormIds[name],
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.viewControllers[name].setFocus(true);
|
self.viewControllers[name].setFocus(true);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,150 +6,150 @@ const _ = require('lodash');
|
||||||
const uuidV4 = require('uuid/v4');
|
const uuidV4 = require('uuid/v4');
|
||||||
|
|
||||||
module.exports = class FileBaseFilters {
|
module.exports = class FileBaseFilters {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
||||||
this.load();
|
this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
static get OrderByValues() {
|
static get OrderByValues() {
|
||||||
return [ 'descending', 'ascending' ];
|
return [ 'descending', 'ascending' ];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get SortByValues() {
|
static get SortByValues() {
|
||||||
return [
|
return [
|
||||||
'upload_timestamp',
|
'upload_timestamp',
|
||||||
'upload_by_username',
|
'upload_by_username',
|
||||||
'dl_count',
|
'dl_count',
|
||||||
'user_rating',
|
'user_rating',
|
||||||
'est_release_year',
|
'est_release_year',
|
||||||
'byte_size',
|
'byte_size',
|
||||||
'file_name',
|
'file_name',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
toArray() {
|
toArray() {
|
||||||
return _.map(this.filters, (filter, uuid) => {
|
return _.map(this.filters, (filter, uuid) => {
|
||||||
return Object.assign( { uuid : uuid }, filter );
|
return Object.assign( { uuid : uuid }, filter );
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get(filterUuid) {
|
get(filterUuid) {
|
||||||
return this.filters[filterUuid];
|
return this.filters[filterUuid];
|
||||||
}
|
}
|
||||||
|
|
||||||
add(filterInfo) {
|
add(filterInfo) {
|
||||||
const filterUuid = uuidV4();
|
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) {
|
replace(filterUuid, filterInfo) {
|
||||||
const filter = this.get(filterUuid);
|
const filter = this.get(filterUuid);
|
||||||
if(!filter) {
|
if(!filter) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
filterInfo.tags = this.cleanTags(filterInfo.tags);
|
||||||
this.filters[filterUuid] = filterInfo;
|
this.filters[filterUuid] = filterInfo;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(filterUuid) {
|
remove(filterUuid) {
|
||||||
delete this.filters[filterUuid];
|
delete this.filters[filterUuid];
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
let filtersProperty = this.client.user.properties.file_base_filters;
|
let filtersProperty = this.client.user.properties.file_base_filters;
|
||||||
let defaulted;
|
let defaulted;
|
||||||
if(!filtersProperty) {
|
if(!filtersProperty) {
|
||||||
filtersProperty = JSON.stringify(FileBaseFilters.getBuiltInSystemFilters());
|
filtersProperty = JSON.stringify(FileBaseFilters.getBuiltInSystemFilters());
|
||||||
defaulted = true;
|
defaulted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.filters = JSON.parse(filtersProperty);
|
this.filters = JSON.parse(filtersProperty);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :(
|
this.filters = FileBaseFilters.getBuiltInSystemFilters(); // something bad happened; reset everything back to defaults :(
|
||||||
defaulted = true;
|
defaulted = true;
|
||||||
this.client.log.error( { error : e.message, property : filtersProperty }, 'Failed parsing file base filters property' );
|
this.client.log.error( { error : e.message, property : filtersProperty }, 'Failed parsing file base filters property' );
|
||||||
}
|
}
|
||||||
|
|
||||||
if(defaulted) {
|
if(defaulted) {
|
||||||
this.persist( err => {
|
this.persist( err => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
const defaultActiveUuid = this.toArray()[0].uuid;
|
const defaultActiveUuid = this.toArray()[0].uuid;
|
||||||
this.setActive(defaultActiveUuid);
|
this.setActive(defaultActiveUuid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
persist(cb) {
|
persist(cb) {
|
||||||
return this.client.user.persistProperty('file_base_filters', JSON.stringify(this.filters), cb);
|
return this.client.user.persistProperty('file_base_filters', JSON.stringify(this.filters), cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanTags(tags) {
|
cleanTags(tags) {
|
||||||
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim();
|
return tags.toLowerCase().replace(/,?\s+|,/g, ' ').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
setActive(filterUuid) {
|
setActive(filterUuid) {
|
||||||
const activeFilter = this.get(filterUuid);
|
const activeFilter = this.get(filterUuid);
|
||||||
|
|
||||||
if(activeFilter) {
|
if(activeFilter) {
|
||||||
this.activeFilter = activeFilter;
|
this.activeFilter = activeFilter;
|
||||||
this.client.user.persistProperty('file_base_filter_active_uuid', filterUuid);
|
this.client.user.persistProperty('file_base_filter_active_uuid', filterUuid);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getBuiltInSystemFilters() {
|
static getBuiltInSystemFilters() {
|
||||||
const U_LATEST = '7458b09d-40ab-4f9b-a0d7-0cf866646329';
|
const U_LATEST = '7458b09d-40ab-4f9b-a0d7-0cf866646329';
|
||||||
|
|
||||||
const filters = {
|
const filters = {
|
||||||
[ U_LATEST ] : {
|
[ U_LATEST ] : {
|
||||||
name : 'By Date Added',
|
name : 'By Date Added',
|
||||||
areaTag : '', // all
|
areaTag : '', // all
|
||||||
terms : '', // *
|
terms : '', // *
|
||||||
tags : '', // *
|
tags : '', // *
|
||||||
order : 'descending',
|
order : 'descending',
|
||||||
sort : 'upload_timestamp',
|
sort : 'upload_timestamp',
|
||||||
uuid : U_LATEST,
|
uuid : U_LATEST,
|
||||||
system : true,
|
system : true,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getActiveFilter(client) {
|
static getActiveFilter(client) {
|
||||||
return new FileBaseFilters(client).get(client.user.properties.file_base_filter_active_uuid);
|
return new FileBaseFilters(client).get(client.user.properties.file_base_filter_active_uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFileBaseLastViewedFileIdByUser(user) {
|
static getFileBaseLastViewedFileIdByUser(user) {
|
||||||
return parseInt((user.properties.user_file_base_last_viewed || 0));
|
return parseInt((user.properties.user_file_base_last_viewed || 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) {
|
static setFileBaseLastViewedFileIdForUser(user, fileId, allowOlder, cb) {
|
||||||
if(!cb && _.isFunction(allowOlder)) {
|
if(!cb && _.isFunction(allowOlder)) {
|
||||||
cb = allowOlder;
|
cb = allowOlder;
|
||||||
allowOlder = false;
|
allowOlder = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const current = FileBaseFilters.getFileBaseLastViewedFileIdByUser(user);
|
const current = FileBaseFilters.getFileBaseLastViewedFileIdByUser(user);
|
||||||
if(!allowOlder && fileId < current) {
|
if(!allowOlder && fileId < current) {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
cb(null);
|
cb(null);
|
||||||
}
|
}
|
||||||
return;
|
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 Config = require('./config.js').get;
|
||||||
const { Errors } = require('./enig_error.js');
|
const { Errors } = require('./enig_error.js');
|
||||||
const {
|
const {
|
||||||
splitTextAtTerms,
|
splitTextAtTerms,
|
||||||
isAnsi,
|
isAnsi,
|
||||||
} = require('./string_util.js');
|
} = require('./string_util.js');
|
||||||
const AnsiPrep = require('./ansi_prep.js');
|
const AnsiPrep = require('./ansi_prep.js');
|
||||||
const Log = require('./logger.js').log;
|
const Log = require('./logger.js').log;
|
||||||
|
@ -26,276 +26,276 @@ exports.exportFileList = exportFileList;
|
||||||
exports.updateFileBaseDescFilesScheduledEvent = updateFileBaseDescFilesScheduledEvent;
|
exports.updateFileBaseDescFilesScheduledEvent = updateFileBaseDescFilesScheduledEvent;
|
||||||
|
|
||||||
function exportFileList(filterCriteria, options, cb) {
|
function exportFileList(filterCriteria, options, cb) {
|
||||||
options.templateEncoding = options.templateEncoding || 'utf8';
|
options.templateEncoding = options.templateEncoding || 'utf8';
|
||||||
options.entryTemplate = options.entryTemplate || 'descript_ion_export_entry_template.asc';
|
options.entryTemplate = options.entryTemplate || 'descript_ion_export_entry_template.asc';
|
||||||
options.tsFormat = options.tsFormat || 'YYYY-MM-DD';
|
options.tsFormat = options.tsFormat || 'YYYY-MM-DD';
|
||||||
options.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec
|
options.descWidth = options.descWidth || 45; // FILE_ID.DIZ spec
|
||||||
options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc?
|
options.escapeDesc = _.get(options, 'escapeDesc', false); // escape \r and \n in desc?
|
||||||
|
|
||||||
if(true === options.escapeDesc) {
|
if(true === options.escapeDesc) {
|
||||||
options.escapeDesc = '\\n';
|
options.escapeDesc = '\\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
total : 0,
|
total : 0,
|
||||||
current : 0,
|
current : 0,
|
||||||
step : 'preparing',
|
step : 'preparing',
|
||||||
status : 'Preparing',
|
status : 'Preparing',
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateProgress = _.isFunction(options.progress) ?
|
const updateProgress = _.isFunction(options.progress) ?
|
||||||
progCb => {
|
progCb => {
|
||||||
return options.progress(state, progCb);
|
return options.progress(state, progCb);
|
||||||
} :
|
} :
|
||||||
progCb => {
|
progCb => {
|
||||||
return progCb(null);
|
return progCb(null);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function readTemplateFiles(callback) {
|
function readTemplateFiles(callback) {
|
||||||
updateProgress(err => {
|
updateProgress(err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const templateFiles = [
|
const templateFiles = [
|
||||||
{ name : options.headerTemplate, req : false },
|
{ name : options.headerTemplate, req : false },
|
||||||
{ name : options.entryTemplate, req : true }
|
{ name : options.entryTemplate, req : true }
|
||||||
];
|
];
|
||||||
|
|
||||||
const config = Config();
|
const config = Config();
|
||||||
async.map(templateFiles, (template, nextTemplate) => {
|
async.map(templateFiles, (template, nextTemplate) => {
|
||||||
if(!template.name && !template.req) {
|
if(!template.name && !template.req) {
|
||||||
return nextTemplate(null, Buffer.from([]));
|
return nextTemplate(null, Buffer.from([]));
|
||||||
}
|
}
|
||||||
|
|
||||||
template.name = paths.isAbsolute(template.name) ? template.name : paths.join(config.paths.misc, template.name);
|
template.name = paths.isAbsolute(template.name) ? template.name : paths.join(config.paths.misc, template.name);
|
||||||
fs.readFile(template.name, (err, data) => {
|
fs.readFile(template.name, (err, data) => {
|
||||||
return nextTemplate(err, data);
|
return nextTemplate(err, data);
|
||||||
});
|
});
|
||||||
}, (err, templates) => {
|
}, (err, templates) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(Errors.General(err.message));
|
return callback(Errors.General(err.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
// decode + ensure DOS style CRLF
|
// decode + ensure DOS style CRLF
|
||||||
templates = templates.map(tmp => iconv.decode(tmp, options.templateEncoding).replace(/\r?\n/g, '\r\n') );
|
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
|
// Look for the first {fileDesc} (if any) in 'entry' template & find indentation requirements
|
||||||
let descIndent = 0;
|
let descIndent = 0;
|
||||||
if(!options.escapeDesc) {
|
if(!options.escapeDesc) {
|
||||||
splitTextAtTerms(templates[1]).some(line => {
|
splitTextAtTerms(templates[1]).some(line => {
|
||||||
const pos = line.indexOf('{fileDesc}');
|
const pos = line.indexOf('{fileDesc}');
|
||||||
if(pos > -1) {
|
if(pos > -1) {
|
||||||
descIndent = pos;
|
descIndent = pos;
|
||||||
return true; // found it!
|
return true; // found it!
|
||||||
}
|
}
|
||||||
return false; // keep looking
|
return false; // keep looking
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, templates[0], templates[1], descIndent);
|
return callback(null, templates[0], templates[1], descIndent);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function findFiles(headerTemplate, entryTemplate, descIndent, callback) {
|
function findFiles(headerTemplate, entryTemplate, descIndent, callback) {
|
||||||
state.step = 'gathering';
|
state.step = 'gathering';
|
||||||
state.status = 'Gathering files for supplied criteria';
|
state.status = 'Gathering files for supplied criteria';
|
||||||
updateProgress(err => {
|
updateProgress(err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
FileEntry.findFiles(filterCriteria, (err, fileIds) => {
|
FileEntry.findFiles(filterCriteria, (err, fileIds) => {
|
||||||
if(0 === fileIds.length) {
|
if(0 === fileIds.length) {
|
||||||
return callback(Errors.General('No results for criteria', 'NORESULTS'));
|
return callback(Errors.General('No results for criteria', 'NORESULTS'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(err, headerTemplate, entryTemplate, descIndent, fileIds);
|
return callback(err, headerTemplate, entryTemplate, descIndent, fileIds);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function buildListEntries(headerTemplate, entryTemplate, descIndent, fileIds, callback) {
|
function buildListEntries(headerTemplate, entryTemplate, descIndent, fileIds, callback) {
|
||||||
const formatObj = {
|
const formatObj = {
|
||||||
totalFileCount : fileIds.length,
|
totalFileCount : fileIds.length,
|
||||||
};
|
};
|
||||||
|
|
||||||
let current = 0;
|
let current = 0;
|
||||||
let listBody = '';
|
let listBody = '';
|
||||||
const totals = { fileCount : fileIds.length, bytes : 0 };
|
const totals = { fileCount : fileIds.length, bytes : 0 };
|
||||||
state.total = fileIds.length;
|
state.total = fileIds.length;
|
||||||
|
|
||||||
state.step = 'file';
|
state.step = 'file';
|
||||||
|
|
||||||
async.eachSeries(fileIds, (fileId, nextFileId) => {
|
async.eachSeries(fileIds, (fileId, nextFileId) => {
|
||||||
const fileInfo = new FileEntry();
|
const fileInfo = new FileEntry();
|
||||||
current += 1;
|
current += 1;
|
||||||
|
|
||||||
fileInfo.load(fileId, err => {
|
fileInfo.load(fileId, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return nextFileId(null); // failed, but try the next
|
return nextFileId(null); // failed, but try the next
|
||||||
}
|
}
|
||||||
|
|
||||||
totals.bytes += fileInfo.meta.byte_size;
|
totals.bytes += fileInfo.meta.byte_size;
|
||||||
|
|
||||||
const appendFileInfo = () => {
|
const appendFileInfo = () => {
|
||||||
if(options.escapeDesc) {
|
if(options.escapeDesc) {
|
||||||
formatObj.fileDesc = formatObj.fileDesc.replace(/\r?\n/g, options.escapeDesc);
|
formatObj.fileDesc = formatObj.fileDesc.replace(/\r?\n/g, options.escapeDesc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.maxDescLen) {
|
if(options.maxDescLen) {
|
||||||
formatObj.fileDesc = formatObj.fileDesc.slice(0, options.maxDescLen);
|
formatObj.fileDesc = formatObj.fileDesc.slice(0, options.maxDescLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
listBody += stringFormat(entryTemplate, formatObj);
|
listBody += stringFormat(entryTemplate, formatObj);
|
||||||
|
|
||||||
state.current = current;
|
state.current = current;
|
||||||
state.status = `Processing ${fileInfo.fileName}`;
|
state.status = `Processing ${fileInfo.fileName}`;
|
||||||
state.fileInfo = formatObj;
|
state.fileInfo = formatObj;
|
||||||
|
|
||||||
updateProgress(err => {
|
updateProgress(err => {
|
||||||
return nextFileId(err);
|
return nextFileId(err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const area = FileArea.getFileAreaByTag(fileInfo.areaTag);
|
const area = FileArea.getFileAreaByTag(fileInfo.areaTag);
|
||||||
|
|
||||||
formatObj.fileId = fileId;
|
formatObj.fileId = fileId;
|
||||||
formatObj.areaName = _.get(area, 'name') || 'N/A';
|
formatObj.areaName = _.get(area, 'name') || 'N/A';
|
||||||
formatObj.areaDesc = _.get(area, 'desc') || 'N/A';
|
formatObj.areaDesc = _.get(area, 'desc') || 'N/A';
|
||||||
formatObj.userRating = fileInfo.userRating || 0;
|
formatObj.userRating = fileInfo.userRating || 0;
|
||||||
formatObj.fileName = fileInfo.fileName;
|
formatObj.fileName = fileInfo.fileName;
|
||||||
formatObj.fileSize = fileInfo.meta.byte_size;
|
formatObj.fileSize = fileInfo.meta.byte_size;
|
||||||
formatObj.fileDesc = fileInfo.desc || '';
|
formatObj.fileDesc = fileInfo.desc || '';
|
||||||
formatObj.fileDescShort = formatObj.fileDesc.slice(0, options.descWidth);
|
formatObj.fileDescShort = formatObj.fileDesc.slice(0, options.descWidth);
|
||||||
formatObj.fileSha256 = fileInfo.fileSha256;
|
formatObj.fileSha256 = fileInfo.fileSha256;
|
||||||
formatObj.fileCrc32 = fileInfo.meta.file_crc32;
|
formatObj.fileCrc32 = fileInfo.meta.file_crc32;
|
||||||
formatObj.fileMd5 = fileInfo.meta.file_md5;
|
formatObj.fileMd5 = fileInfo.meta.file_md5;
|
||||||
formatObj.fileSha1 = fileInfo.meta.file_sha1;
|
formatObj.fileSha1 = fileInfo.meta.file_sha1;
|
||||||
formatObj.uploadBy = fileInfo.meta.upload_by_username || 'N/A';
|
formatObj.uploadBy = fileInfo.meta.upload_by_username || 'N/A';
|
||||||
formatObj.fileUploadTs = moment(fileInfo.uploadTimestamp).format(options.tsFormat);
|
formatObj.fileUploadTs = moment(fileInfo.uploadTimestamp).format(options.tsFormat);
|
||||||
formatObj.fileHashTags = fileInfo.hashTags.size > 0 ? Array.from(fileInfo.hashTags).join(', ') : 'N/A';
|
formatObj.fileHashTags = fileInfo.hashTags.size > 0 ? Array.from(fileInfo.hashTags).join(', ') : 'N/A';
|
||||||
formatObj.currentFile = current;
|
formatObj.currentFile = current;
|
||||||
formatObj.progress = Math.floor( (current / fileIds.length) * 100 );
|
formatObj.progress = Math.floor( (current / fileIds.length) * 100 );
|
||||||
|
|
||||||
if(isAnsi(fileInfo.desc)) {
|
if(isAnsi(fileInfo.desc)) {
|
||||||
AnsiPrep(
|
AnsiPrep(
|
||||||
fileInfo.desc,
|
fileInfo.desc,
|
||||||
{
|
{
|
||||||
cols : Math.min(options.descWidth, 79 - descIndent),
|
cols : Math.min(options.descWidth, 79 - descIndent),
|
||||||
forceLineTerm : true, // ensure each line is term'd
|
forceLineTerm : true, // ensure each line is term'd
|
||||||
asciiMode : true, // export to ASCII
|
asciiMode : true, // export to ASCII
|
||||||
fillLines : false, // don't fill up to |cols|
|
fillLines : false, // don't fill up to |cols|
|
||||||
indent : descIndent,
|
indent : descIndent,
|
||||||
},
|
},
|
||||||
(err, desc) => {
|
(err, desc) => {
|
||||||
if(desc) {
|
if(desc) {
|
||||||
formatObj.fileDesc = desc;
|
formatObj.fileDesc = desc;
|
||||||
}
|
}
|
||||||
return appendFileInfo();
|
return appendFileInfo();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const indentSpc = descIndent > 0 ? ' '.repeat(descIndent) : '';
|
const indentSpc = descIndent > 0 ? ' '.repeat(descIndent) : '';
|
||||||
formatObj.fileDesc = splitTextAtTerms(formatObj.fileDesc).join(`\r\n${indentSpc}`) + '\r\n';
|
formatObj.fileDesc = splitTextAtTerms(formatObj.fileDesc).join(`\r\n${indentSpc}`) + '\r\n';
|
||||||
return appendFileInfo();
|
return appendFileInfo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
return callback(err, listBody, headerTemplate, totals);
|
return callback(err, listBody, headerTemplate, totals);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function buildHeader(listBody, headerTemplate, totals, callback) {
|
function buildHeader(listBody, headerTemplate, totals, callback) {
|
||||||
// header is built last such that we can have totals/etc.
|
// header is built last such that we can have totals/etc.
|
||||||
|
|
||||||
let filterAreaName;
|
let filterAreaName;
|
||||||
let filterAreaDesc;
|
let filterAreaDesc;
|
||||||
if(filterCriteria.areaTag) {
|
if(filterCriteria.areaTag) {
|
||||||
const area = FileArea.getFileAreaByTag(filterCriteria.areaTag);
|
const area = FileArea.getFileAreaByTag(filterCriteria.areaTag);
|
||||||
filterAreaName = _.get(area, 'name') || 'N/A';
|
filterAreaName = _.get(area, 'name') || 'N/A';
|
||||||
filterAreaDesc = _.get(area, 'desc') || 'N/A';
|
filterAreaDesc = _.get(area, 'desc') || 'N/A';
|
||||||
} else {
|
} else {
|
||||||
filterAreaName = '-ALL-';
|
filterAreaName = '-ALL-';
|
||||||
filterAreaDesc = 'All areas';
|
filterAreaDesc = 'All areas';
|
||||||
}
|
}
|
||||||
|
|
||||||
const headerFormatObj = {
|
const headerFormatObj = {
|
||||||
nowTs : moment().format(options.tsFormat),
|
nowTs : moment().format(options.tsFormat),
|
||||||
boardName : Config().general.boardName,
|
boardName : Config().general.boardName,
|
||||||
totalFileCount : totals.fileCount,
|
totalFileCount : totals.fileCount,
|
||||||
totalFileSize : totals.bytes,
|
totalFileSize : totals.bytes,
|
||||||
filterAreaTag : filterCriteria.areaTag || '-ALL-',
|
filterAreaTag : filterCriteria.areaTag || '-ALL-',
|
||||||
filterAreaName : filterAreaName,
|
filterAreaName : filterAreaName,
|
||||||
filterAreaDesc : filterAreaDesc,
|
filterAreaDesc : filterAreaDesc,
|
||||||
filterTerms : filterCriteria.terms || '(none)',
|
filterTerms : filterCriteria.terms || '(none)',
|
||||||
filterHashTags : filterCriteria.tags || '(none)',
|
filterHashTags : filterCriteria.tags || '(none)',
|
||||||
};
|
};
|
||||||
|
|
||||||
listBody = stringFormat(headerTemplate, headerFormatObj) + listBody;
|
listBody = stringFormat(headerTemplate, headerFormatObj) + listBody;
|
||||||
return callback(null, listBody);
|
return callback(null, listBody);
|
||||||
},
|
},
|
||||||
function done(listBody, callback) {
|
function done(listBody, callback) {
|
||||||
delete state.fileInfo;
|
delete state.fileInfo;
|
||||||
state.step = 'finished';
|
state.step = 'finished';
|
||||||
state.status = 'Finished processing';
|
state.status = 'Finished processing';
|
||||||
updateProgress( () => {
|
updateProgress( () => {
|
||||||
return callback(null, listBody);
|
return callback(null, listBody);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
], (err, listBody) => {
|
], (err, listBody) => {
|
||||||
return cb(err, listBody);
|
return cb(err, listBody);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFileBaseDescFilesScheduledEvent(args, cb) {
|
function updateFileBaseDescFilesScheduledEvent(args, cb) {
|
||||||
//
|
//
|
||||||
// For each area, loop over storage locations and build
|
// For each area, loop over storage locations and build
|
||||||
// DESCRIPT.ION file to store in the same directory.
|
// DESCRIPT.ION file to store in the same directory.
|
||||||
//
|
//
|
||||||
// Standard-ish 4DOS spec is as such:
|
// Standard-ish 4DOS spec is as such:
|
||||||
// * Entry: <QUOTED_LFN> <DESC>[0x04<AppData>]\r\n
|
// * Entry: <QUOTED_LFN> <DESC>[0x04<AppData>]\r\n
|
||||||
// * Multi line descriptions are stored with *escaped* \r\n pairs
|
// * 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
|
// * Default template uses 0x2c for <AppData> as per https://stackoverflow.com/questions/1810398/descript-ion-file-spec
|
||||||
//
|
//
|
||||||
const entryTemplate = args[0];
|
const entryTemplate = args[0];
|
||||||
const headerTemplate = args[1];
|
const headerTemplate = args[1];
|
||||||
|
|
||||||
const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck : true });
|
const areas = FileArea.getAvailableFileAreas(null, { skipAcsCheck : true });
|
||||||
async.each(areas, (area, nextArea) => {
|
async.each(areas, (area, nextArea) => {
|
||||||
const storageLocations = FileArea.getAreaStorageLocations(area);
|
const storageLocations = FileArea.getAreaStorageLocations(area);
|
||||||
|
|
||||||
async.each(storageLocations, (storageLoc, nextStorageLoc) => {
|
async.each(storageLocations, (storageLoc, nextStorageLoc) => {
|
||||||
const filterCriteria = {
|
const filterCriteria = {
|
||||||
areaTag : area.areaTag,
|
areaTag : area.areaTag,
|
||||||
storageTag : storageLoc.storageTag,
|
storageTag : storageLoc.storageTag,
|
||||||
};
|
};
|
||||||
|
|
||||||
const exportOpts = {
|
const exportOpts = {
|
||||||
headerTemplate : headerTemplate,
|
headerTemplate : headerTemplate,
|
||||||
entryTemplate : entryTemplate,
|
entryTemplate : entryTemplate,
|
||||||
escapeDesc : true, // escape CRLF's
|
escapeDesc : true, // escape CRLF's
|
||||||
maxDescLen : 4096, // DESCRIPT.ION: "The line length limit is 4096 bytes"
|
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');
|
const descIonPath = paths.join(storageLoc.dir, 'DESCRIPT.ION');
|
||||||
fs.writeFile(descIonPath, iconv.encode(listBody, 'cp437'), err => {
|
fs.writeFile(descIonPath, iconv.encode(listBody, 'cp437'), err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.warn( { error : err.message, path : descIonPath }, 'Failed (re)creating DESCRIPT.ION');
|
Log.warn( { error : err.message, path : descIonPath }, 'Failed (re)creating DESCRIPT.ION');
|
||||||
} else {
|
} else {
|
||||||
Log.debug( { path : descIonPath }, '(Re)generated DESCRIPT.ION');
|
Log.debug( { path : descIonPath }, '(Re)generated DESCRIPT.ION');
|
||||||
}
|
}
|
||||||
return nextStorageLoc(null);
|
return nextStorageLoc(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
return nextArea(null);
|
return nextArea(null);
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,110 +11,110 @@ const FileBaseFilters = require('./file_base_filter.js');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File Base Search',
|
name : 'File Base Search',
|
||||||
desc : 'Module for quickly searching the file base',
|
desc : 'Module for quickly searching the file base',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
search : {
|
search : {
|
||||||
searchTerms : 1,
|
searchTerms : 1,
|
||||||
search : 2,
|
search : 2,
|
||||||
tags : 3,
|
tags : 3,
|
||||||
area : 4,
|
area : 4,
|
||||||
orderBy : 5,
|
orderBy : 5,
|
||||||
sort : 6,
|
sort : 6,
|
||||||
advSearch : 7,
|
advSearch : 7,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileBaseSearch extends MenuModule {
|
exports.getModule = class FileBaseSearch extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
search : (formData, extraArgs, cb) => {
|
search : (formData, extraArgs, cb) => {
|
||||||
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||||
return this.searchNow(formData, isAdvanced, cb);
|
return this.searchNow(formData, isAdvanced, cb);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) );
|
const vc = self.addViewController( 'search', new ViewController( { client : this.client } ) );
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
return vc.loadFromMenuConfig( { callingMenu : self, mciMap : mciData.menu }, callback);
|
||||||
},
|
},
|
||||||
function populateAreas(callback) {
|
function populateAreas(callback) {
|
||||||
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
self.availAreas = [ { name : '-ALL-' } ].concat(getSortedAvailableFileAreas(self.client) || []);
|
||||||
|
|
||||||
const areasView = vc.getView(MciViewIds.search.area);
|
const areasView = vc.getView(MciViewIds.search.area);
|
||||||
areasView.setItems( self.availAreas.map( a => a.name ) );
|
areasView.setItems( self.availAreas.map( a => a.name ) );
|
||||||
areasView.redraw();
|
areasView.redraw();
|
||||||
vc.switchFocus(MciViewIds.search.searchTerms);
|
vc.switchFocus(MciViewIds.search.searchTerms);
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedAreaTag(index) {
|
getSelectedAreaTag(index) {
|
||||||
if(0 === index) {
|
if(0 === index) {
|
||||||
return ''; // -ALL-
|
return ''; // -ALL-
|
||||||
}
|
}
|
||||||
const area = this.availAreas[index];
|
const area = this.availAreas[index];
|
||||||
if(!area) {
|
if(!area) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return area.areaTag;
|
return area.areaTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOrderBy(index) {
|
getOrderBy(index) {
|
||||||
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
return FileBaseFilters.OrderByValues[index] || FileBaseFilters.OrderByValues[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
getSortBy(index) {
|
getSortBy(index) {
|
||||||
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
return FileBaseFilters.SortByValues[index] || FileBaseFilters.SortByValues[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterValuesFromFormData(formData, isAdvanced) {
|
getFilterValuesFromFormData(formData, isAdvanced) {
|
||||||
const areaIndex = isAdvanced ? formData.value.areaIndex : 0;
|
const areaIndex = isAdvanced ? formData.value.areaIndex : 0;
|
||||||
const orderByIndex = isAdvanced ? formData.value.orderByIndex : 0;
|
const orderByIndex = isAdvanced ? formData.value.orderByIndex : 0;
|
||||||
const sortByIndex = isAdvanced ? formData.value.sortByIndex : 0;
|
const sortByIndex = isAdvanced ? formData.value.sortByIndex : 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
areaTag : this.getSelectedAreaTag(areaIndex),
|
areaTag : this.getSelectedAreaTag(areaIndex),
|
||||||
terms : formData.value.searchTerms,
|
terms : formData.value.searchTerms,
|
||||||
tags : isAdvanced ? formData.value.tags : '',
|
tags : isAdvanced ? formData.value.tags : '',
|
||||||
order : this.getOrderBy(orderByIndex),
|
order : this.getOrderBy(orderByIndex),
|
||||||
sort : this.getSortBy(sortByIndex),
|
sort : this.getSortBy(sortByIndex),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
searchNow(formData, isAdvanced, cb) {
|
searchNow(formData, isAdvanced, cb) {
|
||||||
const filterCriteria = this.getFilterValuesFromFormData(formData, isAdvanced);
|
const filterCriteria = this.getFilterValuesFromFormData(formData, isAdvanced);
|
||||||
|
|
||||||
const menuOpts = {
|
const menuOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
filterCriteria : filterCriteria,
|
filterCriteria : filterCriteria,
|
||||||
},
|
},
|
||||||
menuFlags : [ 'popParent' ],
|
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 = {
|
exports.moduleInfo = {
|
||||||
name : 'File Base List Export',
|
name : 'File Base List Export',
|
||||||
desc : 'Exports file base listings for download',
|
desc : 'Exports file base listings for download',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormIds = {
|
const FormIds = {
|
||||||
main : 0,
|
main : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
main : {
|
main : {
|
||||||
status : 1,
|
status : 1,
|
||||||
progressBar : 2,
|
progressBar : 2,
|
||||||
|
|
||||||
customRangeStart : 10,
|
customRangeStart : 10,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileBaseListExport extends MenuModule {
|
exports.getModule = class FileBaseListExport extends MenuModule {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
||||||
|
|
||||||
this.config.templateEncoding = this.config.templateEncoding || 'utf8';
|
this.config.templateEncoding = this.config.templateEncoding || 'utf8';
|
||||||
this.config.tsFormat = this.config.tsFormat || this.client.currentTheme.helpers.getDateTimeFormat('short');
|
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.descWidth = this.config.descWidth || 45; // ie FILE_ID.DIZ
|
||||||
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
|
this.config.progBarChar = renderSubstr( (this.config.progBarChar || '▒'), 0, 1);
|
||||||
this.config.compressThreshold = this.config.compressThreshold || (1440000); // >= 1.44M by default :)
|
this.config.compressThreshold = this.config.compressThreshold || (1440000); // >= 1.44M by default :)
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
(callback) => this.prepViewController('main', FormIds.main, mciData.menu, callback),
|
(callback) => this.prepViewController('main', FormIds.main, mciData.menu, callback),
|
||||||
(callback) => this.prepareList(callback),
|
(callback) => this.prepareList(callback),
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
if('NORESULTS' === err.reasonCode) {
|
if('NORESULTS' === err.reasonCode) {
|
||||||
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'fileBaseExportListNoResults');
|
return this.gotoMenu(this.menuConfig.config.noResultsMenu || 'fileBaseExportListNoResults');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prevMenu();
|
return this.prevMenu();
|
||||||
}
|
}
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedLoading() {
|
finishedLoading() {
|
||||||
this.prevMenu();
|
this.prevMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareList(cb) {
|
prepareList(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
const statusView = self.viewControllers.main.getView(MciViewIds.main.status);
|
const statusView = self.viewControllers.main.getView(MciViewIds.main.status);
|
||||||
const updateStatus = (status) => {
|
const updateStatus = (status) => {
|
||||||
if(statusView) {
|
if(statusView) {
|
||||||
statusView.setText(status);
|
statusView.setText(status);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const progBarView = self.viewControllers.main.getView(MciViewIds.main.progressBar);
|
const progBarView = self.viewControllers.main.getView(MciViewIds.main.progressBar);
|
||||||
const updateProgressBar = (curr, total) => {
|
const updateProgressBar = (curr, total) => {
|
||||||
if(progBarView) {
|
if(progBarView) {
|
||||||
const prog = Math.floor( (curr / total) * progBarView.dimens.width );
|
const prog = Math.floor( (curr / total) * progBarView.dimens.width );
|
||||||
progBarView.setText(self.config.progBarChar.repeat(prog));
|
progBarView.setText(self.config.progBarChar.repeat(prog));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cancel = false;
|
let cancel = false;
|
||||||
|
|
||||||
const exportListProgress = (state, progNext) => {
|
const exportListProgress = (state, progNext) => {
|
||||||
switch(state.step) {
|
switch(state.step) {
|
||||||
case 'preparing' :
|
case 'preparing' :
|
||||||
case 'gathering' :
|
case 'gathering' :
|
||||||
updateStatus(state.status);
|
updateStatus(state.status);
|
||||||
break;
|
break;
|
||||||
case 'file' :
|
case 'file' :
|
||||||
updateStatus(state.status);
|
updateStatus(state.status);
|
||||||
updateProgressBar(state.current, state.total);
|
updateProgressBar(state.current, state.total);
|
||||||
self.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state.fileInfo);
|
self.updateCustomViewTextsWithFilter('main', MciViewIds.main.customRangeStart, state.fileInfo);
|
||||||
break;
|
break;
|
||||||
default :
|
default :
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return progNext(cancel ? Errors.General('User canceled') : null);
|
return progNext(cancel ? Errors.General('User canceled') : null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const keyPressHandler = (ch, key) => {
|
const keyPressHandler = (ch, key) => {
|
||||||
if('escape' === key.name) {
|
if('escape' === key.name) {
|
||||||
cancel = true;
|
cancel = true;
|
||||||
self.client.removeListener('key press', keyPressHandler);
|
self.client.removeListener('key press', keyPressHandler);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function buildList(callback) {
|
function buildList(callback) {
|
||||||
// this may take quite a while; temp disable of idle monitor
|
// this may take quite a while; temp disable of idle monitor
|
||||||
self.client.stopIdleMonitor();
|
self.client.stopIdleMonitor();
|
||||||
|
|
||||||
self.client.on('key press', keyPressHandler);
|
self.client.on('key press', keyPressHandler);
|
||||||
|
|
||||||
const filterCriteria = Object.assign({}, self.config.filterCriteria);
|
const filterCriteria = Object.assign({}, self.config.filterCriteria);
|
||||||
if(!filterCriteria.areaTag) {
|
if(!filterCriteria.areaTag) {
|
||||||
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(self.client);
|
filterCriteria.areaTag = FileArea.getAvailableFileAreaTags(self.client);
|
||||||
}
|
}
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
templateEncoding : self.config.templateEncoding,
|
templateEncoding : self.config.templateEncoding,
|
||||||
headerTemplate : _.get(self.config, 'templates.header', 'file_list_header.asc'),
|
headerTemplate : _.get(self.config, 'templates.header', 'file_list_header.asc'),
|
||||||
entryTemplate : _.get(self.config, 'templates.entry', 'file_list_entry.asc'),
|
entryTemplate : _.get(self.config, 'templates.entry', 'file_list_entry.asc'),
|
||||||
tsFormat : self.config.tsFormat,
|
tsFormat : self.config.tsFormat,
|
||||||
descWidth : self.config.descWidth,
|
descWidth : self.config.descWidth,
|
||||||
progress : exportListProgress,
|
progress : exportListProgress,
|
||||||
};
|
};
|
||||||
|
|
||||||
exportFileList(filterCriteria, opts, (err, listBody) => {
|
exportFileList(filterCriteria, opts, (err, listBody) => {
|
||||||
return callback(err, listBody);
|
return callback(err, listBody);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function persistList(listBody, callback) {
|
function persistList(listBody, callback) {
|
||||||
updateStatus('Persisting list');
|
updateStatus('Persisting list');
|
||||||
|
|
||||||
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
|
const sysTempDownloadArea = FileArea.getFileAreaByTag(FileArea.WellKnownAreaTags.TempDownloads);
|
||||||
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
|
const sysTempDownloadDir = FileArea.getAreaDefaultStorageDirectory(sysTempDownloadArea);
|
||||||
|
|
||||||
fse.mkdirs(sysTempDownloadDir, err => {
|
fse.mkdirs(sysTempDownloadDir, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const outputFileName = paths.join(
|
const outputFileName = paths.join(
|
||||||
sysTempDownloadDir,
|
sysTempDownloadDir,
|
||||||
`file_list_${uuidv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
|
`file_list_${uuidv4().substr(-8)}_${moment().format('YYYY-MM-DD')}.txt`
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFile(outputFileName, listBody, 'utf8', err => {
|
fs.writeFile(outputFileName, listBody, 'utf8', err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.getSizeAndCompressIfMeetsSizeThreshold(outputFileName, (err, finalOutputFileName, fileSize) => {
|
self.getSizeAndCompressIfMeetsSizeThreshold(outputFileName, (err, finalOutputFileName, fileSize) => {
|
||||||
return callback(err, finalOutputFileName, fileSize, sysTempDownloadArea);
|
return callback(err, finalOutputFileName, fileSize, sysTempDownloadArea);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function persistFileEntry(outputFileName, fileSize, sysTempDownloadArea, callback) {
|
function persistFileEntry(outputFileName, fileSize, sysTempDownloadArea, callback) {
|
||||||
const newEntry = new FileEntry({
|
const newEntry = new FileEntry({
|
||||||
areaTag : sysTempDownloadArea.areaTag,
|
areaTag : sysTempDownloadArea.areaTag,
|
||||||
fileName : paths.basename(outputFileName),
|
fileName : paths.basename(outputFileName),
|
||||||
storageTag : sysTempDownloadArea.storageTags[0],
|
storageTag : sysTempDownloadArea.storageTags[0],
|
||||||
meta : {
|
meta : {
|
||||||
upload_by_username : self.client.user.username,
|
upload_by_username : self.client.user.username,
|
||||||
upload_by_user_id : self.client.user.userId,
|
upload_by_user_id : self.client.user.userId,
|
||||||
byte_size : fileSize,
|
byte_size : fileSize,
|
||||||
session_temp_dl : 1, // download is valid until session is over
|
session_temp_dl : 1, // download is valid until session is over
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
newEntry.desc = 'File List Export';
|
newEntry.desc = 'File List Export';
|
||||||
|
|
||||||
newEntry.persist(err => {
|
newEntry.persist(err => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
// queue it!
|
// queue it!
|
||||||
const dlQueue = new DownloadQueue(self.client);
|
const dlQueue = new DownloadQueue(self.client);
|
||||||
dlQueue.add(newEntry, true); // true=systemFile
|
dlQueue.add(newEntry, true); // true=systemFile
|
||||||
|
|
||||||
// clean up after ourselves when the session ends
|
// clean up after ourselves when the session ends
|
||||||
const thisClientId = self.client.session.id;
|
const thisClientId = self.client.session.id;
|
||||||
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
Events.once(Events.getSystemEvents().ClientDisconnected, evt => {
|
||||||
if(thisClientId === _.get(evt, 'client.session.id')) {
|
if(thisClientId === _.get(evt, 'client.session.id')) {
|
||||||
FileEntry.removeEntry(newEntry, { removePhysFile : true }, err => {
|
FileEntry.removeEntry(newEntry, { removePhysFile : true }, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
Log.warn( { fileId : newEntry.fileId, path : outputFileName }, 'Failed removing temporary session download' );
|
Log.warn( { fileId : newEntry.fileId, path : outputFileName }, 'Failed removing temporary session download' );
|
||||||
} else {
|
} else {
|
||||||
Log.debug( { fileId : newEntry.fileId, path : outputFileName }, 'Removed temporary session download item' );
|
Log.debug( { fileId : newEntry.fileId, path : outputFileName }, 'Removed temporary session download item' );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function done(callback) {
|
function done(callback) {
|
||||||
// re-enable idle monitor
|
// re-enable idle monitor
|
||||||
self.client.startIdleMonitor();
|
self.client.startIdleMonitor();
|
||||||
|
|
||||||
updateStatus('Exported list has been added to your download queue');
|
updateStatus('Exported list has been added to your download queue');
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
self.client.removeListener('key press', keyPressHandler);
|
self.client.removeListener('key press', keyPressHandler);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSizeAndCompressIfMeetsSizeThreshold(filePath, cb) {
|
getSizeAndCompressIfMeetsSizeThreshold(filePath, cb) {
|
||||||
fse.stat(filePath, (err, stats) => {
|
fse.stat(filePath, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stats.size < this.config.compressThreshold) {
|
if(stats.size < this.config.compressThreshold) {
|
||||||
// small enough, keep orig
|
// small enough, keep orig
|
||||||
return cb(null, filePath, stats.size);
|
return cb(null, filePath, stats.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
const zipFilePath = `${filePath}.zip`;
|
const zipFilePath = `${filePath}.zip`;
|
||||||
|
|
||||||
const zipFile = new yazl.ZipFile();
|
const zipFile = new yazl.ZipFile();
|
||||||
zipFile.addFile(filePath, paths.basename(filePath));
|
zipFile.addFile(filePath, paths.basename(filePath));
|
||||||
zipFile.end( () => {
|
zipFile.end( () => {
|
||||||
const outZipFile = fs.createWriteStream(zipFilePath);
|
const outZipFile = fs.createWriteStream(zipFilePath);
|
||||||
zipFile.outputStream.pipe(outZipFile);
|
zipFile.outputStream.pipe(outZipFile);
|
||||||
zipFile.outputStream.on('finish', () => {
|
zipFile.outputStream.on('finish', () => {
|
||||||
// delete the original
|
// delete the original
|
||||||
fse.unlink(filePath, err => {
|
fse.unlink(filePath, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally stat the new output
|
// finally stat the new output
|
||||||
fse.stat(zipFilePath, (err, stats) => {
|
fse.stat(zipFilePath, (err, stats) => {
|
||||||
return cb(err, zipFilePath, stats ? stats.size : 0);
|
return cb(err, zipFilePath, stats ? stats.size : 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
|
@ -19,269 +19,269 @@ const _ = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File Base Download Web Queue Manager',
|
name : 'File Base Download Web Queue Manager',
|
||||||
desc : 'Module for interacting with web backed download queue/batch',
|
desc : 'Module for interacting with web backed download queue/batch',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormIds = {
|
const FormIds = {
|
||||||
queueManager : 0
|
queueManager : 0
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
queueManager : {
|
queueManager : {
|
||||||
queue : 1,
|
queue : 1,
|
||||||
navMenu : 2,
|
navMenu : 2,
|
||||||
|
|
||||||
customRangeStart : 10,
|
customRangeStart : 10,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
exports.getModule = class FileBaseWebDownloadQueueManager extends MenuModule {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.dlQueue = new DownloadQueue(this.client);
|
this.dlQueue = new DownloadQueue(this.client);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
removeItem : (formData, extraArgs, cb) => {
|
removeItem : (formData, extraArgs, cb) => {
|
||||||
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
const selectedItem = this.dlQueue.items[formData.value.queueItem];
|
||||||
if(!selectedItem) {
|
if(!selectedItem) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dlQueue.removeItems(selectedItem.fileId);
|
this.dlQueue.removeItems(selectedItem.fileId);
|
||||||
|
|
||||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||||
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
return this.removeItemsFromDownloadQueueView(formData.value.queueItem, cb);
|
||||||
},
|
},
|
||||||
clearQueue : (formData, extraArgs, cb) => {
|
clearQueue : (formData, extraArgs, cb) => {
|
||||||
this.dlQueue.clear();
|
this.dlQueue.clear();
|
||||||
|
|
||||||
// :TODO: broken: does not redraw menu properly - needs fixed!
|
// :TODO: broken: does not redraw menu properly - needs fixed!
|
||||||
return this.removeItemsFromDownloadQueueView('all', cb);
|
return this.removeItemsFromDownloadQueueView('all', cb);
|
||||||
},
|
},
|
||||||
getBatchLink : (formData, extraArgs, cb) => {
|
getBatchLink : (formData, extraArgs, cb) => {
|
||||||
return this.generateAndDisplayBatchLink(cb);
|
return this.generateAndDisplayBatchLink(cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
if(0 === this.dlQueue.items.length) {
|
if(0 === this.dlQueue.items.length) {
|
||||||
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
return this.gotoMenu(this.menuConfig.config.emptyQueueMenu || 'fileBaseDownloadManagerEmptyQueue');
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function beforeArt(callback) {
|
function beforeArt(callback) {
|
||||||
return self.beforeArt(callback);
|
return self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function display(callback) {
|
function display(callback) {
|
||||||
return self.displayQueueManagerPage(false, callback);
|
return self.displayQueueManagerPage(false, callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
return self.finishedLoading();
|
return self.finishedLoading();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
removeItemsFromDownloadQueueView(itemIndex, cb) {
|
||||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||||
if(!queueView) {
|
if(!queueView) {
|
||||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if('all' === itemIndex) {
|
if('all' === itemIndex) {
|
||||||
queueView.setItems([]);
|
queueView.setItems([]);
|
||||||
queueView.setFocusItems([]);
|
queueView.setFocusItems([]);
|
||||||
} else {
|
} else {
|
||||||
queueView.removeItem(itemIndex);
|
queueView.removeItem(itemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
queueView.redraw();
|
queueView.redraw();
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayFileInfoForFileEntry(fileEntry) {
|
displayFileInfoForFileEntry(fileEntry) {
|
||||||
this.updateCustomViewTextsWithFilter(
|
this.updateCustomViewTextsWithFilter(
|
||||||
'queueManager',
|
'queueManager',
|
||||||
MciViewIds.queueManager.customRangeStart, fileEntry,
|
MciViewIds.queueManager.customRangeStart, fileEntry,
|
||||||
{ filter : [ '{webDlLink}', '{webDlExpire}', '{fileName}' ] } // :TODO: Others....
|
{ filter : [ '{webDlLink}', '{webDlExpire}', '{fileName}' ] } // :TODO: Others....
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDownloadQueueView(cb) {
|
updateDownloadQueueView(cb) {
|
||||||
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
const queueView = this.viewControllers.queueManager.getView(MciViewIds.queueManager.queue);
|
||||||
if(!queueView) {
|
if(!queueView) {
|
||||||
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
return cb(Errors.DoesNotExist('Queue view does not exist'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const queueListFormat = this.menuConfig.config.queueListFormat || '{webDlLink}';
|
const queueListFormat = this.menuConfig.config.queueListFormat || '{webDlLink}';
|
||||||
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
const focusQueueListFormat = this.menuConfig.config.focusQueueListFormat || queueListFormat;
|
||||||
|
|
||||||
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
queueView.setItems(this.dlQueue.items.map( queueItem => stringFormat(queueListFormat, queueItem) ) );
|
||||||
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
queueView.setFocusItems(this.dlQueue.items.map( queueItem => stringFormat(focusQueueListFormat, queueItem) ) );
|
||||||
|
|
||||||
queueView.on('index update', idx => {
|
queueView.on('index update', idx => {
|
||||||
const fileEntry = this.dlQueue.items[idx];
|
const fileEntry = this.dlQueue.items[idx];
|
||||||
this.displayFileInfoForFileEntry(fileEntry);
|
this.displayFileInfoForFileEntry(fileEntry);
|
||||||
});
|
});
|
||||||
|
|
||||||
queueView.redraw();
|
queueView.redraw();
|
||||||
this.displayFileInfoForFileEntry(this.dlQueue.items[0]);
|
this.displayFileInfoForFileEntry(this.dlQueue.items[0]);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
generateAndDisplayBatchLink(cb) {
|
generateAndDisplayBatchLink(cb) {
|
||||||
const expireTime = moment().add(Config().fileBase.web.expireMinutes, 'minutes');
|
const expireTime = moment().add(Config().fileBase.web.expireMinutes, 'minutes');
|
||||||
|
|
||||||
FileAreaWeb.createAndServeTempBatchDownload(
|
FileAreaWeb.createAndServeTempBatchDownload(
|
||||||
this.client,
|
this.client,
|
||||||
this.dlQueue.items,
|
this.dlQueue.items,
|
||||||
{
|
{
|
||||||
expireTime : expireTime
|
expireTime : expireTime
|
||||||
},
|
},
|
||||||
(err, webBatchDlLink) => {
|
(err, webBatchDlLink) => {
|
||||||
// :TODO: handle not enabled -> display such
|
// :TODO: handle not enabled -> display such
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(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 = {
|
const formatObj = {
|
||||||
webBatchDlLink : ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
|
webBatchDlLink : ansi.vtxHyperlink(this.client, webBatchDlLink) + webBatchDlLink,
|
||||||
webBatchDlExpire : expireTime.format(webDlExpireTimeFormat),
|
webBatchDlExpire : expireTime.format(webDlExpireTimeFormat),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.updateCustomViewTextsWithFilter(
|
this.updateCustomViewTextsWithFilter(
|
||||||
'queueManager',
|
'queueManager',
|
||||||
MciViewIds.queueManager.customRangeStart,
|
MciViewIds.queueManager.customRangeStart,
|
||||||
formatObj,
|
formatObj,
|
||||||
{ filter : Object.keys(formatObj).map(k => '{' + k + '}' ) }
|
{ filter : Object.keys(formatObj).map(k => '{' + k + '}' ) }
|
||||||
);
|
);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayQueueManagerPage(clearScreen, cb) {
|
displayQueueManagerPage(clearScreen, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function prepArtAndViewController(callback) {
|
function prepArtAndViewController(callback) {
|
||||||
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
return self.displayArtAndPrepViewController('queueManager', { clearScreen : clearScreen }, callback);
|
||||||
},
|
},
|
||||||
function prepareQueueDownloadLinks(callback) {
|
function prepareQueueDownloadLinks(callback) {
|
||||||
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
const webDlExpireTimeFormat = self.menuConfig.config.webDlExpireTimeFormat || 'YYYY-MMM-DD @ h:mm';
|
||||||
|
|
||||||
const config = Config();
|
const config = Config();
|
||||||
async.each(self.dlQueue.items, (fileEntry, nextFileEntry) => {
|
async.each(self.dlQueue.items, (fileEntry, nextFileEntry) => {
|
||||||
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => {
|
FileAreaWeb.getExistingTempDownloadServeItem(self.client, fileEntry, (err, serveItem) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
if(ErrNotEnabled === err.reasonCode) {
|
if(ErrNotEnabled === err.reasonCode) {
|
||||||
return nextFileEntry(err); // we should have caught this prior
|
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(
|
FileAreaWeb.createAndServeTempDownload(
|
||||||
self.client,
|
self.client,
|
||||||
fileEntry,
|
fileEntry,
|
||||||
{ expireTime : expireTime },
|
{ expireTime : expireTime },
|
||||||
(err, url) => {
|
(err, url) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return nextFileEntry(err);
|
return nextFileEntry(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileEntry.webDlLinkRaw = url;
|
fileEntry.webDlLinkRaw = url;
|
||||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
|
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, url) + url;
|
||||||
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat);
|
fileEntry.webDlExpire = expireTime.format(webDlExpireTimeFormat);
|
||||||
|
|
||||||
return nextFileEntry(null);
|
return nextFileEntry(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
fileEntry.webDlLinkRaw = serveItem.url;
|
fileEntry.webDlLinkRaw = serveItem.url;
|
||||||
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url;
|
fileEntry.webDlLink = ansi.vtxHyperlink(self.client, serveItem.url) + serveItem.url;
|
||||||
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
fileEntry.webDlExpire = moment(serveItem.expireTimestamp).format(webDlExpireTimeFormat);
|
||||||
return nextFileEntry(null);
|
return nextFileEntry(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function populateViews(callback) {
|
function populateViews(callback) {
|
||||||
return self.updateDownloadQueueView(callback);
|
return self.updateDownloadQueueView(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayArtAndPrepViewController(name, options, cb) {
|
displayArtAndPrepViewController(name, options, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const config = this.menuConfig.config;
|
const config = this.menuConfig.config;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function readyAndDisplayArt(callback) {
|
function readyAndDisplayArt(callback) {
|
||||||
if(options.clearScreen) {
|
if(options.clearScreen) {
|
||||||
self.client.term.rawWrite(ansi.resetScreen());
|
self.client.term.rawWrite(ansi.resetScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
config.art[name],
|
config.art[name],
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font, trailingLF : false },
|
{ font : self.menuConfig.font, trailingLF : false },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function prepeareViewController(artData, callback) {
|
function prepeareViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers[name])) {
|
if(_.isUndefined(self.viewControllers[name])) {
|
||||||
const vcOpts = {
|
const vcOpts = {
|
||||||
client : self.client,
|
client : self.client,
|
||||||
formId : FormIds[name],
|
formId : FormIds[name],
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!_.isUndefined(options.noInput)) {
|
if(!_.isUndefined(options.noInput)) {
|
||||||
vcOpts.noInput = options.noInput;
|
vcOpts.noInput = options.noInput;
|
||||||
}
|
}
|
||||||
|
|
||||||
const vc = self.addViewController(name, new ViewController(vcOpts));
|
const vc = self.addViewController(name, new ViewController(vcOpts));
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds[name],
|
formId : FormIds[name],
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.viewControllers[name].setFocus(true);
|
self.viewControllers[name].setFocus(true);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(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 = {
|
exports.moduleInfo = {
|
||||||
name : 'Transfer file',
|
name : 'Transfer file',
|
||||||
desc : 'Sends or receives a file(s)',
|
desc : 'Sends or receives a file(s)',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class TransferFileModule extends MenuModule {
|
exports.getModule = class TransferFileModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.config = this.menuConfig.config || {};
|
this.config = this.menuConfig.config || {};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Most options can be set via extraArgs or config block
|
// Most options can be set via extraArgs or config block
|
||||||
//
|
//
|
||||||
const config = Config();
|
const config = Config();
|
||||||
if(options.extraArgs) {
|
if(options.extraArgs) {
|
||||||
if(options.extraArgs.protocol) {
|
if(options.extraArgs.protocol) {
|
||||||
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol];
|
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.extraArgs.direction) {
|
if(options.extraArgs.direction) {
|
||||||
this.direction = options.extraArgs.direction;
|
this.direction = options.extraArgs.direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.extraArgs.sendQueue) {
|
if(options.extraArgs.sendQueue) {
|
||||||
this.sendQueue = options.extraArgs.sendQueue;
|
this.sendQueue = options.extraArgs.sendQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.extraArgs.recvFileName) {
|
if(options.extraArgs.recvFileName) {
|
||||||
this.recvFileName = options.extraArgs.recvFileName;
|
this.recvFileName = options.extraArgs.recvFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.extraArgs.recvDirectory) {
|
if(options.extraArgs.recvDirectory) {
|
||||||
this.recvDirectory = options.extraArgs.recvDirectory;
|
this.recvDirectory = options.extraArgs.recvDirectory;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if(this.config.protocol) {
|
if(this.config.protocol) {
|
||||||
this.protocolConfig = config.fileTransferProtocols[this.config.protocol];
|
this.protocolConfig = config.fileTransferProtocols[this.config.protocol];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.config.direction) {
|
if(this.config.direction) {
|
||||||
this.direction = this.config.direction;
|
this.direction = this.config.direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.config.sendQueue) {
|
if(this.config.sendQueue) {
|
||||||
this.sendQueue = this.config.sendQueue;
|
this.sendQueue = this.config.sendQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.config.recvFileName) {
|
if(this.config.recvFileName) {
|
||||||
this.recvFileName = this.config.recvFileName;
|
this.recvFileName = this.config.recvFileName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.config.recvDirectory) {
|
if(this.config.recvDirectory) {
|
||||||
this.recvDirectory = this.config.recvDirectory;
|
this.recvDirectory = this.config.recvDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.protocolConfig = this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
|
this.protocolConfig = this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
|
||||||
this.direction = this.direction || 'send';
|
this.direction = this.direction || 'send';
|
||||||
this.sendQueue = this.sendQueue || [];
|
this.sendQueue = this.sendQueue || [];
|
||||||
|
|
||||||
// Ensure sendQueue is an array of objects that contain at least a 'path' member
|
// Ensure sendQueue is an array of objects that contain at least a 'path' member
|
||||||
this.sendQueue = this.sendQueue.map(item => {
|
this.sendQueue = this.sendQueue.map(item => {
|
||||||
if(_.isString(item)) {
|
if(_.isString(item)) {
|
||||||
return { path : item };
|
return { path : item };
|
||||||
} else {
|
} else {
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sentFileIds = [];
|
this.sentFileIds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
isSending() {
|
isSending() {
|
||||||
return ('send' === this.direction);
|
return ('send' === this.direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
restorePipeAfterExternalProc() {
|
restorePipeAfterExternalProc() {
|
||||||
if(!this.pipeRestored) {
|
if(!this.pipeRestored) {
|
||||||
this.pipeRestored = true;
|
this.pipeRestored = true;
|
||||||
|
|
||||||
this.client.restoreDataHandler();
|
this.client.restoreDataHandler();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendFiles(cb) {
|
sendFiles(cb) {
|
||||||
// assume *sending* can always batch
|
// assume *sending* can always batch
|
||||||
// :TODO: Look into this further
|
// :TODO: Look into this further
|
||||||
const allFiles = this.sendQueue.map(f => f.path);
|
const allFiles = this.sendQueue.map(f => f.path);
|
||||||
this.executeExternalProtocolHandlerForSend(allFiles, err => {
|
this.executeExternalProtocolHandlerForSend(allFiles, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
this.client.log.warn( { files : allFiles, error : err.message }, 'Error sending file(s)' );
|
this.client.log.warn( { files : allFiles, error : err.message }, 'Error sending file(s)' );
|
||||||
} else {
|
} else {
|
||||||
const sentFiles = [];
|
const sentFiles = [];
|
||||||
this.sendQueue.forEach(f => {
|
this.sendQueue.forEach(f => {
|
||||||
f.sent = true;
|
f.sent = true;
|
||||||
sentFiles.push(f.path);
|
sentFiles.push(f.path);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` );
|
this.client.log.info( { sentFiles : sentFiles }, `Successfully sent ${sentFiles.length} file(s)` );
|
||||||
}
|
}
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
sendFiles(cb) {
|
sendFiles(cb) {
|
||||||
// :TODO: built in/native protocol support
|
// :TODO: built in/native protocol support
|
||||||
|
|
||||||
|
@ -189,408 +189,408 @@ exports.getModule = class TransferFileModule extends MenuModule {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
moveFileWithCollisionHandling(src, dst, cb) {
|
moveFileWithCollisionHandling(src, dst, cb) {
|
||||||
//
|
//
|
||||||
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
|
||||||
// in the case of collisions.
|
// in the case of collisions.
|
||||||
//
|
//
|
||||||
const dstPath = paths.dirname(dst);
|
const dstPath = paths.dirname(dst);
|
||||||
const dstFileExt = paths.extname(dst);
|
const dstFileExt = paths.extname(dst);
|
||||||
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
||||||
|
|
||||||
let renameIndex = 0;
|
let renameIndex = 0;
|
||||||
let movedOk = false;
|
let movedOk = false;
|
||||||
let tryDstPath;
|
let tryDstPath;
|
||||||
|
|
||||||
async.until(
|
async.until(
|
||||||
() => movedOk, // until moved OK
|
() => movedOk, // until moved OK
|
||||||
(cb) => {
|
(cb) => {
|
||||||
if(0 === renameIndex) {
|
if(0 === renameIndex) {
|
||||||
// try originally supplied path first
|
// try originally supplied path first
|
||||||
tryDstPath = dst;
|
tryDstPath = dst;
|
||||||
} else {
|
} else {
|
||||||
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
fse.move(src, tryDstPath, err => {
|
fse.move(src, tryDstPath, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
if('EEXIST' === err.code) {
|
if('EEXIST' === err.code) {
|
||||||
renameIndex += 1;
|
renameIndex += 1;
|
||||||
return cb(null); // keep trying
|
return cb(null); // keep trying
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
movedOk = true;
|
movedOk = true;
|
||||||
return cb(null, tryDstPath);
|
return cb(null, tryDstPath);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
(err, finalPath) => {
|
(err, finalPath) => {
|
||||||
return cb(err, finalPath);
|
return cb(err, finalPath);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
recvFiles(cb) {
|
recvFiles(cb) {
|
||||||
this.executeExternalProtocolHandlerForRecv(err => {
|
this.executeExternalProtocolHandlerForRecv(err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recvFilePaths = [];
|
this.recvFilePaths = [];
|
||||||
|
|
||||||
if(this.recvFileName) {
|
if(this.recvFileName) {
|
||||||
//
|
//
|
||||||
// file name specified - we expect a single file in |this.recvDirectory|
|
// file name specified - we expect a single file in |this.recvDirectory|
|
||||||
// by the name of |this.recvFileName|
|
// by the name of |this.recvFileName|
|
||||||
//
|
//
|
||||||
const recvFullPath = paths.join(this.recvDirectory, this.recvFileName);
|
const recvFullPath = paths.join(this.recvDirectory, this.recvFileName);
|
||||||
fs.stat(recvFullPath, (err, stats) => {
|
fs.stat(recvFullPath, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!stats.isFile()) {
|
if(!stats.isFile()) {
|
||||||
return cb(Errors.Invalid('Expected file entry in recv directory'));
|
return cb(Errors.Invalid('Expected file entry in recv directory'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recvFilePaths.push(recvFullPath);
|
this.recvFilePaths.push(recvFullPath);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
// Blind Upload (recv): files in |this.recvDirectory| should be named appropriately already
|
// Blind Upload (recv): files in |this.recvDirectory| should be named appropriately already
|
||||||
//
|
//
|
||||||
fs.readdir(this.recvDirectory, (err, files) => {
|
fs.readdir(this.recvDirectory, (err, files) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// stat each to grab files only
|
// stat each to grab files only
|
||||||
async.each(files, (fileName, nextFile) => {
|
async.each(files, (fileName, nextFile) => {
|
||||||
const recvFullPath = paths.join(this.recvDirectory, fileName);
|
const recvFullPath = paths.join(this.recvDirectory, fileName);
|
||||||
|
|
||||||
fs.stat(recvFullPath, (err, stats) => {
|
fs.stat(recvFullPath, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
this.client.log.warn('Failed to stat file', { path : recvFullPath } );
|
this.client.log.warn('Failed to stat file', { path : recvFullPath } );
|
||||||
return nextFile(null); // just try the next one
|
return nextFile(null); // just try the next one
|
||||||
}
|
}
|
||||||
|
|
||||||
if(stats.isFile()) {
|
if(stats.isFile()) {
|
||||||
this.recvFilePaths.push(recvFullPath);
|
this.recvFilePaths.push(recvFullPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextFile(null);
|
return nextFile(null);
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pathWithTerminatingSeparator(path) {
|
pathWithTerminatingSeparator(path) {
|
||||||
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
||||||
path = path + paths.sep;
|
path = path + paths.sep;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepAndBuildSendArgs(filePaths, cb) {
|
prepAndBuildSendArgs(filePaths, cb) {
|
||||||
const externalArgs = this.protocolConfig.external['sendArgs'];
|
const externalArgs = this.protocolConfig.external['sendArgs'];
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getTempFileListPath(callback) {
|
function getTempFileListPath(callback) {
|
||||||
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) );
|
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) );
|
||||||
if(!hasFileList) {
|
if(!hasFileList) {
|
||||||
return callback(null, null);
|
return callback(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
|
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err); // failed to create it
|
return callback(err); // failed to create it
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL));
|
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL));
|
||||||
fs.close(tempFileInfo.fd, err => {
|
fs.close(tempFileInfo.fd, err => {
|
||||||
return callback(err, tempFileInfo.path);
|
return callback(err, tempFileInfo.path);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function createArgs(tempFileListPath, callback) {
|
function createArgs(tempFileListPath, callback) {
|
||||||
// initial args: ignore {filePaths} as we must break that into it's own sep array items
|
// initial args: ignore {filePaths} as we must break that into it's own sep array items
|
||||||
const args = externalArgs.map(arg => {
|
const args = externalArgs.map(arg => {
|
||||||
return '{filePaths}' === arg ? arg : stringFormat(arg, {
|
return '{filePaths}' === arg ? arg : stringFormat(arg, {
|
||||||
fileListPath : tempFileListPath || '',
|
fileListPath : tempFileListPath || '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const filePathsPos = args.indexOf('{filePaths}');
|
const filePathsPos = args.indexOf('{filePaths}');
|
||||||
if(filePathsPos > -1) {
|
if(filePathsPos > -1) {
|
||||||
// replace {filePaths} with 0:n individual entries in |args|
|
// replace {filePaths} with 0:n individual entries in |args|
|
||||||
args.splice.apply( args, [ filePathsPos, 1 ].concat(filePaths) );
|
args.splice.apply( args, [ filePathsPos, 1 ].concat(filePaths) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, args);
|
return callback(null, args);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err, args) => {
|
(err, args) => {
|
||||||
return cb(err, args);
|
return cb(err, args);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepAndBuildRecvArgs(cb) {
|
prepAndBuildRecvArgs(cb) {
|
||||||
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
|
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
|
||||||
const externalArgs = this.protocolConfig.external[argsKey];
|
const externalArgs = this.protocolConfig.external[argsKey];
|
||||||
const args = externalArgs.map(arg => stringFormat(arg, {
|
const args = externalArgs.map(arg => stringFormat(arg, {
|
||||||
uploadDir : this.recvDirectory,
|
uploadDir : this.recvDirectory,
|
||||||
fileName : this.recvFileName || '',
|
fileName : this.recvFileName || '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return cb(null, args);
|
return cb(null, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
executeExternalProtocolHandler(args, cb) {
|
executeExternalProtocolHandler(args, cb) {
|
||||||
const external = this.protocolConfig.external;
|
const external = this.protocolConfig.external;
|
||||||
const cmd = external[`${this.direction}Cmd`];
|
const cmd = external[`${this.direction}Cmd`];
|
||||||
|
|
||||||
this.client.log.debug(
|
this.client.log.debug(
|
||||||
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
|
{ cmd : cmd, args : args, tempDir : this.recvDirectory, direction : this.direction },
|
||||||
'Executing external protocol'
|
'Executing external protocol'
|
||||||
);
|
);
|
||||||
|
|
||||||
const spawnOpts = {
|
const spawnOpts = {
|
||||||
cols : this.client.term.termWidth,
|
cols : this.client.term.termWidth,
|
||||||
rows : this.client.term.termHeight,
|
rows : this.client.term.termHeight,
|
||||||
cwd : this.recvDirectory,
|
cwd : this.recvDirectory,
|
||||||
encoding : null, // don't bork our data!
|
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 => {
|
this.client.setTemporaryDirectDataHandler(data => {
|
||||||
// needed for things like sz/rz
|
// needed for things like sz/rz
|
||||||
if(external.escapeTelnet) {
|
if(external.escapeTelnet) {
|
||||||
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
|
const tmp = data.toString('binary').replace(/\xff{2}/g, '\xff'); // de-escape
|
||||||
externalProc.write(Buffer.from(tmp, 'binary'));
|
externalProc.write(Buffer.from(tmp, 'binary'));
|
||||||
} else {
|
} else {
|
||||||
externalProc.write(data);
|
externalProc.write(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
externalProc.on('data', data => {
|
externalProc.on('data', data => {
|
||||||
// needed for things like sz/rz
|
// needed for things like sz/rz
|
||||||
if(external.escapeTelnet) {
|
if(external.escapeTelnet) {
|
||||||
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape
|
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff'); // escape
|
||||||
this.client.term.rawWrite(Buffer.from(tmp, 'binary'));
|
this.client.term.rawWrite(Buffer.from(tmp, 'binary'));
|
||||||
} else {
|
} else {
|
||||||
this.client.term.rawWrite(data);
|
this.client.term.rawWrite(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
externalProc.once('close', () => {
|
externalProc.once('close', () => {
|
||||||
return this.restorePipeAfterExternalProc();
|
return this.restorePipeAfterExternalProc();
|
||||||
});
|
});
|
||||||
|
|
||||||
externalProc.once('exit', (exitCode) => {
|
externalProc.once('exit', (exitCode) => {
|
||||||
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
|
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
|
||||||
|
|
||||||
this.restorePipeAfterExternalProc();
|
this.restorePipeAfterExternalProc();
|
||||||
externalProc.removeAllListeners();
|
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) {
|
executeExternalProtocolHandlerForSend(filePaths, cb) {
|
||||||
if(!Array.isArray(filePaths)) {
|
if(!Array.isArray(filePaths)) {
|
||||||
filePaths = [ filePaths ];
|
filePaths = [ filePaths ];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prepAndBuildSendArgs(filePaths, (err, args) => {
|
this.prepAndBuildSendArgs(filePaths, (err, args) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.executeExternalProtocolHandler(args, err => {
|
this.executeExternalProtocolHandler(args, err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
executeExternalProtocolHandlerForRecv(cb) {
|
executeExternalProtocolHandlerForRecv(cb) {
|
||||||
this.prepAndBuildRecvArgs( (err, args) => {
|
this.prepAndBuildRecvArgs( (err, args) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.executeExternalProtocolHandler(args, err => {
|
this.executeExternalProtocolHandler(args, err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuResult() {
|
getMenuResult() {
|
||||||
if(this.isSending()) {
|
if(this.isSending()) {
|
||||||
return { sentFileIds : this.sentFileIds };
|
return { sentFileIds : this.sentFileIds };
|
||||||
} else {
|
} else {
|
||||||
return { recvFilePaths : this.recvFilePaths };
|
return { recvFilePaths : this.recvFilePaths };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSendStats(cb) {
|
updateSendStats(cb) {
|
||||||
let downloadBytes = 0;
|
let downloadBytes = 0;
|
||||||
let downloadCount = 0;
|
let downloadCount = 0;
|
||||||
let fileIds = [];
|
let fileIds = [];
|
||||||
|
|
||||||
async.each(this.sendQueue, (queueItem, next) => {
|
async.each(this.sendQueue, (queueItem, next) => {
|
||||||
if(!queueItem.sent) {
|
if(!queueItem.sent) {
|
||||||
return next(null);
|
return next(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(queueItem.fileId) {
|
if(queueItem.fileId) {
|
||||||
fileIds.push(queueItem.fileId);
|
fileIds.push(queueItem.fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isNumber(queueItem.byteSize)) {
|
if(_.isNumber(queueItem.byteSize)) {
|
||||||
downloadCount += 1;
|
downloadCount += 1;
|
||||||
downloadBytes += queueItem.byteSize;
|
downloadBytes += queueItem.byteSize;
|
||||||
return next(null);
|
return next(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// we just have a path - figure it out
|
// we just have a path - figure it out
|
||||||
fs.stat(queueItem.path, (err, stats) => {
|
fs.stat(queueItem.path, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
this.client.log.warn( { error : err.message, path : queueItem.path }, 'File stat failed' );
|
this.client.log.warn( { error : err.message, path : queueItem.path }, 'File stat failed' );
|
||||||
} else {
|
} else {
|
||||||
downloadCount += 1;
|
downloadCount += 1;
|
||||||
downloadBytes += stats.size;
|
downloadBytes += stats.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(null);
|
return next(null);
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
// All stats/meta currently updated via fire & forget - if this is ever a issue, we can wait for callbacks
|
// 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_count', downloadCount);
|
||||||
StatLog.incrementUserStat(this.client.user, 'dl_total_bytes', downloadBytes);
|
StatLog.incrementUserStat(this.client.user, 'dl_total_bytes', downloadBytes);
|
||||||
StatLog.incrementSystemStat('dl_total_count', downloadCount);
|
StatLog.incrementSystemStat('dl_total_count', downloadCount);
|
||||||
StatLog.incrementSystemStat('dl_total_bytes', downloadBytes);
|
StatLog.incrementSystemStat('dl_total_bytes', downloadBytes);
|
||||||
|
|
||||||
fileIds.forEach(fileId => {
|
fileIds.forEach(fileId => {
|
||||||
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
|
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateRecvStats(cb) {
|
updateRecvStats(cb) {
|
||||||
let uploadBytes = 0;
|
let uploadBytes = 0;
|
||||||
let uploadCount = 0;
|
let uploadCount = 0;
|
||||||
|
|
||||||
async.each(this.recvFilePaths, (filePath, next) => {
|
async.each(this.recvFilePaths, (filePath, next) => {
|
||||||
// we just have a path - figure it out
|
// we just have a path - figure it out
|
||||||
fs.stat(filePath, (err, stats) => {
|
fs.stat(filePath, (err, stats) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
this.client.log.warn( { error : err.message, path : filePath }, 'File stat failed' );
|
this.client.log.warn( { error : err.message, path : filePath }, 'File stat failed' );
|
||||||
} else {
|
} else {
|
||||||
uploadCount += 1;
|
uploadCount += 1;
|
||||||
uploadBytes += stats.size;
|
uploadBytes += stats.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
return next(null);
|
return next(null);
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
StatLog.incrementUserStat(this.client.user, 'ul_total_count', uploadCount);
|
StatLog.incrementUserStat(this.client.user, 'ul_total_count', uploadCount);
|
||||||
StatLog.incrementUserStat(this.client.user, 'ul_total_bytes', uploadBytes);
|
StatLog.incrementUserStat(this.client.user, 'ul_total_bytes', uploadBytes);
|
||||||
StatLog.incrementSystemStat('ul_total_count', uploadCount);
|
StatLog.incrementSystemStat('ul_total_count', uploadCount);
|
||||||
StatLog.incrementSystemStat('ul_total_bytes', uploadBytes);
|
StatLog.incrementSystemStat('ul_total_bytes', uploadBytes);
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// :TODO: break this up to send|recv
|
// :TODO: break this up to send|recv
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function validateConfig(callback) {
|
function validateConfig(callback) {
|
||||||
if(self.isSending()) {
|
if(self.isSending()) {
|
||||||
if(!Array.isArray(self.sendQueue)) {
|
if(!Array.isArray(self.sendQueue)) {
|
||||||
self.sendQueue = [ self.sendQueue ];
|
self.sendQueue = [ self.sendQueue ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function transferFiles(callback) {
|
function transferFiles(callback) {
|
||||||
if(self.isSending()) {
|
if(self.isSending()) {
|
||||||
self.sendFiles( err => {
|
self.sendFiles( err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sentFileIds = [];
|
const sentFileIds = [];
|
||||||
self.sendQueue.forEach(queueItem => {
|
self.sendQueue.forEach(queueItem => {
|
||||||
if(queueItem.sent && queueItem.fileId) {
|
if(queueItem.sent && queueItem.fileId) {
|
||||||
sentFileIds.push(queueItem.fileId);
|
sentFileIds.push(queueItem.fileId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(sentFileIds.length > 0) {
|
if(sentFileIds.length > 0) {
|
||||||
// remove items we sent from the D/L queue
|
// remove items we sent from the D/L queue
|
||||||
const dlQueue = new DownloadQueue(self.client);
|
const dlQueue = new DownloadQueue(self.client);
|
||||||
const dlFileEntries = dlQueue.removeItems(sentFileIds);
|
const dlFileEntries = dlQueue.removeItems(sentFileIds);
|
||||||
|
|
||||||
// fire event for downloaded entries
|
// fire event for downloaded entries
|
||||||
Events.emit(
|
Events.emit(
|
||||||
Events.getSystemEvents().UserDownload,
|
Events.getSystemEvents().UserDownload,
|
||||||
{
|
{
|
||||||
user : self.client.user,
|
user : self.client.user,
|
||||||
files : dlFileEntries
|
files : dlFileEntries
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
self.sentFileIds = sentFileIds;
|
self.sentFileIds = sentFileIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.recvFiles( err => {
|
self.recvFiles( err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function cleanupTempFiles(callback) {
|
function cleanupTempFiles(callback) {
|
||||||
temptmp.cleanup( paths => {
|
temptmp.cleanup( paths => {
|
||||||
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
|
||||||
});
|
});
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function updateUserAndSystemStats(callback) {
|
function updateUserAndSystemStats(callback) {
|
||||||
if(self.isSending()) {
|
if(self.isSending()) {
|
||||||
return self.updateSendStats(callback);
|
return self.updateSendStats(callback);
|
||||||
} else {
|
} else {
|
||||||
return self.updateRecvStats(callback);
|
return self.updateRecvStats(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.message }, 'File transfer error');
|
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');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'File transfer protocol selection',
|
name : 'File transfer protocol selection',
|
||||||
desc : 'Select protocol / method for file transfer',
|
desc : 'Select protocol / method for file transfer',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
protList : 1,
|
protList : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
|
exports.getModule = class FileTransferProtocolSelectModule extends MenuModule {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.config = this.menuConfig.config || {};
|
this.config = this.menuConfig.config || {};
|
||||||
|
|
||||||
if(options.extraArgs) {
|
if(options.extraArgs) {
|
||||||
if(options.extraArgs.direction) {
|
if(options.extraArgs.direction) {
|
||||||
this.config.direction = 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')) {
|
if(_.has(options, 'lastMenuResult.sentFileIds')) {
|
||||||
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
this.sentFileIds = options.lastMenuResult.sentFileIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.has(options, 'lastMenuResult.recvFilePaths')) {
|
if(_.has(options, 'lastMenuResult.recvFilePaths')) {
|
||||||
this.recvFilePaths = 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 = {
|
this.menuMethods = {
|
||||||
selectProtocol : (formData, extraArgs, cb) => {
|
selectProtocol : (formData, extraArgs, cb) => {
|
||||||
const protocol = this.protocols[formData.value.protocol];
|
const protocol = this.protocols[formData.value.protocol];
|
||||||
const finalExtraArgs = this.extraArgs || {};
|
const finalExtraArgs = this.extraArgs || {};
|
||||||
Object.assign(finalExtraArgs, { protocol : protocol.protocol, direction : this.config.direction }, extraArgs );
|
Object.assign(finalExtraArgs, { protocol : protocol.protocol, direction : this.config.direction }, extraArgs );
|
||||||
|
|
||||||
const modOpts = {
|
const modOpts = {
|
||||||
extraArgs : finalExtraArgs,
|
extraArgs : finalExtraArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
if('send' === this.config.direction) {
|
if('send' === this.config.direction) {
|
||||||
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
|
return this.gotoMenu(this.config.downloadFilesMenu || 'sendFilesToUser', modOpts, cb);
|
||||||
} else {
|
} else {
|
||||||
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb);
|
return this.gotoMenu(this.config.uploadFilesMenu || 'recvFilesFromUser', modOpts, cb);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuResult() {
|
getMenuResult() {
|
||||||
if(this.sentFileIds) {
|
if(this.sentFileIds) {
|
||||||
return { sentFileIds : this.sentFileIds };
|
return { sentFileIds : this.sentFileIds };
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.recvFilePaths) {
|
if(this.recvFilePaths) {
|
||||||
return { recvFilePaths : this.recvFilePaths };
|
return { recvFilePaths : this.recvFilePaths };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
if(this.sentFileIds || this.recvFilePaths) {
|
if(this.sentFileIds || this.recvFilePaths) {
|
||||||
// nothing to do here; move along (we're just falling through)
|
// nothing to do here; move along (we're just falling through)
|
||||||
this.prevMenu();
|
this.prevMenu();
|
||||||
} else {
|
} else {
|
||||||
super.initSequence();
|
super.initSequence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.menu
|
mciMap : mciData.menu
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
},
|
},
|
||||||
function populateList(callback) {
|
function populateList(callback) {
|
||||||
const protListView = vc.getView(MciViewIds.protList);
|
const protListView = vc.getView(MciViewIds.protList);
|
||||||
|
|
||||||
const protListFormat = self.config.protListFormat || '{name}';
|
const protListFormat = self.config.protListFormat || '{name}';
|
||||||
const protListFocusFormat = self.config.protListFocusFormat || protListFormat;
|
const protListFocusFormat = self.config.protListFocusFormat || protListFormat;
|
||||||
|
|
||||||
protListView.setItems(self.protocols.map(p => stringFormat(protListFormat, p) ) );
|
protListView.setItems(self.protocols.map(p => stringFormat(protListFormat, p) ) );
|
||||||
protListView.setFocusItems(self.protocols.map(p => stringFormat(protListFocusFormat, p) ) );
|
protListView.setFocusItems(self.protocols.map(p => stringFormat(protListFocusFormat, p) ) );
|
||||||
|
|
||||||
protListView.redraw();
|
protListView.redraw();
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
loadAvailProtocols() {
|
loadAvailProtocols() {
|
||||||
this.protocols = _.map(Config().fileTransferProtocols, (protInfo, protocol) => {
|
this.protocols = _.map(Config().fileTransferProtocols, (protInfo, protocol) => {
|
||||||
return {
|
return {
|
||||||
protocol : protocol,
|
protocol : protocol,
|
||||||
name : protInfo.name,
|
name : protInfo.name,
|
||||||
hasBatch : _.has(protInfo, 'external.recvArgs'),
|
hasBatch : _.has(protInfo, 'external.recvArgs'),
|
||||||
hasNonBatch : _.has(protInfo, 'external.recvArgsNonBatch'),
|
hasNonBatch : _.has(protInfo, 'external.recvArgsNonBatch'),
|
||||||
sort : protInfo.sort,
|
sort : protInfo.sort,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Filter out batch vs non-batch only protocols
|
// Filter out batch vs non-batch only protocols
|
||||||
if(this.extraArgs.recvFileName) { // non-batch aka non-blind
|
if(this.extraArgs.recvFileName) { // non-batch aka non-blind
|
||||||
this.protocols = this.protocols.filter( prot => prot.hasNonBatch );
|
this.protocols = this.protocols.filter( prot => prot.hasNonBatch );
|
||||||
} else {
|
} else {
|
||||||
this.protocols = this.protocols.filter( prot => prot.hasBatch );
|
this.protocols = this.protocols.filter( prot => prot.hasBatch );
|
||||||
}
|
}
|
||||||
|
|
||||||
// natural sort taking explicit orders into consideration
|
// natural sort taking explicit orders into consideration
|
||||||
this.protocols.sort( (a, b) => {
|
this.protocols.sort( (a, b) => {
|
||||||
if(_.isNumber(a.sort) && _.isNumber(b.sort)) {
|
if(_.isNumber(a.sort) && _.isNumber(b.sort)) {
|
||||||
return a.sort - b.sort;
|
return a.sort - b.sort;
|
||||||
} else {
|
} else {
|
||||||
return a.name.localeCompare(b.name, { sensitivity : false, numeric : true } );
|
return a.name.localeCompare(b.name, { sensitivity : false, numeric : true } );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,59 +14,59 @@ exports.copyFileWithCollisionHandling = copyFileWithCollisionHandling;
|
||||||
exports.pathWithTerminatingSeparator = pathWithTerminatingSeparator;
|
exports.pathWithTerminatingSeparator = pathWithTerminatingSeparator;
|
||||||
|
|
||||||
function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
|
function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
|
||||||
operation = operation || 'copy';
|
operation = operation || 'copy';
|
||||||
const dstPath = paths.dirname(dst);
|
const dstPath = paths.dirname(dst);
|
||||||
const dstFileExt = paths.extname(dst);
|
const dstFileExt = paths.extname(dst);
|
||||||
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
const dstFileSuffix = paths.basename(dst, dstFileExt);
|
||||||
|
|
||||||
EnigAssert('move' === operation || 'copy' === operation);
|
EnigAssert('move' === operation || 'copy' === operation);
|
||||||
|
|
||||||
let renameIndex = 0;
|
let renameIndex = 0;
|
||||||
let opOk = false;
|
let opOk = false;
|
||||||
let tryDstPath;
|
let tryDstPath;
|
||||||
|
|
||||||
function tryOperation(src, dst, callback) {
|
function tryOperation(src, dst, callback) {
|
||||||
if('move' === operation) {
|
if('move' === operation) {
|
||||||
fse.move(src, tryDstPath, err => {
|
fse.move(src, tryDstPath, err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
} else if('copy' === operation) {
|
} else if('copy' === operation) {
|
||||||
fse.copy(src, tryDstPath, { overwrite : false, errorOnExist : true }, err => {
|
fse.copy(src, tryDstPath, { overwrite : false, errorOnExist : true }, err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async.until(
|
async.until(
|
||||||
() => opOk, // until moved OK
|
() => opOk, // until moved OK
|
||||||
(cb) => {
|
(cb) => {
|
||||||
if(0 === renameIndex) {
|
if(0 === renameIndex) {
|
||||||
// try originally supplied path first
|
// try originally supplied path first
|
||||||
tryDstPath = dst;
|
tryDstPath = dst;
|
||||||
} else {
|
} else {
|
||||||
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
tryOperation(src, tryDstPath, err => {
|
tryOperation(src, tryDstPath, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// for some reason fs-extra copy doesn't pass err.code
|
// for some reason fs-extra copy doesn't pass err.code
|
||||||
// :TODO: this is dangerous: submit a PR to fs-extra to set EEXIST
|
// :TODO: this is dangerous: submit a PR to fs-extra to set EEXIST
|
||||||
if('EEXIST' === err.code || 'copy' === operation) {
|
if('EEXIST' === err.code || 'copy' === operation) {
|
||||||
renameIndex += 1;
|
renameIndex += 1;
|
||||||
return cb(null); // keep trying
|
return cb(null); // keep trying
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
opOk = true;
|
opOk = true;
|
||||||
return cb(null, tryDstPath);
|
return cb(null, tryDstPath);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
(err, finalPath) => {
|
(err, finalPath) => {
|
||||||
return cb(err, finalPath);
|
return cb(err, finalPath);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -74,16 +74,16 @@ function moveOrCopyFileWithCollisionHandling(src, dst, operation, cb) {
|
||||||
// in the case of collisions.
|
// in the case of collisions.
|
||||||
//
|
//
|
||||||
function moveFileWithCollisionHandling(src, dst, cb) {
|
function moveFileWithCollisionHandling(src, dst, cb) {
|
||||||
return moveOrCopyFileWithCollisionHandling(src, dst, 'move', cb);
|
return moveOrCopyFileWithCollisionHandling(src, dst, 'move', cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyFileWithCollisionHandling(src, dst, cb) {
|
function copyFileWithCollisionHandling(src, dst, cb) {
|
||||||
return moveOrCopyFileWithCollisionHandling(src, dst, 'copy', cb);
|
return moveOrCopyFileWithCollisionHandling(src, dst, 'copy', cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pathWithTerminatingSeparator(path) {
|
function pathWithTerminatingSeparator(path) {
|
||||||
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
if(path && paths.sep !== path.charAt(path.length - 1)) {
|
||||||
path = path + paths.sep;
|
path = path + paths.sep;
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,46 +5,46 @@ let _ = require('lodash');
|
||||||
|
|
||||||
// FNV-1a based on work here: https://github.com/wiedi/node-fnv
|
// FNV-1a based on work here: https://github.com/wiedi/node-fnv
|
||||||
module.exports = class FNV1a {
|
module.exports = class FNV1a {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.hash = 0x811c9dc5;
|
this.hash = 0x811c9dc5;
|
||||||
|
|
||||||
if(!_.isUndefined(data)) {
|
if(!_.isUndefined(data)) {
|
||||||
this.update(data);
|
this.update(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data) {
|
update(data) {
|
||||||
if(_.isNumber(data)) {
|
if(_.isNumber(data)) {
|
||||||
data = data.toString();
|
data = data.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.isString(data)) {
|
if(_.isString(data)) {
|
||||||
data = Buffer.from(data);
|
data = Buffer.from(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Buffer.isBuffer(data)) {
|
if(!Buffer.isBuffer(data)) {
|
||||||
throw new Error('data must be String or Buffer!');
|
throw new Error('data must be String or Buffer!');
|
||||||
}
|
}
|
||||||
|
|
||||||
for(let b of data) {
|
for(let b of data) {
|
||||||
this.hash = this.hash ^ b;
|
this.hash = this.hash ^ b;
|
||||||
this.hash +=
|
this.hash +=
|
||||||
(this.hash << 24) + (this.hash << 8) + (this.hash << 7) +
|
(this.hash << 24) + (this.hash << 8) + (this.hash << 7) +
|
||||||
(this.hash << 4) + (this.hash << 1);
|
(this.hash << 4) + (this.hash << 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
digest(encoding) {
|
digest(encoding) {
|
||||||
encoding = encoding || 'binary';
|
encoding = encoding || 'binary';
|
||||||
const buf = Buffer.alloc(4);
|
const buf = Buffer.alloc(4);
|
||||||
buf.writeInt32BE(this.hash & 0xffffffff, 0);
|
buf.writeInt32BE(this.hash & 0xffffffff, 0);
|
||||||
return buf.toString(encoding);
|
return buf.toString(encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
get value() {
|
get value() {
|
||||||
return this.hash & 0xffffffff;
|
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;
|
const FTN_PATTERN_REGEXP = /^([0-9*]+:)?([0-9*]+)(\/[0-9*]+)?(\.[0-9*]+)?(@[a-z0-9\-.*]+)?$/i;
|
||||||
|
|
||||||
module.exports = class Address {
|
module.exports = class Address {
|
||||||
constructor(addr) {
|
constructor(addr) {
|
||||||
if(addr) {
|
if(addr) {
|
||||||
if(_.isObject(addr)) {
|
if(_.isObject(addr)) {
|
||||||
Object.assign(this, addr);
|
Object.assign(this, addr);
|
||||||
} else if(_.isString(addr)) {
|
} else if(_.isString(addr)) {
|
||||||
const temp = Address.fromString(addr);
|
const temp = Address.fromString(addr);
|
||||||
if(temp) {
|
if(temp) {
|
||||||
Object.assign(this, temp);
|
Object.assign(this, temp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static isValidAddress(addr) {
|
static isValidAddress(addr) {
|
||||||
return addr && addr.isValid();
|
return addr && addr.isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid() {
|
isValid() {
|
||||||
// FTN address is valid if we have at least a net/node
|
// FTN address is valid if we have at least a net/node
|
||||||
return _.isNumber(this.net) && _.isNumber(this.node);
|
return _.isNumber(this.net) && _.isNumber(this.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
isEqual(other) {
|
isEqual(other) {
|
||||||
if(_.isString(other)) {
|
if(_.isString(other)) {
|
||||||
other = Address.fromString(other);
|
other = Address.fromString(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
this.net === other.net &&
|
this.net === other.net &&
|
||||||
this.node === other.node &&
|
this.node === other.node &&
|
||||||
this.zone === other.zone &&
|
this.zone === other.zone &&
|
||||||
this.point === other.point &&
|
this.point === other.point &&
|
||||||
this.domain === other.domain
|
this.domain === other.domain
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMatchAddr(pattern) {
|
getMatchAddr(pattern) {
|
||||||
const m = FTN_PATTERN_REGEXP.exec(pattern);
|
const m = FTN_PATTERN_REGEXP.exec(pattern);
|
||||||
if(m) {
|
if(m) {
|
||||||
let addr = { };
|
let addr = { };
|
||||||
|
|
||||||
if(m[1]) {
|
if(m[1]) {
|
||||||
addr.zone = m[1].slice(0, -1);
|
addr.zone = m[1].slice(0, -1);
|
||||||
if('*' !== addr.zone) {
|
if('*' !== addr.zone) {
|
||||||
addr.zone = parseInt(addr.zone);
|
addr.zone = parseInt(addr.zone);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr.zone = '*';
|
addr.zone = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m[2]) {
|
if(m[2]) {
|
||||||
addr.net = m[2];
|
addr.net = m[2];
|
||||||
if('*' !== addr.net) {
|
if('*' !== addr.net) {
|
||||||
addr.net = parseInt(addr.net);
|
addr.net = parseInt(addr.net);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr.net = '*';
|
addr.net = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m[3]) {
|
if(m[3]) {
|
||||||
addr.node = m[3].substr(1);
|
addr.node = m[3].substr(1);
|
||||||
if('*' !== addr.node) {
|
if('*' !== addr.node) {
|
||||||
addr.node = parseInt(addr.node);
|
addr.node = parseInt(addr.node);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr.node = '*';
|
addr.node = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m[4]) {
|
if(m[4]) {
|
||||||
addr.point = m[4].substr(1);
|
addr.point = m[4].substr(1);
|
||||||
if('*' !== addr.point) {
|
if('*' !== addr.point) {
|
||||||
addr.point = parseInt(addr.point);
|
addr.point = parseInt(addr.point);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr.point = '*';
|
addr.point = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m[5]) {
|
if(m[5]) {
|
||||||
addr.domain = m[5].substr(1);
|
addr.domain = m[5].substr(1);
|
||||||
} else {
|
} else {
|
||||||
addr.domain = '*';
|
addr.domain = '*';
|
||||||
}
|
}
|
||||||
|
|
||||||
return addr;
|
return addr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
getMatchScore(pattern) {
|
getMatchScore(pattern) {
|
||||||
let score = 0;
|
let score = 0;
|
||||||
const addr = this.getMatchAddr(pattern);
|
const addr = this.getMatchAddr(pattern);
|
||||||
|
@ -116,92 +116,92 @@ module.exports = class Address {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
isPatternMatch(pattern) {
|
isPatternMatch(pattern) {
|
||||||
const addr = this.getMatchAddr(pattern);
|
const addr = this.getMatchAddr(pattern);
|
||||||
if(addr) {
|
if(addr) {
|
||||||
return (
|
return (
|
||||||
('*' === addr.net || this.net === addr.net) &&
|
('*' === addr.net || this.net === addr.net) &&
|
||||||
('*' === addr.node || this.node === addr.node) &&
|
('*' === addr.node || this.node === addr.node) &&
|
||||||
('*' === addr.zone || this.zone === addr.zone) &&
|
('*' === addr.zone || this.zone === addr.zone) &&
|
||||||
('*' === addr.point || this.point === addr.point) &&
|
('*' === addr.point || this.point === addr.point) &&
|
||||||
('*' === addr.domain || this.domain === addr.domain)
|
('*' === addr.domain || this.domain === addr.domain)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromString(addrStr) {
|
static fromString(addrStr) {
|
||||||
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
const m = FTN_ADDRESS_REGEXP.exec(addrStr);
|
||||||
|
|
||||||
if(m) {
|
if(m) {
|
||||||
// start with a 2D
|
// start with a 2D
|
||||||
let addr = {
|
let addr = {
|
||||||
net : parseInt(m[2]),
|
net : parseInt(m[2]),
|
||||||
node : parseInt(m[3].substr(1)),
|
node : parseInt(m[3].substr(1)),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 3D: Addition of zone if present
|
// 3D: Addition of zone if present
|
||||||
if(m[1]) {
|
if(m[1]) {
|
||||||
addr.zone = parseInt(m[1].slice(0, -1));
|
addr.zone = parseInt(m[1].slice(0, -1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4D if optional point is present
|
// 4D if optional point is present
|
||||||
if(m[4]) {
|
if(m[4]) {
|
||||||
addr.point = parseInt(m[4].substr(1));
|
addr.point = parseInt(m[4].substr(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5D with @domain
|
// 5D with @domain
|
||||||
if(m[5]) {
|
if(m[5]) {
|
||||||
addr.domain = m[5].substr(1);
|
addr.domain = m[5].substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Address(addr);
|
return new Address(addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(dimensions) {
|
toString(dimensions) {
|
||||||
dimensions = dimensions || '5D';
|
dimensions = dimensions || '5D';
|
||||||
|
|
||||||
let addrStr = `${this.zone}:${this.net}`;
|
let addrStr = `${this.zone}:${this.net}`;
|
||||||
|
|
||||||
// allow for e.g. '4D' or 5
|
// allow for e.g. '4D' or 5
|
||||||
const dim = parseInt(dimensions.toString()[0]);
|
const dim = parseInt(dimensions.toString()[0]);
|
||||||
|
|
||||||
if(dim >= 3) {
|
if(dim >= 3) {
|
||||||
addrStr += `/${this.node}`;
|
addrStr += `/${this.node}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// missing & .0 are equiv for point
|
// missing & .0 are equiv for point
|
||||||
if(dim >= 4 && this.point) {
|
if(dim >= 4 && this.point) {
|
||||||
addrStr += `.${this.point}`;
|
addrStr += `.${this.point}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(5 === dim && this.domain) {
|
if(5 === dim && this.domain) {
|
||||||
addrStr += `@${this.domain.toLowerCase()}`;
|
addrStr += `@${this.domain.toLowerCase()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addrStr;
|
return addrStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static getComparator() {
|
static getComparator() {
|
||||||
return function(left, right) {
|
return function(left, right) {
|
||||||
let c = (left.zone || 0) - (right.zone || 0);
|
let c = (left.zone || 0) - (right.zone || 0);
|
||||||
if(0 !== c) {
|
if(0 !== c) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (left.net || 0) - (right.net || 0);
|
c = (left.net || 0) - (right.net || 0);
|
||||||
if(0 !== c) {
|
if(0 !== c) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (left.node || 0) - (right.node || 0);
|
c = (left.node || 0) - (right.node || 0);
|
||||||
if(0 !== c) {
|
if(0 !== c) {
|
||||||
return 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
|
// See list here: https://github.com/Mithgol/node-fidonet-jam
|
||||||
|
|
||||||
function stringToNullPaddedBuffer(s, bufLen) {
|
function stringToNullPaddedBuffer(s, bufLen) {
|
||||||
let buffer = Buffer.alloc(bufLen);
|
let buffer = Buffer.alloc(bufLen);
|
||||||
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
|
let enc = iconv.encode(s, 'CP437').slice(0, bufLen);
|
||||||
for(let i = 0; i < enc.length; ++i) {
|
for(let i = 0; i < enc.length; ++i) {
|
||||||
buffer[i] = enc[i];
|
buffer[i] = enc[i];
|
||||||
}
|
}
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -58,45 +58,45 @@ function stringToNullPaddedBuffer(s, bufLen) {
|
||||||
//
|
//
|
||||||
// :TODO: Name the next couple methods better - for FTN *packets*
|
// :TODO: Name the next couple methods better - for FTN *packets*
|
||||||
function getDateFromFtnDateTime(dateTime) {
|
function getDateFromFtnDateTime(dateTime) {
|
||||||
//
|
//
|
||||||
// Examples seen in the wild (Working):
|
// Examples seen in the wild (Working):
|
||||||
// "12 Sep 88 18:17:59"
|
// "12 Sep 88 18:17:59"
|
||||||
// "Tue 01 Jan 80 00:00"
|
// "Tue 01 Jan 80 00:00"
|
||||||
// "27 Feb 15 00:00:03"
|
// "27 Feb 15 00:00:03"
|
||||||
//
|
//
|
||||||
// :TODO: Use moment.js here
|
// :TODO: Use moment.js here
|
||||||
return moment(Date.parse(dateTime)); // Date.parse() allows funky formats
|
return moment(Date.parse(dateTime)); // Date.parse() allows funky formats
|
||||||
// return (new Date(Date.parse(dateTime))).toISOString();
|
// return (new Date(Date.parse(dateTime))).toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDateTimeString(m) {
|
function getDateTimeString(m) {
|
||||||
//
|
//
|
||||||
// From http://ftsc.org/docs/fts-0001.016:
|
// From http://ftsc.org/docs/fts-0001.016:
|
||||||
// DateTime = (* a character string 20 characters long *)
|
// DateTime = (* a character string 20 characters long *)
|
||||||
// (* 01 Jan 86 02:34:56 *)
|
// (* 01 Jan 86 02:34:56 *)
|
||||||
// DayOfMonth " " Month " " Year " "
|
// DayOfMonth " " Month " " Year " "
|
||||||
// " " HH ":" MM ":" SS
|
// " " HH ":" MM ":" SS
|
||||||
// Null
|
// Null
|
||||||
//
|
//
|
||||||
// DayOfMonth = "01" | "02" | "03" | ... | "31" (* Fido 0 fills *)
|
// DayOfMonth = "01" | "02" | "03" | ... | "31" (* Fido 0 fills *)
|
||||||
// Month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
|
// Month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
|
||||||
// "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
|
// "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
|
||||||
// Year = "01" | "02" | .. | "85" | "86" | ... | "99" | "00"
|
// Year = "01" | "02" | .. | "85" | "86" | ... | "99" | "00"
|
||||||
// HH = "00" | .. | "23"
|
// HH = "00" | .. | "23"
|
||||||
// MM = "00" | .. | "59"
|
// MM = "00" | .. | "59"
|
||||||
// SS = "00" | .. | "59"
|
// SS = "00" | .. | "59"
|
||||||
//
|
//
|
||||||
if(!moment.isMoment(m)) {
|
if(!moment.isMoment(m)) {
|
||||||
m = moment(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) {
|
function getMessageSerialNumber(messageId) {
|
||||||
const msSinceEnigmaEpoc = (Date.now() - Date.UTC(2016, 1, 1));
|
const msSinceEnigmaEpoc = (Date.now() - Date.UTC(2016, 1, 1));
|
||||||
const hash = Math.abs(new FNV1a(msSinceEnigmaEpoc + messageId).value).toString(16);
|
const hash = Math.abs(new FNV1a(msSinceEnigmaEpoc + messageId).value).toString(16);
|
||||||
return `00000000${hash}`.substr(-8);
|
return `00000000${hash}`.substr(-8);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -143,11 +143,11 @@ function getMessageSerialNumber(messageId) {
|
||||||
// format, but that will only help when using newer Mystic versions.
|
// format, but that will only help when using newer Mystic versions.
|
||||||
//
|
//
|
||||||
function getMessageIdentifier(message, address, isNetMail = false) {
|
function getMessageIdentifier(message, address, isNetMail = false) {
|
||||||
const addrStr = new Address(address).toString('5D');
|
const addrStr = new Address(address).toString('5D');
|
||||||
return isNetMail ?
|
return isNetMail ?
|
||||||
`${addrStr} ${getMessageSerialNumber(message.messageId)}` :
|
`${addrStr} ${getMessageSerialNumber(message.messageId)}` :
|
||||||
`${message.messageId}.${message.areaTag.toLowerCase()}@${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
|
// in which (<os>; <arch>; <nodeVer>) is used instead
|
||||||
//
|
//
|
||||||
function getProductIdentifier() {
|
function getProductIdentifier() {
|
||||||
const version = getCleanEnigmaVersion();
|
const version = getCleanEnigmaVersion();
|
||||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
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
|
// http://ftsc.org/docs/frl-1004.002
|
||||||
//
|
//
|
||||||
function getUTCTimeZoneOffset() {
|
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
|
// http://ftsc.org/docs/fsc-0032.001
|
||||||
//
|
//
|
||||||
function getQuotePrefix(name) {
|
function getQuotePrefix(name) {
|
||||||
let initials;
|
let initials;
|
||||||
|
|
||||||
const parts = name.split(' ');
|
const parts = name.split(' ');
|
||||||
if(parts.length > 1) {
|
if(parts.length > 1) {
|
||||||
// First & Last initials - (Bryan Ashby -> BA)
|
// First & Last initials - (Bryan Ashby -> BA)
|
||||||
initials = `${parts[0].slice(0, 1)}${parts[parts.length - 1].slice(0, 1)}`.toUpperCase();
|
initials = `${parts[0].slice(0, 1)}${parts[parts.length - 1].slice(0, 1)}`.toUpperCase();
|
||||||
} else {
|
} else {
|
||||||
// Just use the first two - (NuSkooler -> Nu)
|
// Just use the first two - (NuSkooler -> Nu)
|
||||||
initials = _.capitalize(name.slice(0, 2));
|
initials = _.capitalize(name.slice(0, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ` ${initials}> `;
|
return ` ${initials}> `;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -198,18 +198,18 @@ function getQuotePrefix(name) {
|
||||||
// http://ftsc.org/docs/fts-0004.001
|
// http://ftsc.org/docs/fts-0004.001
|
||||||
//
|
//
|
||||||
function getOrigin(address) {
|
function getOrigin(address) {
|
||||||
const config = Config();
|
const config = Config();
|
||||||
const origin = _.has(config, 'messageNetworks.originLine') ?
|
const origin = _.has(config, 'messageNetworks.originLine') ?
|
||||||
config.messageNetworks.originLine :
|
config.messageNetworks.originLine :
|
||||||
config.general.boardName;
|
config.general.boardName;
|
||||||
|
|
||||||
const addrStr = new Address(address).toString('5D');
|
const addrStr = new Address(address).toString('5D');
|
||||||
return ` * Origin: ${origin} (${addrStr})`;
|
return ` * Origin: ${origin} (${addrStr})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTearLine() {
|
function getTearLine() {
|
||||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
||||||
return `--- ENiGMA 1/2 v${packageJson.version} (${os.platform()}; ${os.arch()}; ${nodeVer})`;
|
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
|
// http://ftsc.org/docs/frl-1005.001
|
||||||
//
|
//
|
||||||
function getVia(address) {
|
function getVia(address) {
|
||||||
/*
|
/*
|
||||||
FRL-1005.001 states teh following format:
|
FRL-1005.001 states teh following format:
|
||||||
|
|
||||||
^AVia: <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone]
|
^AVia: <FTN Address> @YYYYMMDD.HHMMSS[.Precise][.Time Zone]
|
||||||
<Program Name> <Version> [Serial Number]<CR>
|
<Program Name> <Version> [Serial Number]<CR>
|
||||||
*/
|
*/
|
||||||
const addrStr = new Address(address).toString('5D');
|
const addrStr = new Address(address).toString('5D');
|
||||||
const dateTime = moment().utc().format('YYYYMMDD.HHmmSS.SSSS.UTC');
|
const dateTime = moment().utc().format('YYYYMMDD.HHmmSS.SSSS.UTC');
|
||||||
const version = getCleanEnigmaVersion();
|
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
|
// http://retro.fidoweb.ru/docs/index=ftsc&doc=FTS-4001&enc=mac
|
||||||
//
|
//
|
||||||
function getIntl(toAddress, fromAddress) {
|
function getIntl(toAddress, fromAddress) {
|
||||||
//
|
//
|
||||||
// INTL differs from 'standard' kludges in that there is no ':' after "INTL"
|
// INTL differs from 'standard' kludges in that there is no ':' after "INTL"
|
||||||
//
|
//
|
||||||
// "<SOH>"INTL "<destination address>" "<origin address><CR>"
|
// "<SOH>"INTL "<destination address>" "<origin address><CR>"
|
||||||
// "...These addresses shall be given on the form <zone>:<net>/<node>"
|
// "...These addresses shall be given on the form <zone>:<net>/<node>"
|
||||||
//
|
//
|
||||||
return `${toAddress.toString('3D')} ${fromAddress.toString('3D')}`;
|
return `${toAddress.toString('3D')} ${fromAddress.toString('3D')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAbbreviatedNetNodeList(netNodes) {
|
function getAbbreviatedNetNodeList(netNodes) {
|
||||||
let abbrList = '';
|
let abbrList = '';
|
||||||
let currNet;
|
let currNet;
|
||||||
netNodes.forEach(netNode => {
|
netNodes.forEach(netNode => {
|
||||||
if(_.isString(netNode)) {
|
if(_.isString(netNode)) {
|
||||||
netNode = Address.fromString(netNode);
|
netNode = Address.fromString(netNode);
|
||||||
}
|
}
|
||||||
if(currNet !== netNode.net) {
|
if(currNet !== netNode.net) {
|
||||||
abbrList += `${netNode.net}/`;
|
abbrList += `${netNode.net}/`;
|
||||||
currNet = netNode.net;
|
currNet = netNode.net;
|
||||||
}
|
}
|
||||||
abbrList += `${netNode.node} `;
|
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
|
// Parse an abbreviated net/node list commonly used for SEEN-BY and PATH
|
||||||
//
|
//
|
||||||
function parseAbbreviatedNetNodeList(netNodes) {
|
function parseAbbreviatedNetNodeList(netNodes) {
|
||||||
const re = /([0-9]+)\/([0-9]+)\s?|([0-9]+)\s?/g;
|
const re = /([0-9]+)\/([0-9]+)\s?|([0-9]+)\s?/g;
|
||||||
let net;
|
let net;
|
||||||
let m;
|
let m;
|
||||||
let results = [];
|
let results = [];
|
||||||
while(null !== (m = re.exec(netNodes))) {
|
while(null !== (m = re.exec(netNodes))) {
|
||||||
if(m[1] && m[2]) {
|
if(m[1] && m[2]) {
|
||||||
net = parseInt(m[1]);
|
net = parseInt(m[1]);
|
||||||
results.push(new Address( { net : net, node : parseInt(m[2]) } ));
|
results.push(new Address( { net : net, node : parseInt(m[2]) } ));
|
||||||
} else if(net) {
|
} else if(net) {
|
||||||
results.push(new Address( { net : net, node : parseInt(m[3]) } ));
|
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
|
// not the "SEEN-BY" prefix itself
|
||||||
//
|
//
|
||||||
function getUpdatedSeenByEntries(existingEntries, additions) {
|
function getUpdatedSeenByEntries(existingEntries, additions) {
|
||||||
/*
|
/*
|
||||||
From FTS-0004:
|
From FTS-0004:
|
||||||
|
|
||||||
"There can be many seen-by lines at the end of Conference
|
"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
|
this field is not put in place by other Echomail compatible
|
||||||
programs."
|
programs."
|
||||||
*/
|
*/
|
||||||
existingEntries = existingEntries || [];
|
existingEntries = existingEntries || [];
|
||||||
if(!_.isArray(existingEntries)) {
|
if(!_.isArray(existingEntries)) {
|
||||||
existingEntries = [ existingEntries ];
|
existingEntries = [ existingEntries ];
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isString(additions)) {
|
if(!_.isString(additions)) {
|
||||||
additions = parseAbbreviatedNetNodeList(getAbbreviatedNetNodeList(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
|
// 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
|
// :TODO: we should at least try and update what is already there in a smart way
|
||||||
existingEntries.push(getAbbreviatedNetNodeList(additions));
|
existingEntries.push(getAbbreviatedNetNodeList(additions));
|
||||||
return existingEntries;
|
return existingEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUpdatedPathEntries(existingEntries, localAddress) {
|
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 || [];
|
existingEntries = existingEntries || [];
|
||||||
if(!_.isArray(existingEntries)) {
|
if(!_.isArray(existingEntries)) {
|
||||||
existingEntries = [ existingEntries ];
|
existingEntries = [ existingEntries ];
|
||||||
}
|
}
|
||||||
|
|
||||||
existingEntries.push(getAbbreviatedNetNodeList(
|
existingEntries.push(getAbbreviatedNetNodeList(
|
||||||
parseAbbreviatedNetNodeList(localAddress)));
|
parseAbbreviatedNetNodeList(localAddress)));
|
||||||
|
|
||||||
return existingEntries;
|
return existingEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -354,71 +354,71 @@ function getUpdatedPathEntries(existingEntries, localAddress) {
|
||||||
// http://ftsc.org/docs/fts-5003.001
|
// http://ftsc.org/docs/fts-5003.001
|
||||||
//
|
//
|
||||||
const ENCODING_TO_FTS_5003_001_CHARS = {
|
const ENCODING_TO_FTS_5003_001_CHARS = {
|
||||||
// level 1 - generally should not be used
|
// level 1 - generally should not be used
|
||||||
ascii : [ 'ASCII', 1 ],
|
ascii : [ 'ASCII', 1 ],
|
||||||
'us-ascii' : [ 'ASCII', 1 ],
|
'us-ascii' : [ 'ASCII', 1 ],
|
||||||
|
|
||||||
// level 2 - 8 bit, ASCII based
|
// level 2 - 8 bit, ASCII based
|
||||||
cp437 : [ 'CP437', 2 ],
|
cp437 : [ 'CP437', 2 ],
|
||||||
cp850 : [ 'CP850', 2 ],
|
cp850 : [ 'CP850', 2 ],
|
||||||
|
|
||||||
// level 3 - reserved
|
// level 3 - reserved
|
||||||
|
|
||||||
// level 4
|
// level 4
|
||||||
utf8 : [ 'UTF-8', 4 ],
|
utf8 : [ 'UTF-8', 4 ],
|
||||||
'utf-8' : [ 'UTF-8', 4 ],
|
'utf-8' : [ 'UTF-8', 4 ],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function getCharacterSetIdentifierByEncoding(encodingName) {
|
function getCharacterSetIdentifierByEncoding(encodingName) {
|
||||||
const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()];
|
const value = ENCODING_TO_FTS_5003_001_CHARS[encodingName.toLowerCase()];
|
||||||
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
|
return value ? `${value[0]} ${value[1]}` : encodingName.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEncodingFromCharacterSetIdentifier(chrs) {
|
function getEncodingFromCharacterSetIdentifier(chrs) {
|
||||||
const ident = chrs.split(' ')[0].toUpperCase();
|
const ident = chrs.split(' ')[0].toUpperCase();
|
||||||
|
|
||||||
// :TODO: fill in the rest!!!
|
// :TODO: fill in the rest!!!
|
||||||
return {
|
return {
|
||||||
// level 1
|
// level 1
|
||||||
'ASCII' : 'iso-646-1',
|
'ASCII' : 'iso-646-1',
|
||||||
'DUTCH' : 'iso-646',
|
'DUTCH' : 'iso-646',
|
||||||
'FINNISH' : 'iso-646-10',
|
'FINNISH' : 'iso-646-10',
|
||||||
'FRENCH' : 'iso-646',
|
'FRENCH' : 'iso-646',
|
||||||
'CANADIAN' : 'iso-646',
|
'CANADIAN' : 'iso-646',
|
||||||
'GERMAN' : 'iso-646',
|
'GERMAN' : 'iso-646',
|
||||||
'ITALIAN' : 'iso-646',
|
'ITALIAN' : 'iso-646',
|
||||||
'NORWEIG' : 'iso-646',
|
'NORWEIG' : 'iso-646',
|
||||||
'PORTU' : 'iso-646',
|
'PORTU' : 'iso-646',
|
||||||
'SPANISH' : 'iso-656',
|
'SPANISH' : 'iso-656',
|
||||||
'SWEDISH' : 'iso-646-10',
|
'SWEDISH' : 'iso-646-10',
|
||||||
'SWISS' : 'iso-646',
|
'SWISS' : 'iso-646',
|
||||||
'UK' : 'iso-646',
|
'UK' : 'iso-646',
|
||||||
'ISO-10' : 'iso-646-10',
|
'ISO-10' : 'iso-646-10',
|
||||||
|
|
||||||
// level 2
|
// level 2
|
||||||
'CP437' : 'cp437',
|
'CP437' : 'cp437',
|
||||||
'CP850' : 'cp850',
|
'CP850' : 'cp850',
|
||||||
'CP852' : 'cp852',
|
'CP852' : 'cp852',
|
||||||
'CP866' : 'cp866',
|
'CP866' : 'cp866',
|
||||||
'CP848' : 'cp848',
|
'CP848' : 'cp848',
|
||||||
'CP1250' : 'cp1250',
|
'CP1250' : 'cp1250',
|
||||||
'CP1251' : 'cp1251',
|
'CP1251' : 'cp1251',
|
||||||
'CP1252' : 'cp1252',
|
'CP1252' : 'cp1252',
|
||||||
'CP10000' : 'macroman',
|
'CP10000' : 'macroman',
|
||||||
'LATIN-1' : 'iso-8859-1',
|
'LATIN-1' : 'iso-8859-1',
|
||||||
'LATIN-2' : 'iso-8859-2',
|
'LATIN-2' : 'iso-8859-2',
|
||||||
'LATIN-5' : 'iso-8859-9',
|
'LATIN-5' : 'iso-8859-9',
|
||||||
'LATIN-9' : 'iso-8859-15',
|
'LATIN-9' : 'iso-8859-15',
|
||||||
|
|
||||||
// level 4
|
// level 4
|
||||||
'UTF-8' : 'utf8',
|
'UTF-8' : 'utf8',
|
||||||
|
|
||||||
// deprecated stuff
|
// deprecated stuff
|
||||||
'IBMPC' : 'cp1250', // :TODO: validate
|
'IBMPC' : 'cp1250', // :TODO: validate
|
||||||
'+7_FIDO' : 'cp866',
|
'+7_FIDO' : 'cp866',
|
||||||
'+7' : 'cp866',
|
'+7' : 'cp866',
|
||||||
'MAC' : 'macroman', // :TODO: validate
|
'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)
|
// :TODO: Update this to allow scrolling if number of items cannot fit in width (similar to VerticalMenuView)
|
||||||
|
|
||||||
function HorizontalMenuView(options) {
|
function HorizontalMenuView(options) {
|
||||||
options.cursor = options.cursor || 'hide';
|
options.cursor = options.cursor || 'hide';
|
||||||
|
|
||||||
if(!_.isNumber(options.itemSpacing)) {
|
if(!_.isNumber(options.itemSpacing)) {
|
||||||
options.itemSpacing = 1;
|
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() {
|
this.getSpacer = function() {
|
||||||
return new Array(self.itemSpacing + 1).join(' ');
|
return new Array(self.itemSpacing + 1).join(' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
this.performAutoScale = function() {
|
this.performAutoScale = function() {
|
||||||
if(self.autoScale.width) {
|
if(self.autoScale.width) {
|
||||||
var spacer = self.getSpacer();
|
var spacer = self.getSpacer();
|
||||||
var width = self.items.join(spacer).length + (spacer.length * 2);
|
var width = self.items.join(spacer).length + (spacer.length * 2);
|
||||||
assert(width <= self.client.term.termWidth - self.position.col);
|
assert(width <= self.client.term.termWidth - self.position.col);
|
||||||
self.dimens.width = width;
|
self.dimens.width = width;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.performAutoScale();
|
this.performAutoScale();
|
||||||
|
|
||||||
this.cachePositions = function() {
|
this.cachePositions = function() {
|
||||||
if(this.positionCacheExpired) {
|
if(this.positionCacheExpired) {
|
||||||
var col = self.position.col;
|
var col = self.position.col;
|
||||||
var spacer = self.getSpacer();
|
var spacer = self.getSpacer();
|
||||||
|
|
||||||
for(var i = 0; i < self.items.length; ++i) {
|
for(var i = 0; i < self.items.length; ++i) {
|
||||||
self.items[i].col = col;
|
self.items[i].col = col;
|
||||||
col += spacer.length + self.items[i].text.length + spacer.length;
|
col += spacer.length + self.items[i].text.length + spacer.length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.positionCacheExpired = false;
|
this.positionCacheExpired = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.drawItem = function(index) {
|
this.drawItem = function(index) {
|
||||||
assert(!this.positionCacheExpired);
|
assert(!this.positionCacheExpired);
|
||||||
|
|
||||||
const item = self.items[index];
|
const item = self.items[index];
|
||||||
if(!item) {
|
if(!item) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let text;
|
let text;
|
||||||
let sgr;
|
let sgr;
|
||||||
if(item.focused && self.hasFocusItems()) {
|
if(item.focused && self.hasFocusItems()) {
|
||||||
const focusItem = self.focusItems[index];
|
const focusItem = self.focusItems[index];
|
||||||
text = focusItem ? focusItem.text : item.text;
|
text = focusItem ? focusItem.text : item.text;
|
||||||
sgr = '';
|
sgr = '';
|
||||||
} else if(this.complexItems) {
|
} else if(this.complexItems) {
|
||||||
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
|
text = pipeToAnsi(formatString(item.focused && this.focusItemFormat ? this.focusItemFormat : this.itemFormat, item));
|
||||||
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
sgr = this.focusItemFormat ? '' : (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
||||||
} else {
|
} else {
|
||||||
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
|
text = strUtil.stylizeString(item.text, item.focused ? self.focusTextStyle : self.textStyle);
|
||||||
sgr = (index === self.focusedItemIndex ? self.getFocusSGR() : self.getSGR());
|
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(
|
self.client.term.write(
|
||||||
`${goto(self.position.row, item.col)}${sgr}${strUtil.pad(text, drawWidth, self.fillChar, 'center')}`
|
`${goto(self.position.row, item.col)}${sgr}${strUtil.pad(text, drawWidth, self.fillChar, 'center')}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(HorizontalMenuView, MenuView);
|
require('util').inherits(HorizontalMenuView, MenuView);
|
||||||
|
|
||||||
HorizontalMenuView.prototype.setHeight = function(height) {
|
HorizontalMenuView.prototype.setHeight = function(height) {
|
||||||
height = parseInt(height, 10);
|
height = parseInt(height, 10);
|
||||||
assert(1 === height); // nothing else allowed here
|
assert(1 === height); // nothing else allowed here
|
||||||
HorizontalMenuView.super_.prototype.setHeight(this, height);
|
HorizontalMenuView.super_.prototype.setHeight(this, height);
|
||||||
};
|
};
|
||||||
|
|
||||||
HorizontalMenuView.prototype.redraw = function() {
|
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) {
|
for(var i = 0; i < this.items.length; ++i) {
|
||||||
this.items[i].focused = this.focusedItemIndex === i;
|
this.items[i].focused = this.focusedItemIndex === i;
|
||||||
this.drawItem(i);
|
this.drawItem(i);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
HorizontalMenuView.prototype.setPosition = function(pos) {
|
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.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.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() {
|
HorizontalMenuView.prototype.focusNext = function() {
|
||||||
if(this.items.length - 1 === this.focusedItemIndex) {
|
if(this.items.length - 1 === this.focusedItemIndex) {
|
||||||
this.focusedItemIndex = 0;
|
this.focusedItemIndex = 0;
|
||||||
} else {
|
} else {
|
||||||
this.focusedItemIndex++;
|
this.focusedItemIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
|
||||||
HorizontalMenuView.super_.prototype.focusNext.call(this);
|
HorizontalMenuView.super_.prototype.focusNext.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
HorizontalMenuView.prototype.focusPrevious = function() {
|
HorizontalMenuView.prototype.focusPrevious = function() {
|
||||||
|
|
||||||
if(0 === this.focusedItemIndex) {
|
if(0 === this.focusedItemIndex) {
|
||||||
this.focusedItemIndex = this.items.length - 1;
|
this.focusedItemIndex = this.items.length - 1;
|
||||||
} else {
|
} else {
|
||||||
this.focusedItemIndex--;
|
this.focusedItemIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
// :TODO: Optimize this in cases where we only need to redraw two items. Always the case now, somtimes
|
||||||
this.redraw();
|
this.redraw();
|
||||||
|
|
||||||
HorizontalMenuView.super_.prototype.focusPrevious.call(this);
|
HorizontalMenuView.super_.prototype.focusPrevious.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
HorizontalMenuView.prototype.onKeyPress = function(ch, key) {
|
HorizontalMenuView.prototype.onKeyPress = function(ch, key) {
|
||||||
if(key) {
|
if(key) {
|
||||||
if(this.isKeyMapped('left', key.name)) {
|
if(this.isKeyMapped('left', key.name)) {
|
||||||
this.focusPrevious();
|
this.focusPrevious();
|
||||||
} else if(this.isKeyMapped('right', key.name)) {
|
} else if(this.isKeyMapped('right', key.name)) {
|
||||||
this.focusNext();
|
this.focusNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalMenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
HorizontalMenuView.super_.prototype.onKeyPress.call(this, ch, key);
|
||||||
};
|
};
|
||||||
|
|
||||||
HorizontalMenuView.prototype.getData = function() {
|
HorizontalMenuView.prototype.getData = function() {
|
||||||
const item = this.getItem(this.focusedItemIndex);
|
const item = this.getItem(this.focusedItemIndex);
|
||||||
return _.isString(item.data) ? item.data : this.focusedItemIndex;
|
return _.isString(item.data) ? item.data : this.focusedItemIndex;
|
||||||
};
|
};
|
|
@ -9,69 +9,69 @@ const stylizeString = require('./string_util.js').stylizeString;
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = class KeyEntryView extends View {
|
module.exports = class KeyEntryView extends View {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
options.acceptsFocus = valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = valueWithDefault(options.acceptsInput, true);
|
options.acceptsInput = valueWithDefault(options.acceptsInput, true);
|
||||||
|
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.eatTabKey = options.eatTabKey || true;
|
this.eatTabKey = options.eatTabKey || true;
|
||||||
this.caseInsensitive = options.caseInsensitive || true;
|
this.caseInsensitive = options.caseInsensitive || true;
|
||||||
|
|
||||||
if(Array.isArray(options.keys)) {
|
if(Array.isArray(options.keys)) {
|
||||||
if(this.caseInsensitive) {
|
if(this.caseInsensitive) {
|
||||||
this.keys = options.keys.map( k => k.toUpperCase() );
|
this.keys = options.keys.map( k => k.toUpperCase() );
|
||||||
} else {
|
} else {
|
||||||
this.keys = options.keys;
|
this.keys = options.keys;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyPress(ch, key) {
|
onKeyPress(ch, key) {
|
||||||
const drawKey = ch;
|
const drawKey = ch;
|
||||||
|
|
||||||
if(ch && this.caseInsensitive) {
|
if(ch && this.caseInsensitive) {
|
||||||
ch = ch.toUpperCase();
|
ch = ch.toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(drawKey && isPrintable(drawKey) && (!this.keys || this.keys.indexOf(ch) > -1)) {
|
if(drawKey && isPrintable(drawKey) && (!this.keys || this.keys.indexOf(ch) > -1)) {
|
||||||
this.redraw(); // sets position
|
this.redraw(); // sets position
|
||||||
this.client.term.write(stylizeString(ch, this.textStyle));
|
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) {
|
if(key && 'tab' === key.name && !this.eatTabKey) {
|
||||||
return this.emit('action', 'next', key);
|
return this.emit('action', 'next', key);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('action', 'accept');
|
this.emit('action', 'accept');
|
||||||
// NOTE: we don't call super here. KeyEntryView is a special snowflake.
|
// NOTE: we don't call super here. KeyEntryView is a special snowflake.
|
||||||
}
|
}
|
||||||
|
|
||||||
setPropertyValue(propName, propValue) {
|
setPropertyValue(propName, propValue) {
|
||||||
switch(propName) {
|
switch(propName) {
|
||||||
case 'eatTabKey' :
|
case 'eatTabKey' :
|
||||||
if(_.isBoolean(propValue)) {
|
if(_.isBoolean(propValue)) {
|
||||||
this.eatTabKey = propValue;
|
this.eatTabKey = propValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'caseInsensitive' :
|
case 'caseInsensitive' :
|
||||||
if(_.isBoolean(propValue)) {
|
if(_.isBoolean(propValue)) {
|
||||||
this.caseInsensitive = propValue;
|
this.caseInsensitive = propValue;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'keys' :
|
case 'keys' :
|
||||||
if(Array.isArray(propValue)) {
|
if(Array.isArray(propValue)) {
|
||||||
this.keys = propValue;
|
this.keys = propValue;
|
||||||
}
|
}
|
||||||
break;
|
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 = {
|
exports.moduleInfo = {
|
||||||
name : 'Last Callers',
|
name : 'Last Callers',
|
||||||
desc : 'Last callers to the system',
|
desc : 'Last callers to the system',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
packageName : 'codes.l33t.enigma.lastcallers'
|
packageName : 'codes.l33t.enigma.lastcallers'
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciCodeIds = {
|
const MciCodeIds = {
|
||||||
CallerList : 1,
|
CallerList : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class LastCallersModule extends MenuModule {
|
exports.getModule = class LastCallersModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
let loginHistory;
|
let loginHistory;
|
||||||
let callersView;
|
let callersView;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.menu,
|
mciMap : mciData.menu,
|
||||||
noInput : true,
|
noInput : true,
|
||||||
};
|
};
|
||||||
|
|
||||||
vc.loadFromMenuConfig(loadOpts, callback);
|
vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
},
|
},
|
||||||
function fetchHistory(callback) {
|
function fetchHistory(callback) {
|
||||||
callersView = vc.getView(MciCodeIds.CallerList);
|
callersView = vc.getView(MciCodeIds.CallerList);
|
||||||
|
|
||||||
// fetch up
|
// fetch up
|
||||||
StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => {
|
StatLog.getSystemLogEntries('user_login_history', StatLog.Order.TimestampDesc, 200, (err, lh) => {
|
||||||
loginHistory = lh;
|
loginHistory = lh;
|
||||||
|
|
||||||
if(self.menuConfig.config.hideSysOpLogin) {
|
if(self.menuConfig.config.hideSysOpLogin) {
|
||||||
const noOpLoginHistory = loginHistory.filter(lh => {
|
const noOpLoginHistory = loginHistory.filter(lh => {
|
||||||
return false === User.isRootUserId(parseInt(lh.log_value)); // log_value=userId
|
return false === User.isRootUserId(parseInt(lh.log_value)); // log_value=userId
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// If we have enough items to display, or hideSysOpLogin is set to 'always',
|
// 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.
|
// then set loginHistory to our filtered list. Else, we'll leave it be.
|
||||||
//
|
//
|
||||||
if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) {
|
if(noOpLoginHistory.length >= callersView.dimens.height || 'always' === self.menuConfig.config.hideSysOpLogin) {
|
||||||
loginHistory = noOpLoginHistory;
|
loginHistory = noOpLoginHistory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Finally, we need to trim up the list to the needed size
|
// Finally, we need to trim up the list to the needed size
|
||||||
//
|
//
|
||||||
loginHistory = loginHistory.slice(0, callersView.dimens.height);
|
loginHistory = loginHistory.slice(0, callersView.dimens.height);
|
||||||
|
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getUserNamesAndProperties(callback) {
|
function getUserNamesAndProperties(callback) {
|
||||||
const getPropOpts = {
|
const getPropOpts = {
|
||||||
names : [ 'location', 'affiliation' ]
|
names : [ 'location', 'affiliation' ]
|
||||||
};
|
};
|
||||||
|
|
||||||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
|
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || 'ddd MMM DD';
|
||||||
|
|
||||||
async.each(
|
async.each(
|
||||||
loginHistory,
|
loginHistory,
|
||||||
(item, next) => {
|
(item, next) => {
|
||||||
item.userId = parseInt(item.log_value);
|
item.userId = parseInt(item.log_value);
|
||||||
item.ts = moment(item.timestamp).format(dateTimeFormat);
|
item.ts = moment(item.timestamp).format(dateTimeFormat);
|
||||||
|
|
||||||
User.getUserName(item.userId, (err, userName) => {
|
User.getUserName(item.userId, (err, userName) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
item.deleted = true;
|
item.deleted = true;
|
||||||
return next(null);
|
return next(null);
|
||||||
} else {
|
} else {
|
||||||
item.userName = userName || 'N/A';
|
item.userName = userName || 'N/A';
|
||||||
|
|
||||||
User.loadProperties(item.userId, getPropOpts, (err, props) => {
|
User.loadProperties(item.userId, getPropOpts, (err, props) => {
|
||||||
if(!err && props) {
|
if(!err && props) {
|
||||||
item.location = props.location || 'N/A';
|
item.location = props.location || 'N/A';
|
||||||
item.affiliation = item.affils = (props.affiliation || 'N/A');
|
item.affiliation = item.affils = (props.affiliation || 'N/A');
|
||||||
} else {
|
} else {
|
||||||
item.location = 'N/A';
|
item.location = 'N/A';
|
||||||
item.affiliation = item.affils = 'N/A';
|
item.affiliation = item.affils = 'N/A';
|
||||||
}
|
}
|
||||||
return next(null);
|
return next(null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
loginHistory = loginHistory.filter(lh => true !== lh.deleted);
|
loginHistory = loginHistory.filter(lh => true !== lh.deleted);
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function populateList(callback) {
|
function populateList(callback) {
|
||||||
const listFormat = self.menuConfig.config.listFormat || '{userName} - {location} - {affiliation} - {ts}';
|
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();
|
callersView.redraw();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err) => {
|
(err) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.error( { error : err.toString() }, 'Error loading last callers');
|
self.client.log.error( { error : err.toString() }, 'Error loading last callers');
|
||||||
}
|
}
|
||||||
cb(err);
|
cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,51 +14,51 @@ exports.shutdown = shutdown;
|
||||||
exports.getServer = getServer;
|
exports.getServer = getServer;
|
||||||
|
|
||||||
function startup(cb) {
|
function startup(cb) {
|
||||||
return startListening(cb);
|
return startListening(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shutdown(cb) {
|
function shutdown(cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServer(packageName) {
|
function getServer(packageName) {
|
||||||
return listeningServers[packageName];
|
return listeningServers[packageName];
|
||||||
}
|
}
|
||||||
|
|
||||||
function startListening(cb) {
|
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) => {
|
async.each( [ 'login', 'content' ], (category, next) => {
|
||||||
moduleUtil.loadModulesForCategory(`${category}Servers`, (err, module) => {
|
moduleUtil.loadModulesForCategory(`${category}Servers`, (err, module) => {
|
||||||
// :TODO: use enig error here!
|
// :TODO: use enig error here!
|
||||||
if(err) {
|
if(err) {
|
||||||
if('EENIGMODDISABLED' === err.code) {
|
if('EENIGMODDISABLED' === err.code) {
|
||||||
logger.log.debug(err.message);
|
logger.log.debug(err.message);
|
||||||
} else {
|
} else {
|
||||||
logger.log.info( { err : err }, 'Failed loading module');
|
logger.log.info( { err : err }, 'Failed loading module');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const moduleInst = new module.getModule();
|
const moduleInst = new module.getModule();
|
||||||
try {
|
try {
|
||||||
moduleInst.createServer();
|
moduleInst.createServer();
|
||||||
if(!moduleInst.listen()) {
|
if(!moduleInst.listen()) {
|
||||||
throw new Error('Failed listening');
|
throw new Error('Failed listening');
|
||||||
}
|
}
|
||||||
|
|
||||||
listeningServers[module.moduleInfo.packageName] = {
|
listeningServers[module.moduleInfo.packageName] = {
|
||||||
instance : moduleInst,
|
instance : moduleInst,
|
||||||
info : module.moduleInfo,
|
info : module.moduleInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
logger.log.error(e, 'Exception caught creating server!');
|
logger.log.error(e, 'Exception caught creating server!');
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
return next(err);
|
return next(err);
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
108
core/logger.js
108
core/logger.js
|
@ -9,66 +9,66 @@ const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = class Log {
|
module.exports = class Log {
|
||||||
|
|
||||||
static init() {
|
static init() {
|
||||||
const Config = require('./config.js').get();
|
const Config = require('./config.js').get();
|
||||||
const logPath = Config.paths.logs;
|
const logPath = Config.paths.logs;
|
||||||
|
|
||||||
const err = this.checkLogPath(logPath);
|
const err = this.checkLogPath(logPath);
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error(err.message); // eslint-disable-line no-console
|
console.error(err.message); // eslint-disable-line no-console
|
||||||
return process.exit();
|
return process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
const logStreams = [];
|
const logStreams = [];
|
||||||
if(_.isObject(Config.logging.rotatingFile)) {
|
if(_.isObject(Config.logging.rotatingFile)) {
|
||||||
Config.logging.rotatingFile.path = paths.join(logPath, Config.logging.rotatingFile.fileName);
|
Config.logging.rotatingFile.path = paths.join(logPath, Config.logging.rotatingFile.fileName);
|
||||||
logStreams.push(Config.logging.rotatingFile);
|
logStreams.push(Config.logging.rotatingFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
const serializers = {
|
const serializers = {
|
||||||
err : bunyan.stdSerializers.err, // handle 'err' fields with stack/etc.
|
err : bunyan.stdSerializers.err, // handle 'err' fields with stack/etc.
|
||||||
};
|
};
|
||||||
|
|
||||||
// try to remove sensitive info by default, e.g. 'password' fields
|
// try to remove sensitive info by default, e.g. 'password' fields
|
||||||
[ 'formData', 'formValue' ].forEach(keyName => {
|
[ 'formData', 'formValue' ].forEach(keyName => {
|
||||||
serializers[keyName] = (fd) => Log.hideSensitive(fd);
|
serializers[keyName] = (fd) => Log.hideSensitive(fd);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.log = bunyan.createLogger({
|
this.log = bunyan.createLogger({
|
||||||
name : 'ENiGMA½ BBS',
|
name : 'ENiGMA½ BBS',
|
||||||
streams : logStreams,
|
streams : logStreams,
|
||||||
serializers : serializers,
|
serializers : serializers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkLogPath(logPath) {
|
static checkLogPath(logPath) {
|
||||||
try {
|
try {
|
||||||
if(!fs.statSync(logPath).isDirectory()) {
|
if(!fs.statSync(logPath).isDirectory()) {
|
||||||
return new Error(`${logPath} is not a directory`);
|
return new Error(`${logPath} is not a directory`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if('ENOENT' === e.code) {
|
if('ENOENT' === e.code) {
|
||||||
return new Error(`${logPath} does not exist`);
|
return new Error(`${logPath} does not exist`);
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static hideSensitive(obj) {
|
static hideSensitive(obj) {
|
||||||
try {
|
try {
|
||||||
//
|
//
|
||||||
// Use a regexp -- we don't know how nested fields we want to seek and destroy may be
|
// Use a regexp -- we don't know how nested fields we want to seek and destroy may be
|
||||||
//
|
//
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
JSON.stringify(obj).replace(/"(password|passwordConfirm|key|authCode)"\s?:\s?"([^"]+)"/, (match, valueName) => {
|
JSON.stringify(obj).replace(/"(password|passwordConfirm|key|authCode)"\s?:\s?"([^"]+)"/, (match, valueName) => {
|
||||||
return `"${valueName}":"********"`;
|
return `"${valueName}":"********"`;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
// be safe and return empty obj!
|
// be safe and return empty obj!
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,77 +11,77 @@ const clientConns = require('./client_connections.js');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
module.exports = class LoginServerModule extends ServerModule {
|
module.exports = class LoginServerModule extends ServerModule {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
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) {
|
prepareClient(client, cb) {
|
||||||
const theme = require('./theme.js');
|
const theme = require('./theme.js');
|
||||||
|
|
||||||
//
|
//
|
||||||
// Choose initial theme before we have user context
|
// Choose initial theme before we have user context
|
||||||
//
|
//
|
||||||
if('*' === conf.config.preLoginTheme) {
|
if('*' === conf.config.preLoginTheme) {
|
||||||
client.user.properties.theme_id = theme.getRandomTheme() || '';
|
client.user.properties.theme_id = theme.getRandomTheme() || '';
|
||||||
} else {
|
} else {
|
||||||
client.user.properties.theme_id = conf.config.preLoginTheme;
|
client.user.properties.theme_id = conf.config.preLoginTheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.setClientTheme(client, client.user.properties.theme_id);
|
theme.setClientTheme(client, client.user.properties.theme_id);
|
||||||
return cb(null); // note: currently useless to use cb here - but this may change...again...
|
return cb(null); // note: currently useless to use cb here - but this may change...again...
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNewClient(client, clientSock, modInfo) {
|
handleNewClient(client, clientSock, modInfo) {
|
||||||
//
|
//
|
||||||
// Start tracking the client. We'll assign it an ID which is
|
// Start tracking the client. We'll assign it an ID which is
|
||||||
// just the index in our connections array.
|
// just the index in our connections array.
|
||||||
//
|
//
|
||||||
if(_.isUndefined(client.session)) {
|
if(_.isUndefined(client.session)) {
|
||||||
client.session = {};
|
client.session = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
client.session.serverName = modInfo.name;
|
client.session.serverName = modInfo.name;
|
||||||
client.session.isSecure = _.isBoolean(client.isSecure) ? client.isSecure : (modInfo.isSecure || false);
|
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
|
// Go to module -- use default error handler
|
||||||
this.prepareClient(client, () => {
|
this.prepareClient(client, () => {
|
||||||
require('./connect.js').connectEntry(client, readyOptions.firstMenu);
|
require('./connect.js').connectEntry(client, readyOptions.firstMenu);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('end', () => {
|
client.on('end', () => {
|
||||||
clientConns.removeClient(client);
|
clientConns.removeClient(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('error', err => {
|
client.on('error', err => {
|
||||||
logger.log.info({ clientId : client.session.id }, 'Connection error: %s' % err.message);
|
logger.log.info({ clientId : client.session.id }, 'Connection error: %s' % err.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('close', err => {
|
client.on('close', err => {
|
||||||
const logFunc = err ? logger.log.info : logger.log.debug;
|
const logFunc = err ? logger.log.info : logger.log.debug;
|
||||||
logFunc( { clientId : client.session.id }, 'Connection closed');
|
logFunc( { clientId : client.session.id }, 'Connection closed');
|
||||||
|
|
||||||
clientConns.removeClient(client);
|
clientConns.removeClient(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('idle timeout', () => {
|
client.on('idle timeout', () => {
|
||||||
client.log.info('User idle timeout expired');
|
client.log.info('User idle timeout expired');
|
||||||
|
|
||||||
client.menuStack.goto('idleLogoff', err => {
|
client.menuStack.goto('idleLogoff', err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// likely just doesn't exist
|
// likely just doesn't exist
|
||||||
client.term.write('\nIdle timeout expired. Goodbye!\n');
|
client.term.write('\nIdle timeout expired. Goodbye!\n');
|
||||||
client.end();
|
client.end();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,29 +8,29 @@ var _ = require('lodash');
|
||||||
module.exports = MailPacket;
|
module.exports = MailPacket;
|
||||||
|
|
||||||
function MailPacket(options) {
|
function MailPacket(options) {
|
||||||
events.EventEmitter.call(this);
|
events.EventEmitter.call(this);
|
||||||
|
|
||||||
// map of network name -> address obj ( { zone, net, node, point, domain } )
|
// map of network name -> address obj ( { zone, net, node, point, domain } )
|
||||||
this.nodeAddresses = options.nodeAddresses || {};
|
this.nodeAddresses = options.nodeAddresses || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(MailPacket, events.EventEmitter);
|
require('util').inherits(MailPacket, events.EventEmitter);
|
||||||
|
|
||||||
MailPacket.prototype.read = function(options) {
|
MailPacket.prototype.read = function(options) {
|
||||||
//
|
//
|
||||||
// options.packetPath | opts.packetBuffer: supplies a path-to-file
|
// options.packetPath | opts.packetBuffer: supplies a path-to-file
|
||||||
// or a buffer containing packet data
|
// or a buffer containing packet data
|
||||||
//
|
//
|
||||||
// emits 'message' event per message read
|
// emits 'message' event per message read
|
||||||
//
|
//
|
||||||
assert(_.isString(options.packetPath) || Buffer.isBuffer(options.packetBuffer));
|
assert(_.isString(options.packetPath) || Buffer.isBuffer(options.packetBuffer));
|
||||||
};
|
};
|
||||||
|
|
||||||
MailPacket.prototype.write = function(options) {
|
MailPacket.prototype.write = function(options) {
|
||||||
//
|
//
|
||||||
// options.messages[]: array of message(s) to create packets from
|
// options.messages[]: array of message(s) to create packets from
|
||||||
//
|
//
|
||||||
// emits 'packet' event per packet constructed
|
// emits 'packet' event per packet constructed
|
||||||
//
|
//
|
||||||
assert(_.isArray(options.messages));
|
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' }
|
Bar <baz@foobar.net> { name : 'Bar', flavor : 'email', remote : 'baz@foobar.com' }
|
||||||
*/
|
*/
|
||||||
function getAddressedToInfo(input) {
|
function getAddressedToInfo(input) {
|
||||||
input = input.trim();
|
input = input.trim();
|
||||||
|
|
||||||
const firstAtPos = input.indexOf('@');
|
const firstAtPos = input.indexOf('@');
|
||||||
|
|
||||||
if(firstAtPos < 0) {
|
if(firstAtPos < 0) {
|
||||||
let addr = Address.fromString(input);
|
let addr = Address.fromString(input);
|
||||||
if(Address.isValidAddress(addr)) {
|
if(Address.isValidAddress(addr)) {
|
||||||
return { flavor : Message.AddressFlavor.FTN, remote : input };
|
return { flavor : Message.AddressFlavor.FTN, remote : input };
|
||||||
}
|
}
|
||||||
|
|
||||||
const lessThanPos = input.indexOf('<');
|
const lessThanPos = input.indexOf('<');
|
||||||
if(lessThanPos < 0) {
|
if(lessThanPos < 0) {
|
||||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||||
}
|
}
|
||||||
|
|
||||||
const greaterThanPos = input.indexOf('>');
|
const greaterThanPos = input.indexOf('>');
|
||||||
if(greaterThanPos < lessThanPos) {
|
if(greaterThanPos < lessThanPos) {
|
||||||
return { name : input, flavor : Message.AddressFlavor.Local };
|
return { name : input, flavor : Message.AddressFlavor.Local };
|
||||||
}
|
}
|
||||||
|
|
||||||
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
|
addr = Address.fromString(input.slice(lessThanPos + 1, greaterThanPos));
|
||||||
if(Address.isValidAddress(addr)) {
|
if(Address.isValidAddress(addr)) {
|
||||||
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() };
|
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 lessThanPos = input.indexOf('<');
|
||||||
const greaterThanPos = input.indexOf('>');
|
const greaterThanPos = input.indexOf('>');
|
||||||
if(lessThanPos > 0 && greaterThanPos > lessThanPos) {
|
if(lessThanPos > 0 && greaterThanPos > lessThanPos) {
|
||||||
const addr = input.slice(lessThanPos + 1, greaterThanPos);
|
const addr = input.slice(lessThanPos + 1, greaterThanPos);
|
||||||
const m = addr.match(EMAIL_REGEX);
|
const m = addr.match(EMAIL_REGEX);
|
||||||
if(m) {
|
if(m) {
|
||||||
return { name : input.slice(0, lessThanPos).trim(), flavor : Message.AddressFlavor.Email, remote : addr };
|
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);
|
let m = input.match(EMAIL_REGEX);
|
||||||
if(m) {
|
if(m) {
|
||||||
return { name : input.slice(0, firstAtPos), flavor : Message.AddressFlavor.Email, remote : input };
|
return { name : input.slice(0, firstAtPos), flavor : Message.AddressFlavor.Email, remote : input };
|
||||||
}
|
}
|
||||||
|
|
||||||
let addr = Address.fromString(input); // 5D?
|
let addr = Address.fromString(input); // 5D?
|
||||||
if(Address.isValidAddress(addr)) {
|
if(Address.isValidAddress(addr)) {
|
||||||
return { flavor : Message.AddressFlavor.FTN, remote : addr.toString() } ;
|
return { flavor : Message.AddressFlavor.FTN, remote : addr.toString() } ;
|
||||||
}
|
}
|
||||||
|
|
||||||
addr = Address.fromString(input.slice(firstAtPos + 1).trim());
|
addr = Address.fromString(input.slice(firstAtPos + 1).trim());
|
||||||
if(Address.isValidAddress(addr)) {
|
if(Address.isValidAddress(addr)) {
|
||||||
return { name : input.slice(0, firstAtPos).trim(), flavor : Message.AddressFlavor.FTN, remote : addr.toString() };
|
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) {
|
function MaskEditTextView(options) {
|
||||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, true);
|
||||||
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
options.cursorStyle = miscUtil.valueWithDefault(options.cursorStyle, 'steady block');
|
||||||
options.resizable = false;
|
options.resizable = false;
|
||||||
|
|
||||||
TextView.call(this, options);
|
TextView.call(this, options);
|
||||||
|
|
||||||
this.cursorPos = { x : 0 };
|
this.cursorPos = { x : 0 };
|
||||||
this.patternArrayPos = 0;
|
this.patternArrayPos = 0;
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
this.maskPattern = options.maskPattern || '';
|
this.maskPattern = options.maskPattern || '';
|
||||||
|
|
||||||
this.clientBackspace = function() {
|
this.clientBackspace = function() {
|
||||||
var fillCharSGR = this.getStyleSGR(3) || this.getSGR();
|
var fillCharSGR = this.getStyleSGR(3) || this.getSGR();
|
||||||
this.client.term.write('\b' + fillCharSGR + this.fillChar + '\b' + this.getFocusSGR());
|
this.client.term.write('\b' + fillCharSGR + this.fillChar + '\b' + this.getFocusSGR());
|
||||||
};
|
};
|
||||||
|
|
||||||
this.drawText = function(s) {
|
this.drawText = function(s) {
|
||||||
var textToDraw = strUtil.stylizeString(s, this.hasFocus ? this.focusTextStyle : this.textStyle);
|
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
|
// draw out the text we have so far
|
||||||
var i = 0;
|
var i = 0;
|
||||||
var t = 0;
|
var t = 0;
|
||||||
while(i < self.patternArray.length) {
|
while(i < self.patternArray.length) {
|
||||||
if(_.isRegExp(self.patternArray[i])) {
|
if(_.isRegExp(self.patternArray[i])) {
|
||||||
if(t < textToDraw.length) {
|
if(t < textToDraw.length) {
|
||||||
self.client.term.write((self.hasFocus ? self.getFocusSGR() : self.getSGR()) + textToDraw[t]);
|
self.client.term.write((self.hasFocus ? self.getFocusSGR() : self.getSGR()) + textToDraw[t]);
|
||||||
t++;
|
t++;
|
||||||
} else {
|
} else {
|
||||||
self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar);
|
self.client.term.write((self.getStyleSGR(3) || '') + self.fillChar);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var styleSgr = this.hasFocus ? (self.getStyleSGR(2) || '') : (self.getStyleSGR(1) || '');
|
var styleSgr = this.hasFocus ? (self.getStyleSGR(2) || '') : (self.getStyleSGR(1) || '');
|
||||||
self.client.term.write(styleSgr + self.maskPattern[i]);
|
self.client.term.write(styleSgr + self.maskPattern[i]);
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.buildPattern = function() {
|
this.buildPattern = function() {
|
||||||
self.patternArray = [];
|
self.patternArray = [];
|
||||||
self.maxLength = 0;
|
self.maxLength = 0;
|
||||||
|
|
||||||
for(var i = 0; i < self.maskPattern.length; i++) {
|
for(var i = 0; i < self.maskPattern.length; i++) {
|
||||||
// :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark!
|
// :TODO: support escaped characters, e.g. \#. Also allow \\ for a '\' mark!
|
||||||
if(self.maskPattern[i] in MaskEditTextView.maskPatternCharacterRegEx) {
|
if(self.maskPattern[i] in MaskEditTextView.maskPatternCharacterRegEx) {
|
||||||
self.patternArray.push(MaskEditTextView.maskPatternCharacterRegEx[self.maskPattern[i]]);
|
self.patternArray.push(MaskEditTextView.maskPatternCharacterRegEx[self.maskPattern[i]]);
|
||||||
++self.maxLength;
|
++self.maxLength;
|
||||||
} else {
|
} else {
|
||||||
self.patternArray.push(self.maskPattern[i]);
|
self.patternArray.push(self.maskPattern[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getEndOfTextColumn = function() {
|
this.getEndOfTextColumn = function() {
|
||||||
return this.position.col + this.patternArrayPos;
|
return this.position.col + this.patternArrayPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.buildPattern();
|
this.buildPattern();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(MaskEditTextView, TextView);
|
require('util').inherits(MaskEditTextView, TextView);
|
||||||
|
|
||||||
MaskEditTextView.maskPatternCharacterRegEx = {
|
MaskEditTextView.maskPatternCharacterRegEx = {
|
||||||
'#' : /[0-9]/, // Numeric
|
'#' : /[0-9]/, // Numeric
|
||||||
'A' : /[a-zA-Z]/, // Alpha
|
'A' : /[a-zA-Z]/, // Alpha
|
||||||
'@' : /[0-9a-zA-Z]/, // Alphanumeric
|
'@' : /[0-9a-zA-Z]/, // Alphanumeric
|
||||||
'&' : /[\w\d\s]/, // Any "printable" 32-126, 128-255
|
'&' : /[\w\d\s]/, // Any "printable" 32-126, 128-255
|
||||||
};
|
};
|
||||||
|
|
||||||
MaskEditTextView.prototype.setText = function(text) {
|
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()
|
if(this.patternArray) { // :TODO: This is a hack - see TextView ctor note about setText()
|
||||||
this.patternArrayPos = this.patternArray.length;
|
this.patternArrayPos = this.patternArray.length;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MaskEditTextView.prototype.setMaskPattern = function(pattern) {
|
MaskEditTextView.prototype.setMaskPattern = function(pattern) {
|
||||||
this.dimens.width = pattern.length;
|
this.dimens.width = pattern.length;
|
||||||
|
|
||||||
this.maskPattern = pattern;
|
this.maskPattern = pattern;
|
||||||
this.buildPattern();
|
this.buildPattern();
|
||||||
};
|
};
|
||||||
|
|
||||||
MaskEditTextView.prototype.onKeyPress = function(ch, key) {
|
MaskEditTextView.prototype.onKeyPress = function(ch, key) {
|
||||||
if(key) {
|
if(key) {
|
||||||
if(this.isKeyMapped('backspace', key.name)) {
|
if(this.isKeyMapped('backspace', key.name)) {
|
||||||
if(this.text.length > 0) {
|
if(this.text.length > 0) {
|
||||||
this.patternArrayPos--;
|
this.patternArrayPos--;
|
||||||
assert(this.patternArrayPos >= 0);
|
assert(this.patternArrayPos >= 0);
|
||||||
|
|
||||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||||
this.text = this.text.substr(0, this.text.length - 1);
|
this.text = this.text.substr(0, this.text.length - 1);
|
||||||
this.clientBackspace();
|
this.clientBackspace();
|
||||||
} else {
|
} else {
|
||||||
while(this.patternArrayPos > 0) {
|
while(this.patternArrayPos > 0) {
|
||||||
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
if(_.isRegExp(this.patternArray[this.patternArrayPos])) {
|
||||||
this.text = this.text.substr(0, this.text.length - 1);
|
this.text = this.text.substr(0, this.text.length - 1);
|
||||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn() + 1));
|
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn() + 1));
|
||||||
this.clientBackspace();
|
this.clientBackspace();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
this.patternArrayPos--;
|
this.patternArrayPos--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} else if(this.isKeyMapped('clearLine', key.name)) {
|
} else if(this.isKeyMapped('clearLine', key.name)) {
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.patternArrayPos = 0;
|
this.patternArrayPos = 0;
|
||||||
this.setFocus(true); // redraw + adjust cursor
|
this.setFocus(true); // redraw + adjust cursor
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(ch && strUtil.isPrintable(ch)) {
|
if(ch && strUtil.isPrintable(ch)) {
|
||||||
if(this.text.length < this.maxLength) {
|
if(this.text.length < this.maxLength) {
|
||||||
ch = strUtil.stylizeString(ch, this.textStyle);
|
ch = strUtil.stylizeString(ch, this.textStyle);
|
||||||
|
|
||||||
if(!ch.match(this.patternArray[this.patternArrayPos])) {
|
if(!ch.match(this.patternArray[this.patternArrayPos])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.text += ch;
|
this.text += ch;
|
||||||
this.patternArrayPos++;
|
this.patternArrayPos++;
|
||||||
|
|
||||||
while(this.patternArrayPos < this.patternArray.length &&
|
while(this.patternArrayPos < this.patternArray.length &&
|
||||||
!_.isRegExp(this.patternArray[this.patternArrayPos]))
|
!_.isRegExp(this.patternArray[this.patternArrayPos]))
|
||||||
{
|
{
|
||||||
this.patternArrayPos++;
|
this.patternArrayPos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
this.client.term.write(ansi.goto(this.position.row, this.getEndOfTextColumn()));
|
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) {
|
MaskEditTextView.prototype.setPropertyValue = function(propName, value) {
|
||||||
switch(propName) {
|
switch(propName) {
|
||||||
case 'maskPattern' : this.setMaskPattern(value); break;
|
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() {
|
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) {
|
if(!rawData || 0 === rawData.length) {
|
||||||
return rawData;
|
return rawData;
|
||||||
}
|
}
|
||||||
|
|
||||||
var data = '';
|
var data = '';
|
||||||
|
|
||||||
assert(rawData.length <= this.patternArray.length);
|
assert(rawData.length <= this.patternArray.length);
|
||||||
|
|
||||||
var p = 0;
|
var p = 0;
|
||||||
for(var i = 0; i < this.patternArray.length; ++i) {
|
for(var i = 0; i < this.patternArray.length; ++i) {
|
||||||
if(_.isRegExp(this.patternArray[i])) {
|
if(_.isRegExp(this.patternArray[i])) {
|
||||||
data += rawData[p++];
|
data += rawData[p++];
|
||||||
} else {
|
} else {
|
||||||
data += this.patternArray[i];
|
data += this.patternArray[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,186 +22,186 @@ const _ = require('lodash');
|
||||||
exports.MCIViewFactory = MCIViewFactory;
|
exports.MCIViewFactory = MCIViewFactory;
|
||||||
|
|
||||||
function MCIViewFactory(client) {
|
function MCIViewFactory(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
MCIViewFactory.UserViewCodes = [
|
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
|
// XY is a special MCI code that allows finding positions
|
||||||
// and counts for key lookup, but does not explicitly
|
// and counts for key lookup, but does not explicitly
|
||||||
// represent a visible View on it's own
|
// represent a visible View on it's own
|
||||||
//
|
//
|
||||||
'XY',
|
'XY',
|
||||||
];
|
];
|
||||||
|
|
||||||
MCIViewFactory.prototype.createFromMCI = function(mci) {
|
MCIViewFactory.prototype.createFromMCI = function(mci) {
|
||||||
assert(mci.code);
|
assert(mci.code);
|
||||||
assert(mci.id > 0);
|
assert(mci.id > 0);
|
||||||
assert(mci.position);
|
assert(mci.position);
|
||||||
|
|
||||||
var view;
|
var view;
|
||||||
var options = {
|
var options = {
|
||||||
client : this.client,
|
client : this.client,
|
||||||
id : mci.id,
|
id : mci.id,
|
||||||
ansiSGR : mci.SGR,
|
ansiSGR : mci.SGR,
|
||||||
ansiFocusSGR : mci.focusSGR,
|
ansiFocusSGR : mci.focusSGR,
|
||||||
position : { row : mci.position[0], col : mci.position[1] },
|
position : { row : mci.position[0], col : mci.position[1] },
|
||||||
};
|
};
|
||||||
|
|
||||||
// :TODO: These should use setPropertyValue()!
|
// :TODO: These should use setPropertyValue()!
|
||||||
function setOption(pos, name) {
|
function setOption(pos, name) {
|
||||||
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
||||||
options[name] = mci.args[pos];
|
options[name] = mci.args[pos];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWidth(pos) {
|
function setWidth(pos) {
|
||||||
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
if(mci.args.length > pos && mci.args[pos].length > 0) {
|
||||||
if(!_.isObject(options.dimens)) {
|
if(!_.isObject(options.dimens)) {
|
||||||
options.dimens = {};
|
options.dimens = {};
|
||||||
}
|
}
|
||||||
options.dimens.width = parseInt(mci.args[pos], 10);
|
options.dimens.width = parseInt(mci.args[pos], 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setFocusOption(pos, name) {
|
function setFocusOption(pos, name) {
|
||||||
if(mci.focusArgs && mci.focusArgs.length > pos && mci.focusArgs[pos].length > 0) {
|
if(mci.focusArgs && mci.focusArgs.length > pos && mci.focusArgs[pos].length > 0) {
|
||||||
options[name] = mci.focusArgs[pos];
|
options[name] = mci.focusArgs[pos];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Note: Keep this in sync with UserViewCodes above!
|
// Note: Keep this in sync with UserViewCodes above!
|
||||||
//
|
//
|
||||||
switch(mci.code) {
|
switch(mci.code) {
|
||||||
// Text Label (Text View)
|
// Text Label (Text View)
|
||||||
case 'TL' :
|
case 'TL' :
|
||||||
setOption(0, 'textStyle');
|
setOption(0, 'textStyle');
|
||||||
setOption(1, 'justify');
|
setOption(1, 'justify');
|
||||||
setWidth(2);
|
setWidth(2);
|
||||||
|
|
||||||
view = new TextView(options);
|
view = new TextView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Edit Text
|
// Edit Text
|
||||||
case 'ET' :
|
case 'ET' :
|
||||||
setWidth(0);
|
setWidth(0);
|
||||||
|
|
||||||
setOption(1, 'textStyle');
|
setOption(1, 'textStyle');
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new EditTextView(options);
|
view = new EditTextView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Masked Edit Text
|
// Masked Edit Text
|
||||||
case 'ME' :
|
case 'ME' :
|
||||||
setOption(0, 'textStyle');
|
setOption(0, 'textStyle');
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new MaskEditTextView(options);
|
view = new MaskEditTextView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Multi Line Edit Text
|
// Multi Line Edit Text
|
||||||
case 'MT' :
|
case 'MT' :
|
||||||
// :TODO: apply params
|
// :TODO: apply params
|
||||||
view = new MultiLineEditTextView(options);
|
view = new MultiLineEditTextView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Pre-defined Label (Text View)
|
// Pre-defined Label (Text View)
|
||||||
// :TODO: Currently no real point of PL -- @method replaces this pretty much... probably remove
|
// :TODO: Currently no real point of PL -- @method replaces this pretty much... probably remove
|
||||||
case 'PL' :
|
case 'PL' :
|
||||||
if(mci.args.length > 0) {
|
if(mci.args.length > 0) {
|
||||||
options.text = getPredefinedMCIValue(this.client, mci.args[0]);
|
options.text = getPredefinedMCIValue(this.client, mci.args[0]);
|
||||||
if(options.text) {
|
if(options.text) {
|
||||||
setOption(1, 'textStyle');
|
setOption(1, 'textStyle');
|
||||||
setOption(2, 'justify');
|
setOption(2, 'justify');
|
||||||
setWidth(3);
|
setWidth(3);
|
||||||
|
|
||||||
view = new TextView(options);
|
view = new TextView(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Button
|
// Button
|
||||||
case 'BT' :
|
case 'BT' :
|
||||||
if(mci.args.length > 0) {
|
if(mci.args.length > 0) {
|
||||||
options.dimens = { width : parseInt(mci.args[0], 10) };
|
options.dimens = { width : parseInt(mci.args[0], 10) };
|
||||||
}
|
}
|
||||||
|
|
||||||
setOption(1, 'textStyle');
|
setOption(1, 'textStyle');
|
||||||
setOption(2, 'justify');
|
setOption(2, 'justify');
|
||||||
|
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new ButtonView(options);
|
view = new ButtonView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Vertial Menu
|
// Vertial Menu
|
||||||
case 'VM' :
|
case 'VM' :
|
||||||
setOption(0, 'itemSpacing');
|
setOption(0, 'itemSpacing');
|
||||||
setOption(1, 'justify');
|
setOption(1, 'justify');
|
||||||
setOption(2, 'textStyle');
|
setOption(2, 'textStyle');
|
||||||
|
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new VerticalMenuView(options);
|
view = new VerticalMenuView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Horizontal Menu
|
// Horizontal Menu
|
||||||
case 'HM' :
|
case 'HM' :
|
||||||
setOption(0, 'itemSpacing');
|
setOption(0, 'itemSpacing');
|
||||||
setOption(1, 'textStyle');
|
setOption(1, 'textStyle');
|
||||||
|
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new HorizontalMenuView(options);
|
view = new HorizontalMenuView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'SM' :
|
case 'SM' :
|
||||||
setOption(0, 'textStyle');
|
setOption(0, 'textStyle');
|
||||||
setOption(1, 'justify');
|
setOption(1, 'justify');
|
||||||
|
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new SpinnerMenuView(options);
|
view = new SpinnerMenuView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'TM' :
|
case 'TM' :
|
||||||
if(mci.args.length > 0) {
|
if(mci.args.length > 0) {
|
||||||
var styleSG1 = { fg : parseInt(mci.args[0], 10) };
|
var styleSG1 = { fg : parseInt(mci.args[0], 10) };
|
||||||
if(mci.args.length > 1) {
|
if(mci.args.length > 1) {
|
||||||
styleSG1.bg = parseInt(mci.args[1], 10);
|
styleSG1.bg = parseInt(mci.args[1], 10);
|
||||||
}
|
}
|
||||||
options.styleSG1 = ansi.getSGRFromGraphicRendition(styleSG1, true);
|
options.styleSG1 = ansi.getSGRFromGraphicRendition(styleSG1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocusOption(0, 'focusTextStyle');
|
setFocusOption(0, 'focusTextStyle');
|
||||||
|
|
||||||
view = new ToggleMenuView(options);
|
view = new ToggleMenuView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'KE' :
|
case 'KE' :
|
||||||
view = new KeyEntryView(options);
|
view = new KeyEntryView(options);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default :
|
default :
|
||||||
options.text = getPredefinedMCIValue(this.client, mci.code);
|
options.text = getPredefinedMCIValue(this.client, mci.code);
|
||||||
if(_.isString(options.text)) {
|
if(_.isString(options.text)) {
|
||||||
setWidth(0);
|
setWidth(0);
|
||||||
|
|
||||||
setOption(1, 'textStyle');
|
setOption(1, 'textStyle');
|
||||||
setOption(2, 'justify');
|
setOption(2, 'justify');
|
||||||
|
|
||||||
view = new TextView(options);
|
view = new TextView(options);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(view) {
|
if(view) {
|
||||||
view.mciCode = mci.code;
|
view.mciCode = mci.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,358 +19,358 @@ const _ = require('lodash');
|
||||||
|
|
||||||
exports.MenuModule = class MenuModule extends PluginModule {
|
exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.menuName = options.menuName;
|
this.menuName = options.menuName;
|
||||||
this.menuConfig = options.menuConfig;
|
this.menuConfig = options.menuConfig;
|
||||||
this.client = options.client;
|
this.client = options.client;
|
||||||
this.menuConfig.options = options.menuConfig.options || {};
|
this.menuConfig.options = options.menuConfig.options || {};
|
||||||
this.menuMethods = {}; // methods called from @method's
|
this.menuMethods = {}; // methods called from @method's
|
||||||
this.menuConfig.config = this.menuConfig.config || {};
|
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() {
|
enter() {
|
||||||
this.initSequence();
|
this.initSequence();
|
||||||
}
|
}
|
||||||
|
|
||||||
leave() {
|
leave() {
|
||||||
this.detachViewControllers();
|
this.detachViewControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
const mciData = {};
|
const mciData = {};
|
||||||
let pausePosition;
|
let pausePosition;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function beforeDisplayArt(callback) {
|
function beforeDisplayArt(callback) {
|
||||||
self.beforeArt(callback);
|
self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function displayMenuArt(callback) {
|
function displayMenuArt(callback) {
|
||||||
if(!_.isString(self.menuConfig.art)) {
|
if(!_.isString(self.menuConfig.art)) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.displayAsset(
|
self.displayAsset(
|
||||||
self.menuConfig.art,
|
self.menuConfig.art,
|
||||||
self.menuConfig.options,
|
self.menuConfig.options,
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } );
|
self.client.log.trace('Could not display art', { art : self.menuConfig.art, reason : err.message } );
|
||||||
} else {
|
} else {
|
||||||
mciData.menu = artData.mciMap;
|
mciData.menu = artData.mciMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null); // any errors are non-fatal
|
return callback(null); // any errors are non-fatal
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function moveToPromptLocation(callback) {
|
function moveToPromptLocation(callback) {
|
||||||
if(self.menuConfig.prompt) {
|
if(self.menuConfig.prompt) {
|
||||||
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
|
// :TODO: fetch and move cursor to prompt location, if supplied. See notes/etc. on placements
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function displayPromptArt(callback) {
|
function displayPromptArt(callback) {
|
||||||
if(!_.isString(self.menuConfig.prompt)) {
|
if(!_.isString(self.menuConfig.prompt)) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isObject(self.menuConfig.promptConfig)) {
|
if(!_.isObject(self.menuConfig.promptConfig)) {
|
||||||
return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found'));
|
return callback(Errors.MissingConfig('Prompt specified but no "promptConfig" block found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.displayAsset(
|
self.displayAsset(
|
||||||
self.menuConfig.promptConfig.art,
|
self.menuConfig.promptConfig.art,
|
||||||
self.menuConfig.options,
|
self.menuConfig.options,
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
if(artData) {
|
if(artData) {
|
||||||
mciData.prompt = artData.mciMap;
|
mciData.prompt = artData.mciMap;
|
||||||
}
|
}
|
||||||
return callback(err); // pass err here; prompts *must* have art
|
return callback(err); // pass err here; prompts *must* have art
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function recordCursorPosition(callback) {
|
function recordCursorPosition(callback) {
|
||||||
if(!self.shouldPause()) {
|
if(!self.shouldPause()) {
|
||||||
return callback(null); // cursor position not needed
|
return callback(null); // cursor position not needed
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.once('cursor position report', pos => {
|
self.client.once('cursor position report', pos => {
|
||||||
pausePosition = { row : pos[0], col : 1 };
|
pausePosition = { row : pos[0], col : 1 };
|
||||||
self.client.log.trace('After art position recorded', pausePosition );
|
self.client.log.trace('After art position recorded', pausePosition );
|
||||||
return callback(null);
|
return callback(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.client.term.rawWrite(ansi.queryPos());
|
self.client.term.rawWrite(ansi.queryPos());
|
||||||
},
|
},
|
||||||
function afterArtDisplayed(callback) {
|
function afterArtDisplayed(callback) {
|
||||||
return self.mciReady(mciData, callback);
|
return self.mciReady(mciData, callback);
|
||||||
},
|
},
|
||||||
function displayPauseIfRequested(callback) {
|
function displayPauseIfRequested(callback) {
|
||||||
if(!self.shouldPause()) {
|
if(!self.shouldPause()) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.pausePrompt(pausePosition, callback);
|
return self.pausePrompt(pausePosition, callback);
|
||||||
},
|
},
|
||||||
function finishAndNext(callback) {
|
function finishAndNext(callback) {
|
||||||
self.finishedLoading();
|
self.finishedLoading();
|
||||||
return self.autoNextMenu(callback);
|
return self.autoNextMenu(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn('Error during init sequence', { error : err.message } );
|
self.client.log.warn('Error during init sequence', { error : err.message } );
|
||||||
|
|
||||||
return self.prevMenu( () => { /* dummy */ } );
|
return self.prevMenu( () => { /* dummy */ } );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeArt(cb) {
|
beforeArt(cb) {
|
||||||
if(_.isNumber(this.menuConfig.options.baudRate)) {
|
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
|
// :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));
|
this.client.term.rawWrite(ansi.setEmulatedBaudRate(this.menuConfig.options.baudRate));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.cls) {
|
if(this.cls) {
|
||||||
this.client.term.rawWrite(ansi.resetScreen());
|
this.client.term.rawWrite(ansi.resetScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
// available for sub-classes
|
// available for sub-classes
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedLoading() {
|
finishedLoading() {
|
||||||
// nothing in base
|
// nothing in base
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaveState() {
|
getSaveState() {
|
||||||
// nothing in base
|
// nothing in base
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreSavedState(/*savedState*/) {
|
restoreSavedState(/*savedState*/) {
|
||||||
// nothing in base
|
// nothing in base
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuResult() {
|
getMenuResult() {
|
||||||
// default to the formData that was provided @ a submit, if any
|
// default to the formData that was provided @ a submit, if any
|
||||||
return this.submitFormData;
|
return this.submitFormData;
|
||||||
}
|
}
|
||||||
|
|
||||||
nextMenu(cb) {
|
nextMenu(cb) {
|
||||||
if(!this.haveNext()) {
|
if(!this.haveNext()) {
|
||||||
return this.prevMenu(cb); // no next, go to prev
|
return this.prevMenu(cb); // no next, go to prev
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.client.menuStack.next(cb);
|
return this.client.menuStack.next(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
prevMenu(cb) {
|
prevMenu(cb) {
|
||||||
return this.client.menuStack.prev(cb);
|
return this.client.menuStack.prev(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
gotoMenu(name, options, cb) {
|
gotoMenu(name, options, cb) {
|
||||||
return this.client.menuStack.goto(name, options, cb);
|
return this.client.menuStack.goto(name, options, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
addViewController(name, vc) {
|
addViewController(name, vc) {
|
||||||
assert(!this.viewControllers[name], `ViewController by the name of "${name}" already exists!`);
|
assert(!this.viewControllers[name], `ViewController by the name of "${name}" already exists!`);
|
||||||
|
|
||||||
this.viewControllers[name] = vc;
|
this.viewControllers[name] = vc;
|
||||||
return vc;
|
return vc;
|
||||||
}
|
}
|
||||||
|
|
||||||
detachViewControllers() {
|
detachViewControllers() {
|
||||||
Object.keys(this.viewControllers).forEach( name => {
|
Object.keys(this.viewControllers).forEach( name => {
|
||||||
this.viewControllers[name].detachClientEvents();
|
this.viewControllers[name].detachClientEvents();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldPause() {
|
shouldPause() {
|
||||||
return ('end' === this.menuConfig.options.pause || true === this.menuConfig.options.pause);
|
return ('end' === this.menuConfig.options.pause || true === this.menuConfig.options.pause);
|
||||||
}
|
}
|
||||||
|
|
||||||
hasNextTimeout() {
|
hasNextTimeout() {
|
||||||
return _.isNumber(this.menuConfig.options.nextTimeout);
|
return _.isNumber(this.menuConfig.options.nextTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
haveNext() {
|
haveNext() {
|
||||||
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
|
return (_.isString(this.menuConfig.next) || _.isArray(this.menuConfig.next));
|
||||||
}
|
}
|
||||||
|
|
||||||
autoNextMenu(cb) {
|
autoNextMenu(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
function gotoNextMenu() {
|
function gotoNextMenu() {
|
||||||
if(self.haveNext()) {
|
if(self.haveNext()) {
|
||||||
return menuUtil.handleNext(self.client, self.menuConfig.next, {}, cb);
|
return menuUtil.handleNext(self.client, self.menuConfig.next, {}, cb);
|
||||||
} else {
|
} else {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
if(_.has(this.menuConfig, 'runtime.autoNext') && true === this.menuConfig.runtime.autoNext) {
|
||||||
if(this.hasNextTimeout()) {
|
if(this.hasNextTimeout()) {
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return gotoNextMenu();
|
return gotoNextMenu();
|
||||||
}, this.menuConfig.options.nextTimeout);
|
}, this.menuConfig.options.nextTimeout);
|
||||||
} else {
|
} else {
|
||||||
return gotoNextMenu();
|
return gotoNextMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
standardMCIReadyHandler(mciData, cb) {
|
standardMCIReadyHandler(mciData, cb) {
|
||||||
//
|
//
|
||||||
// A quick rundown:
|
// A quick rundown:
|
||||||
// * We may have mciData.menu, mciData.prompt, or both.
|
// * We may have mciData.menu, mciData.prompt, or both.
|
||||||
// * Prompt form is favored over menu form if both are present.
|
// * 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)
|
// * Standard/prefdefined MCI entries must load both (e.g. %BN is expected to resolve)
|
||||||
//
|
//
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function addViewControllers(callback) {
|
function addViewControllers(callback) {
|
||||||
_.forEach(mciData, (mciMap, name) => {
|
_.forEach(mciData, (mciMap, name) => {
|
||||||
assert('menu' === name || 'prompt' === name);
|
assert('menu' === name || 'prompt' === name);
|
||||||
self.addViewController(name, new ViewController( { client : self.client } ) );
|
self.addViewController(name, new ViewController( { client : self.client } ) );
|
||||||
});
|
});
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function createMenu(callback) {
|
function createMenu(callback) {
|
||||||
if(!self.viewControllers.menu) {
|
if(!self.viewControllers.menu) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuLoadOpts = {
|
const menuLoadOpts = {
|
||||||
mciMap : mciData.menu,
|
mciMap : mciData.menu,
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
withoutForm : _.isObject(mciData.prompt),
|
withoutForm : _.isObject(mciData.prompt),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, err => {
|
self.viewControllers.menu.loadFromMenuConfig(menuLoadOpts, err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function createPrompt(callback) {
|
function createPrompt(callback) {
|
||||||
if(!self.viewControllers.prompt) {
|
if(!self.viewControllers.prompt) {
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptLoadOpts = {
|
const promptLoadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.prompt,
|
mciMap : mciData.prompt,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => {
|
self.viewControllers.prompt.loadFromPromptConfig(promptLoadOpts, err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAsset(name, options, cb) {
|
displayAsset(name, options, cb) {
|
||||||
if(_.isFunction(options)) {
|
if(_.isFunction(options)) {
|
||||||
cb = options;
|
cb = options;
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.clearScreen) {
|
if(options.clearScreen) {
|
||||||
this.client.term.rawWrite(ansi.resetScreen());
|
this.client.term.rawWrite(ansi.resetScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
return theme.displayThemedAsset(
|
return theme.displayThemedAsset(
|
||||||
name,
|
name,
|
||||||
this.client,
|
this.client,
|
||||||
Object.assign( { font : this.menuConfig.config.font }, options ),
|
Object.assign( { font : this.menuConfig.config.font }, options ),
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err, artData);
|
return cb(err, artData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepViewController(name, formId, mciMap, cb) {
|
prepViewController(name, formId, mciMap, cb) {
|
||||||
if(_.isUndefined(this.viewControllers[name])) {
|
if(_.isUndefined(this.viewControllers[name])) {
|
||||||
const vcOpts = {
|
const vcOpts = {
|
||||||
client : this.client,
|
client : this.client,
|
||||||
formId : formId,
|
formId : formId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const vc = this.addViewController(name, new ViewController(vcOpts));
|
const vc = this.addViewController(name, new ViewController(vcOpts));
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : this,
|
callingMenu : this,
|
||||||
mciMap : mciMap,
|
mciMap : mciMap,
|
||||||
formId : formId,
|
formId : formId,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, err => {
|
return vc.loadFromMenuConfig(loadOpts, err => {
|
||||||
return cb(err, vc);
|
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) {
|
prepViewControllerWithArt(name, formId, options, cb) {
|
||||||
this.displayAsset(
|
this.displayAsset(
|
||||||
this.menuConfig.config.art[name],
|
this.menuConfig.config.art[name],
|
||||||
options,
|
options,
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prepViewController(name, formId, artData.mciMap, cb);
|
return this.prepViewController(name, formId, artData.mciMap, cb);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
optionalMoveToPosition(position) {
|
optionalMoveToPosition(position) {
|
||||||
if(position) {
|
if(position) {
|
||||||
position.x = position.row || position.x || 1;
|
position.x = position.row || position.x || 1;
|
||||||
position.y = position.col || position.y || 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) {
|
pausePrompt(position, cb) {
|
||||||
if(!cb && _.isFunction(position)) {
|
if(!cb && _.isFunction(position)) {
|
||||||
cb = position;
|
cb = position;
|
||||||
position = null;
|
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) => ... )
|
:TODO: this needs quite a bit of work - but would be nice: promptForInput(..., (err, formData) => ... )
|
||||||
promptForInput(formName, name, options, cb) {
|
promptForInput(formName, name, options, cb) {
|
||||||
if(!cb && _.isFunction(options)) {
|
if(!cb && _.isFunction(options)) {
|
||||||
|
@ -386,55 +386,55 @@ exports.MenuModule = class MenuModule extends PluginModule {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
setViewText(formName, mciId, text, appendMultiLine) {
|
setViewText(formName, mciId, text, appendMultiLine) {
|
||||||
const view = this.viewControllers[formName].getView(mciId);
|
const view = this.viewControllers[formName].getView(mciId);
|
||||||
if(!view) {
|
if(!view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(appendMultiLine && (view instanceof MultiLineEditTextView)) {
|
if(appendMultiLine && (view instanceof MultiLineEditTextView)) {
|
||||||
view.addText(text);
|
view.addText(text);
|
||||||
} else {
|
} else {
|
||||||
view.setText(text);
|
view.setText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCustomViewTextsWithFilter(formName, startId, fmtObj, options) {
|
updateCustomViewTextsWithFilter(formName, startId, fmtObj, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
let textView;
|
let textView;
|
||||||
let customMciId = startId;
|
let customMciId = startId;
|
||||||
const config = this.menuConfig.config;
|
const config = this.menuConfig.config;
|
||||||
const endId = options.endId || 99; // we'll fail to get a view before 99
|
const endId = options.endId || 99; // we'll fail to get a view before 99
|
||||||
|
|
||||||
while(customMciId <= endId && (textView = this.viewControllers[formName].getView(customMciId)) ) {
|
while(customMciId <= endId && (textView = this.viewControllers[formName].getView(customMciId)) ) {
|
||||||
const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10"
|
const key = `${formName}InfoFormat${customMciId}`; // e.g. "mainInfoFormat10"
|
||||||
const format = config[key];
|
const format = config[key];
|
||||||
|
|
||||||
if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) {
|
if(format && (!options.filter || options.filter.find(f => format.indexOf(f) > - 1))) {
|
||||||
const text = stringFormat(format, fmtObj);
|
const text = stringFormat(format, fmtObj);
|
||||||
|
|
||||||
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) {
|
if(options.appendMultiLine && (textView instanceof MultiLineEditTextView)) {
|
||||||
textView.addText(text);
|
textView.addText(text);
|
||||||
} else {
|
} else {
|
||||||
textView.setText(text);
|
textView.setText(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
++customMciId;
|
++customMciId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshPredefinedMciViewsByCode(formName, mciCodes) {
|
refreshPredefinedMciViewsByCode(formName, mciCodes) {
|
||||||
const form = _.get(this, [ 'viewControllers', formName] );
|
const form = _.get(this, [ 'viewControllers', formName] );
|
||||||
if(form) {
|
if(form) {
|
||||||
form.getViewsByMciCode(mciCodes).forEach(v => {
|
form.getViewsByMciCode(mciCodes).forEach(v => {
|
||||||
if(!v.setText) {
|
if(!v.setText) {
|
||||||
return;
|
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! :)
|
// :TODO: Stack is backwards.... top should be most recent! :)
|
||||||
|
|
||||||
module.exports = class MenuStack {
|
module.exports = class MenuStack {
|
||||||
constructor(client) {
|
constructor(client) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.stack = [];
|
this.stack = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
push(moduleInfo) {
|
push(moduleInfo) {
|
||||||
return this.stack.push(moduleInfo);
|
return this.stack.push(moduleInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
pop() {
|
pop() {
|
||||||
return this.stack.pop();
|
return this.stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
peekPrev() {
|
peekPrev() {
|
||||||
if(this.stackSize > 1) {
|
if(this.stackSize > 1) {
|
||||||
return this.stack[this.stack.length - 2];
|
return this.stack[this.stack.length - 2];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
top() {
|
top() {
|
||||||
if(this.stackSize > 0) {
|
if(this.stackSize > 0) {
|
||||||
return this.stack[this.stack.length - 1];
|
return this.stack[this.stack.length - 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get stackSize() {
|
get stackSize() {
|
||||||
return this.stack.length;
|
return this.stack.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentModule() {
|
get currentModule() {
|
||||||
const top = this.top();
|
const top = this.top();
|
||||||
if(top) {
|
if(top) {
|
||||||
return top.instance;
|
return top.instance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next(cb) {
|
next(cb) {
|
||||||
const currentModuleInfo = this.top();
|
const currentModuleInfo = this.top();
|
||||||
assert(currentModuleInfo, 'Empty menu stack!');
|
assert(currentModuleInfo, 'Empty menu stack!');
|
||||||
|
|
||||||
const menuConfig = currentModuleInfo.instance.menuConfig;
|
const menuConfig = currentModuleInfo.instance.menuConfig;
|
||||||
const nextMenu = this.client.acs.getConditionalValue(menuConfig.next, 'next');
|
const nextMenu = this.client.acs.getConditionalValue(menuConfig.next, 'next');
|
||||||
if(!nextMenu) {
|
if(!nextMenu) {
|
||||||
return cb(Array.isArray(menuConfig.next) ?
|
return cb(Array.isArray(menuConfig.next) ?
|
||||||
Errors.MenuStack('No matching condition for "next"', 'NOCONDMATCH') :
|
Errors.MenuStack('No matching condition for "next"', 'NOCONDMATCH') :
|
||||||
Errors.MenuStack('Invalid or missing "next" member in menu config', 'BADNEXT')
|
Errors.MenuStack('Invalid or missing "next" member in menu config', 'BADNEXT')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(nextMenu === currentModuleInfo.name) {
|
if(nextMenu === currentModuleInfo.name) {
|
||||||
return cb(Errors.MenuStack('Menu config "next" specifies current menu', 'ALREADYTHERE'));
|
return cb(Errors.MenuStack('Menu config "next" specifies current menu', 'ALREADYTHERE'));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.goto(nextMenu, { }, cb);
|
this.goto(nextMenu, { }, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
prev(cb) {
|
prev(cb) {
|
||||||
const menuResult = this.top().instance.getMenuResult();
|
const menuResult = this.top().instance.getMenuResult();
|
||||||
|
|
||||||
// :TODO: leave() should really take a cb...
|
// :TODO: leave() should really take a cb...
|
||||||
this.pop().instance.leave(); // leave & remove current
|
this.pop().instance.leave(); // leave & remove current
|
||||||
|
|
||||||
const previousModuleInfo = this.pop(); // get previous
|
const previousModuleInfo = this.pop(); // get previous
|
||||||
|
|
||||||
if(previousModuleInfo) {
|
if(previousModuleInfo) {
|
||||||
const opts = {
|
const opts = {
|
||||||
extraArgs : previousModuleInfo.extraArgs,
|
extraArgs : previousModuleInfo.extraArgs,
|
||||||
savedState : previousModuleInfo.savedState,
|
savedState : previousModuleInfo.savedState,
|
||||||
lastMenuResult : menuResult,
|
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) {
|
goto(name, options, cb) {
|
||||||
const currentModuleInfo = this.top();
|
const currentModuleInfo = this.top();
|
||||||
|
|
||||||
if(!cb && _.isFunction(options)) {
|
if(!cb && _.isFunction(options)) {
|
||||||
cb = options;
|
cb = options;
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if(currentModuleInfo && name === currentModuleInfo.name) {
|
if(currentModuleInfo && name === currentModuleInfo.name) {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
|
cb(Errors.MenuStack('Already at supplied menu', 'ALREADYTHERE'));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
name : name,
|
name : name,
|
||||||
client : self.client,
|
client : self.client,
|
||||||
};
|
};
|
||||||
|
|
||||||
if(currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) {
|
if(currentModuleInfo && currentModuleInfo.menuFlags.includes('forwardArgs')) {
|
||||||
loadOpts.extraArgs = currentModuleInfo.extraArgs;
|
loadOpts.extraArgs = currentModuleInfo.extraArgs;
|
||||||
} else {
|
} else {
|
||||||
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
loadOpts.extraArgs = options.extraArgs || _.get(options, 'formData.value');
|
||||||
}
|
}
|
||||||
loadOpts.lastMenuResult = options.lastMenuResult;
|
loadOpts.lastMenuResult = options.lastMenuResult;
|
||||||
|
|
||||||
loadMenu(loadOpts, (err, modInst) => {
|
loadMenu(loadOpts, (err, modInst) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO: probably should just require a cb...
|
// :TODO: probably should just require a cb...
|
||||||
const errCb = cb || self.client.defaultHandlerMissingMod();
|
const errCb = cb || self.client.defaultHandlerMissingMod();
|
||||||
errCb(err);
|
errCb(err);
|
||||||
} else {
|
} else {
|
||||||
self.client.log.debug( { menuName : name }, 'Goto menu module');
|
self.client.log.debug( { menuName : name }, 'Goto menu module');
|
||||||
|
|
||||||
//
|
//
|
||||||
// If menuFlags were supplied in menu.hjson, they should win over
|
// If menuFlags were supplied in menu.hjson, they should win over
|
||||||
// anything supplied in code.
|
// anything supplied in code.
|
||||||
//
|
//
|
||||||
let menuFlags;
|
let menuFlags;
|
||||||
if(0 === modInst.menuConfig.options.menuFlags.length) {
|
if(0 === modInst.menuConfig.options.menuFlags.length) {
|
||||||
menuFlags = Array.isArray(options.menuFlags) ? options.menuFlags : [];
|
menuFlags = Array.isArray(options.menuFlags) ? options.menuFlags : [];
|
||||||
} else {
|
} else {
|
||||||
menuFlags = modInst.menuConfig.options.menuFlags;
|
menuFlags = modInst.menuConfig.options.menuFlags;
|
||||||
|
|
||||||
// in code we can ask to merge in
|
// in code we can ask to merge in
|
||||||
if(Array.isArray(options.menuFlags) && options.menuFlags.includes('mergeFlags')) {
|
if(Array.isArray(options.menuFlags) && options.menuFlags.includes('mergeFlags')) {
|
||||||
menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
|
menuFlags = _.uniq(menuFlags.concat(options.menuFlags));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(currentModuleInfo) {
|
if(currentModuleInfo) {
|
||||||
// save stack state
|
// save stack state
|
||||||
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
|
currentModuleInfo.savedState = currentModuleInfo.instance.getSaveState();
|
||||||
|
|
||||||
currentModuleInfo.instance.leave();
|
currentModuleInfo.instance.leave();
|
||||||
|
|
||||||
if(currentModuleInfo.menuFlags.includes('noHistory')) {
|
if(currentModuleInfo.menuFlags.includes('noHistory')) {
|
||||||
this.pop();
|
this.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(menuFlags.includes('popParent')) {
|
if(menuFlags.includes('popParent')) {
|
||||||
this.pop().instance.leave(); // leave & remove current
|
this.pop().instance.leave(); // leave & remove current
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.push({
|
self.push({
|
||||||
name : name,
|
name : name,
|
||||||
instance : modInst,
|
instance : modInst,
|
||||||
extraArgs : loadOpts.extraArgs,
|
extraArgs : loadOpts.extraArgs,
|
||||||
menuFlags : menuFlags,
|
menuFlags : menuFlags,
|
||||||
});
|
});
|
||||||
|
|
||||||
// restore previous state if requested
|
// restore previous state if requested
|
||||||
if(options.savedState) {
|
if(options.savedState) {
|
||||||
modInst.restoreSavedState(options.savedState);
|
modInst.restoreSavedState(options.savedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stackEntries = self.stack.map(stackEntry => {
|
const stackEntries = self.stack.map(stackEntry => {
|
||||||
let name = stackEntry.name;
|
let name = stackEntry.name;
|
||||||
if(stackEntry.instance.menuConfig.options.menuFlags.length > 0) {
|
if(stackEntry.instance.menuConfig.options.menuFlags.length > 0) {
|
||||||
name += ` (${stackEntry.instance.menuConfig.options.menuFlags.join(', ')})`;
|
name += ` (${stackEntry.instance.menuConfig.options.menuFlags.join(', ')})`;
|
||||||
}
|
}
|
||||||
return name;
|
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) {
|
if(cb) {
|
||||||
cb(null);
|
cb(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -19,243 +19,243 @@ exports.handleAction = handleAction;
|
||||||
exports.handleNext = handleNext;
|
exports.handleNext = handleNext;
|
||||||
|
|
||||||
function getMenuConfig(client, name, cb) {
|
function getMenuConfig(client, name, cb) {
|
||||||
var menuConfig;
|
var menuConfig;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function locateMenuConfig(callback) {
|
function locateMenuConfig(callback) {
|
||||||
if(_.has(client.currentTheme, [ 'menus', name ])) {
|
if(_.has(client.currentTheme, [ 'menus', name ])) {
|
||||||
menuConfig = client.currentTheme.menus[name];
|
menuConfig = client.currentTheme.menus[name];
|
||||||
callback(null);
|
callback(null);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('No menu entry for \'' + name + '\''));
|
callback(new Error('No menu entry for \'' + name + '\''));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function locatePromptConfig(callback) {
|
function locatePromptConfig(callback) {
|
||||||
if(_.isString(menuConfig.prompt)) {
|
if(_.isString(menuConfig.prompt)) {
|
||||||
if(_.has(client.currentTheme, [ 'prompts', menuConfig.prompt ])) {
|
if(_.has(client.currentTheme, [ 'prompts', menuConfig.prompt ])) {
|
||||||
menuConfig.promptConfig = client.currentTheme.prompts[menuConfig.prompt];
|
menuConfig.promptConfig = client.currentTheme.prompts[menuConfig.prompt];
|
||||||
callback(null);
|
callback(null);
|
||||||
} else {
|
} else {
|
||||||
callback(new Error('No prompt entry for \'' + menuConfig.prompt + '\''));
|
callback(new Error('No prompt entry for \'' + menuConfig.prompt + '\''));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
cb(err, menuConfig);
|
cb(err, menuConfig);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMenu(options, cb) {
|
function loadMenu(options, cb) {
|
||||||
assert(_.isObject(options));
|
assert(_.isObject(options));
|
||||||
assert(_.isString(options.name));
|
assert(_.isString(options.name));
|
||||||
assert(_.isObject(options.client));
|
assert(_.isObject(options.client));
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function getMenuConfiguration(callback) {
|
function getMenuConfiguration(callback) {
|
||||||
getMenuConfig(options.client, options.name, (err, menuConfig) => {
|
getMenuConfig(options.client, options.name, (err, menuConfig) => {
|
||||||
return callback(err, menuConfig);
|
return callback(err, menuConfig);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function loadMenuModule(menuConfig, callback) {
|
function loadMenuModule(menuConfig, callback) {
|
||||||
|
|
||||||
menuConfig.options = menuConfig.options || {};
|
menuConfig.options = menuConfig.options || {};
|
||||||
menuConfig.options.menuFlags = menuConfig.options.menuFlags || [];
|
menuConfig.options.menuFlags = menuConfig.options.menuFlags || [];
|
||||||
if(!Array.isArray(menuConfig.options.menuFlags)) {
|
if(!Array.isArray(menuConfig.options.menuFlags)) {
|
||||||
menuConfig.options.menuFlags = [ menuConfig.options.menuFlags ];
|
menuConfig.options.menuFlags = [ menuConfig.options.menuFlags ];
|
||||||
}
|
}
|
||||||
|
|
||||||
const modAsset = asset.getModuleAsset(menuConfig.module);
|
const modAsset = asset.getModuleAsset(menuConfig.module);
|
||||||
const modSupplied = null !== modAsset;
|
const modSupplied = null !== modAsset;
|
||||||
|
|
||||||
const modLoadOpts = {
|
const modLoadOpts = {
|
||||||
name : modSupplied ? modAsset.asset : 'standard_menu',
|
name : modSupplied ? modAsset.asset : 'standard_menu',
|
||||||
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config().paths.mods,
|
path : (!modSupplied || 'systemModule' === modAsset.type) ? __dirname : Config().paths.mods,
|
||||||
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
|
category : (!modSupplied || 'systemModule' === modAsset.type) ? null : 'mods',
|
||||||
};
|
};
|
||||||
|
|
||||||
moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => {
|
moduleUtil.loadModuleEx(modLoadOpts, (err, mod) => {
|
||||||
const modData = {
|
const modData = {
|
||||||
name : modLoadOpts.name,
|
name : modLoadOpts.name,
|
||||||
config : menuConfig,
|
config : menuConfig,
|
||||||
mod : mod,
|
mod : mod,
|
||||||
};
|
};
|
||||||
|
|
||||||
return callback(err, modData);
|
return callback(err, modData);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function createModuleInstance(modData, callback) {
|
function createModuleInstance(modData, callback) {
|
||||||
Log.trace(
|
Log.trace(
|
||||||
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
{ moduleName : modData.name, extraArgs : options.extraArgs, config : modData.config, info : modData.mod.modInfo },
|
||||||
'Creating menu module instance');
|
'Creating menu module instance');
|
||||||
|
|
||||||
let moduleInstance;
|
let moduleInstance;
|
||||||
try {
|
try {
|
||||||
moduleInstance = new modData.mod.getModule({
|
moduleInstance = new modData.mod.getModule({
|
||||||
menuName : options.name,
|
menuName : options.name,
|
||||||
menuConfig : modData.config,
|
menuConfig : modData.config,
|
||||||
extraArgs : options.extraArgs,
|
extraArgs : options.extraArgs,
|
||||||
client : options.client,
|
client : options.client,
|
||||||
lastMenuResult : options.lastMenuResult,
|
lastMenuResult : options.lastMenuResult,
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return callback(e);
|
return callback(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, moduleInstance);
|
return callback(null, moduleInstance);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err, modInst) => {
|
(err, modInst) => {
|
||||||
return cb(err, modInst);
|
return cb(err, modInst);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
|
function getFormConfigByIDAndMap(menuConfig, formId, mciMap, cb) {
|
||||||
assert(_.isObject(menuConfig));
|
assert(_.isObject(menuConfig));
|
||||||
|
|
||||||
if(!_.isObject(menuConfig.form)) {
|
if(!_.isObject(menuConfig.form)) {
|
||||||
cb(new Error('Invalid or missing \'form\' member for menu'));
|
cb(new Error('Invalid or missing \'form\' member for menu'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isObject(menuConfig.form[formId])) {
|
if(!_.isObject(menuConfig.form[formId])) {
|
||||||
cb(new Error('No form found for formId ' + formId));
|
cb(new Error('No form found for formId ' + formId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const formForId = menuConfig.form[formId];
|
const formForId = menuConfig.form[formId];
|
||||||
const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), (mci) => {
|
const mciReqKey = _.filter(_.map(_.sortBy(mciMap, 'code'), 'code'), (mci) => {
|
||||||
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
|
return MCIViewFactory.UserViewCodes.indexOf(mci) > -1;
|
||||||
}).join('');
|
}).join('');
|
||||||
|
|
||||||
Log.trace( { mciKey : mciReqKey }, 'Looking for MCI configuration key');
|
Log.trace( { mciKey : mciReqKey }, 'Looking for MCI configuration key');
|
||||||
|
|
||||||
//
|
//
|
||||||
// Exact, explicit match?
|
// Exact, explicit match?
|
||||||
//
|
//
|
||||||
if(_.isObject(formForId[mciReqKey])) {
|
if(_.isObject(formForId[mciReqKey])) {
|
||||||
Log.trace( { mciKey : mciReqKey }, 'Using exact configuration key match');
|
Log.trace( { mciKey : mciReqKey }, 'Using exact configuration key match');
|
||||||
cb(null, formForId[mciReqKey]);
|
cb(null, formForId[mciReqKey]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Generic match
|
// Generic match
|
||||||
//
|
//
|
||||||
if(_.has(formForId, 'mci') || _.has(formForId, 'submit')) {
|
if(_.has(formForId, 'mci') || _.has(formForId, 'submit')) {
|
||||||
Log.trace('Using generic configuration');
|
Log.trace('Using generic configuration');
|
||||||
return cb(null, formForId);
|
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...
|
// :TODO: Most of this should be moved elsewhere .... DRY...
|
||||||
function callModuleMenuMethod(client, asset, path, formData, extraArgs, cb) {
|
function callModuleMenuMethod(client, asset, path, formData, extraArgs, cb) {
|
||||||
if('' === paths.extname(path)) {
|
if('' === paths.extname(path)) {
|
||||||
path += '.js';
|
path += '.js';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
client.log.trace(
|
client.log.trace(
|
||||||
{ path : path, methodName : asset.asset, formData : formData, extraArgs : extraArgs },
|
{ path : path, methodName : asset.asset, formData : formData, extraArgs : extraArgs },
|
||||||
'Calling menu method');
|
'Calling menu method');
|
||||||
|
|
||||||
const methodMod = require(path);
|
const methodMod = require(path);
|
||||||
return methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs, cb);
|
return methodMod[asset.asset](client.currentMenuModule, formData || { }, extraArgs, cb);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method');
|
client.log.error( { error : e.toString(), methodName : asset.asset }, 'Failed to execute asset method');
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAction(client, formData, conf, cb) {
|
function handleAction(client, formData, conf, cb) {
|
||||||
assert(_.isObject(conf));
|
assert(_.isObject(conf));
|
||||||
assert(_.isString(conf.action));
|
assert(_.isString(conf.action));
|
||||||
|
|
||||||
const actionAsset = asset.parseAsset(conf.action);
|
const actionAsset = asset.parseAsset(conf.action);
|
||||||
assert(_.isObject(actionAsset));
|
assert(_.isObject(actionAsset));
|
||||||
|
|
||||||
switch(actionAsset.type) {
|
switch(actionAsset.type) {
|
||||||
case 'method' :
|
case 'method' :
|
||||||
case 'systemMethod' :
|
case 'systemMethod' :
|
||||||
if(_.isString(actionAsset.location)) {
|
if(_.isString(actionAsset.location)) {
|
||||||
return callModuleMenuMethod(
|
return callModuleMenuMethod(
|
||||||
client,
|
client,
|
||||||
actionAsset,
|
actionAsset,
|
||||||
paths.join(Config().paths.mods, actionAsset.location),
|
paths.join(Config().paths.mods, actionAsset.location),
|
||||||
formData,
|
formData,
|
||||||
conf.extraArgs,
|
conf.extraArgs,
|
||||||
cb);
|
cb);
|
||||||
} else if('systemMethod' === actionAsset.type) {
|
} else if('systemMethod' === actionAsset.type) {
|
||||||
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
|
// :TODO: Need to pass optional args here -- conf.extraArgs and args between e.g. ()
|
||||||
// :TODO: Probably better as system_method.js
|
// :TODO: Probably better as system_method.js
|
||||||
return callModuleMenuMethod(
|
return callModuleMenuMethod(
|
||||||
client,
|
client,
|
||||||
actionAsset,
|
actionAsset,
|
||||||
paths.join(__dirname, 'system_menu_method.js'),
|
paths.join(__dirname, 'system_menu_method.js'),
|
||||||
formData,
|
formData,
|
||||||
conf.extraArgs,
|
conf.extraArgs,
|
||||||
cb);
|
cb);
|
||||||
} else {
|
} else {
|
||||||
// local to current module
|
// local to current module
|
||||||
const currentModule = client.currentMenuModule;
|
const currentModule = client.currentMenuModule;
|
||||||
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
if(_.isFunction(currentModule.menuMethods[actionAsset.asset])) {
|
||||||
return currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs, cb);
|
return currentModule.menuMethods[actionAsset.asset](formData, conf.extraArgs, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
const err = new Error('Method does not exist');
|
const err = new Error('Method does not exist');
|
||||||
client.log.warn( { method : actionAsset.asset }, err.message);
|
client.log.warn( { method : actionAsset.asset }, err.message);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'menu' :
|
case 'menu' :
|
||||||
return client.currentMenuModule.gotoMenu(actionAsset.asset, { formData : formData, extraArgs : conf.extraArgs }, cb );
|
return client.currentMenuModule.gotoMenu(actionAsset.asset, { formData : formData, extraArgs : conf.extraArgs }, cb );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNext(client, nextSpec, conf, 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');
|
const nextAsset = asset.getAssetWithShorthand(nextSpec, 'menu');
|
||||||
// :TODO: getAssetWithShorthand() can return undefined - handle it!
|
// :TODO: getAssetWithShorthand() can return undefined - handle it!
|
||||||
|
|
||||||
conf = conf || {};
|
conf = conf || {};
|
||||||
const extraArgs = conf.extraArgs || {};
|
const extraArgs = conf.extraArgs || {};
|
||||||
|
|
||||||
// :TODO: DRY this with handleAction()
|
// :TODO: DRY this with handleAction()
|
||||||
switch(nextAsset.type) {
|
switch(nextAsset.type) {
|
||||||
case 'method' :
|
case 'method' :
|
||||||
case 'systemMethod' :
|
case 'systemMethod' :
|
||||||
if(_.isString(nextAsset.location)) {
|
if(_.isString(nextAsset.location)) {
|
||||||
return callModuleMenuMethod(client, nextAsset, paths.join(Config().paths.mods, nextAsset.location), {}, extraArgs, cb);
|
return callModuleMenuMethod(client, nextAsset, paths.join(Config().paths.mods, nextAsset.location), {}, extraArgs, cb);
|
||||||
} else if('systemMethod' === nextAsset.type) {
|
} else if('systemMethod' === nextAsset.type) {
|
||||||
// :TODO: see other notes about system_menu_method.js here
|
// :TODO: see other notes about system_menu_method.js here
|
||||||
return callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'), {}, extraArgs, cb);
|
return callModuleMenuMethod(client, nextAsset, paths.join(__dirname, 'system_menu_method.js'), {}, extraArgs, cb);
|
||||||
} else {
|
} else {
|
||||||
// local to current module
|
// local to current module
|
||||||
const currentModule = client.currentMenuModule;
|
const currentModule = client.currentMenuModule;
|
||||||
if(_.isFunction(currentModule.menuMethods[nextAsset.asset])) {
|
if(_.isFunction(currentModule.menuMethods[nextAsset.asset])) {
|
||||||
const formData = {}; // we don't have any
|
const formData = {}; // we don't have any
|
||||||
return currentModule.menuMethods[nextAsset.asset]( formData, extraArgs, cb );
|
return currentModule.menuMethods[nextAsset.asset]( formData, extraArgs, cb );
|
||||||
}
|
}
|
||||||
|
|
||||||
const err = new Error('Method does not exist');
|
const err = new Error('Method does not exist');
|
||||||
client.log.warn( { method : nextAsset.asset }, err.message);
|
client.log.warn( { method : nextAsset.asset }, err.message);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'menu' :
|
case 'menu' :
|
||||||
return client.currentMenuModule.gotoMenu(nextAsset.asset, { extraArgs : extraArgs }, cb );
|
return client.currentMenuModule.gotoMenu(nextAsset.asset, { extraArgs : extraArgs }, cb );
|
||||||
}
|
}
|
||||||
|
|
||||||
const err = new Error('Invalid asset type for "next"');
|
const err = new Error('Invalid asset type for "next"');
|
||||||
client.log.error( { nextSpec : nextSpec }, err.message);
|
client.log.error( { nextSpec : nextSpec }, err.message);
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,264 +14,264 @@ const _ = require('lodash');
|
||||||
exports.MenuView = MenuView;
|
exports.MenuView = MenuView;
|
||||||
|
|
||||||
function MenuView(options) {
|
function MenuView(options) {
|
||||||
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
options.acceptsFocus = miscUtil.valueWithDefault(options.acceptsFocus, true);
|
||||||
options.acceptsInput = miscUtil.valueWithDefault(options.acceptsInput, 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) {
|
if(options.items) {
|
||||||
this.setItems(options.items);
|
this.setItems(options.items);
|
||||||
} else {
|
} else {
|
||||||
this.items = [];
|
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 = options.focusedItemIndex || 0;
|
||||||
this.focusedItemIndex = this.items.length >= this.focusedItemIndex ? this.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
|
// :TODO: probably just replace this with owner draw / pipe codes / etc. more control, less specialization
|
||||||
this.focusPrefix = options.focusPrefix || '';
|
this.focusPrefix = options.focusPrefix || '';
|
||||||
this.focusSuffix = options.focusSuffix || '';
|
this.focusSuffix = options.focusSuffix || '';
|
||||||
|
|
||||||
this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1);
|
this.fillChar = miscUtil.valueWithDefault(options.fillChar, ' ').substr(0, 1);
|
||||||
this.justify = options.justify || 'none';
|
this.justify = options.justify || 'none';
|
||||||
|
|
||||||
this.hasFocusItems = function() {
|
this.hasFocusItems = function() {
|
||||||
return !_.isUndefined(self.focusItems);
|
return !_.isUndefined(self.focusItems);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getHotKeyItemIndex = function(ch) {
|
this.getHotKeyItemIndex = function(ch) {
|
||||||
if(ch && self.hotKeys) {
|
if(ch && self.hotKeys) {
|
||||||
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
|
const keyIndex = self.hotKeys[self.caseInsensitiveHotKeys ? ch.toLowerCase() : ch];
|
||||||
if(_.isNumber(keyIndex)) {
|
if(_.isNumber(keyIndex)) {
|
||||||
return keyIndex;
|
return keyIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.emitIndexUpdate = function() {
|
this.emitIndexUpdate = function() {
|
||||||
self.emit('index update', self.focusedItemIndex);
|
self.emit('index update', self.focusedItemIndex);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
util.inherits(MenuView, View);
|
util.inherits(MenuView, View);
|
||||||
|
|
||||||
MenuView.prototype.setItems = function(items) {
|
MenuView.prototype.setItems = function(items) {
|
||||||
if(Array.isArray(items)) {
|
if(Array.isArray(items)) {
|
||||||
this.sorted = false;
|
this.sorted = false;
|
||||||
this.renderCache = {};
|
this.renderCache = {};
|
||||||
|
|
||||||
//
|
//
|
||||||
// Items can be an array of strings or an array of objects.
|
// Items can be an array of strings or an array of objects.
|
||||||
//
|
//
|
||||||
// In the case of objects, items are considered complex and
|
// In the case of objects, items are considered complex and
|
||||||
// may have one or more members that can later be formatted
|
// may have one or more members that can later be formatted
|
||||||
// against. The default member is 'text'. The member 'data'
|
// against. The default member is 'text'. The member 'data'
|
||||||
// may be overridden to provide a form value other than the
|
// may be overridden to provide a form value other than the
|
||||||
// item's index.
|
// item's index.
|
||||||
//
|
//
|
||||||
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
|
// Items can be formatted with 'itemFormat' and 'focusItemFormat'
|
||||||
//
|
//
|
||||||
let text;
|
let text;
|
||||||
let stringItem;
|
let stringItem;
|
||||||
this.items = items.map(item => {
|
this.items = items.map(item => {
|
||||||
stringItem = _.isString(item);
|
stringItem = _.isString(item);
|
||||||
if(stringItem) {
|
if(stringItem) {
|
||||||
text = item;
|
text = item;
|
||||||
} else {
|
} else {
|
||||||
text = item.text || '';
|
text = item.text || '';
|
||||||
this.complexItems = true;
|
this.complexItems = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
|
text = this.disablePipe ? text : pipeToAnsi(text, this.client);
|
||||||
return Object.assign({ }, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
|
return Object.assign({ }, { text }, stringItem ? {} : item); // ensure we have a text member, plus any others
|
||||||
});
|
});
|
||||||
|
|
||||||
if(this.complexItems) {
|
if(this.complexItems) {
|
||||||
this.itemFormat = this.itemFormat || '{text}';
|
this.itemFormat = this.itemFormat || '{text}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getRenderCacheItem = function(index, focusItem = false) {
|
MenuView.prototype.getRenderCacheItem = function(index, focusItem = false) {
|
||||||
const item = this.renderCache[index];
|
const item = this.renderCache[index];
|
||||||
return item && item[focusItem ? 'focus' : 'standard'];
|
return item && item[focusItem ? 'focus' : 'standard'];
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setRenderCacheItem = function(index, rendered, focusItem = false) {
|
MenuView.prototype.setRenderCacheItem = function(index, rendered, focusItem = false) {
|
||||||
this.renderCache[index] = this.renderCache[index] || {};
|
this.renderCache[index] = this.renderCache[index] || {};
|
||||||
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
|
this.renderCache[index][focusItem ? 'focus' : 'standard'] = rendered;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setSort = function(sort) {
|
MenuView.prototype.setSort = function(sort) {
|
||||||
if(this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
|
if(this.sorted || !Array.isArray(this.items) || 0 === this.items.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = true === sort ? 'text' : sort;
|
const key = true === sort ? 'text' : sort;
|
||||||
if('text' !== sort && !this.complexItems) {
|
if('text' !== sort && !this.complexItems) {
|
||||||
return; // need a valid sort key
|
return; // need a valid sort key
|
||||||
}
|
}
|
||||||
|
|
||||||
this.items.sort( (a, b) => {
|
this.items.sort( (a, b) => {
|
||||||
const a1 = a[key];
|
const a1 = a[key];
|
||||||
const b1 = b[key];
|
const b1 = b[key];
|
||||||
if(!a1) {
|
if(!a1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if(!b1) {
|
if(!b1) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
return a1.localeCompare( b1, { sensitivity : false, numeric : true } );
|
return a1.localeCompare( b1, { sensitivity : false, numeric : true } );
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sorted = true;
|
this.sorted = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.removeItem = function(index) {
|
MenuView.prototype.removeItem = function(index) {
|
||||||
this.sorted = false;
|
this.sorted = false;
|
||||||
this.items.splice(index, 1);
|
this.items.splice(index, 1);
|
||||||
|
|
||||||
if(this.focusItems) {
|
if(this.focusItems) {
|
||||||
this.focusItems.splice(index, 1);
|
this.focusItems.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.focusedItemIndex >= index) {
|
if(this.focusedItemIndex >= index) {
|
||||||
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
|
this.focusedItemIndex = Math.max(this.focusedItemIndex - 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.positionCacheExpired = true;
|
this.positionCacheExpired = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getCount = function() {
|
MenuView.prototype.getCount = function() {
|
||||||
return this.items.length;
|
return this.items.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getItems = function() {
|
MenuView.prototype.getItems = function() {
|
||||||
if(this.complexItems) {
|
if(this.complexItems) {
|
||||||
return this.items;
|
return this.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.items.map( item => {
|
return this.items.map( item => {
|
||||||
return item.text;
|
return item.text;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.getItem = function(index) {
|
MenuView.prototype.getItem = function(index) {
|
||||||
if(this.complexItems) {
|
if(this.complexItems) {
|
||||||
return this.items[index];
|
return this.items[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.items[index].text;
|
return this.items[index].text;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusNext = function() {
|
MenuView.prototype.focusNext = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusPrevious = function() {
|
MenuView.prototype.focusPrevious = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusNextPageItem = function() {
|
MenuView.prototype.focusNextPageItem = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusPreviousPageItem = function() {
|
MenuView.prototype.focusPreviousPageItem = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusFirst = function() {
|
MenuView.prototype.focusFirst = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.focusLast = function() {
|
MenuView.prototype.focusLast = function() {
|
||||||
this.emitIndexUpdate();
|
this.emitIndexUpdate();
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setFocusItemIndex = function(index) {
|
MenuView.prototype.setFocusItemIndex = function(index) {
|
||||||
this.focusedItemIndex = index;
|
this.focusedItemIndex = index;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.onKeyPress = function(ch, key) {
|
MenuView.prototype.onKeyPress = function(ch, key) {
|
||||||
const itemIndex = this.getHotKeyItemIndex(ch);
|
const itemIndex = this.getHotKeyItemIndex(ch);
|
||||||
if(itemIndex >= 0) {
|
if(itemIndex >= 0) {
|
||||||
this.setFocusItemIndex(itemIndex);
|
this.setFocusItemIndex(itemIndex);
|
||||||
|
|
||||||
if(true === this.hotKeySubmit) {
|
if(true === this.hotKeySubmit) {
|
||||||
this.emit('action', 'accept');
|
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) {
|
MenuView.prototype.setFocusItems = function(items) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
if(items) {
|
if(items) {
|
||||||
this.focusItems = [];
|
this.focusItems = [];
|
||||||
items.forEach( itemText => {
|
items.forEach( itemText => {
|
||||||
this.focusItems.push(
|
this.focusItems.push(
|
||||||
{
|
{
|
||||||
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
text : self.disablePipe ? itemText : pipeToAnsi(itemText, self.client)
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setItemSpacing = function(itemSpacing) {
|
MenuView.prototype.setItemSpacing = function(itemSpacing) {
|
||||||
itemSpacing = parseInt(itemSpacing);
|
itemSpacing = parseInt(itemSpacing);
|
||||||
assert(_.isNumber(itemSpacing));
|
assert(_.isNumber(itemSpacing));
|
||||||
|
|
||||||
this.itemSpacing = itemSpacing;
|
this.itemSpacing = itemSpacing;
|
||||||
this.positionCacheExpired = true;
|
this.positionCacheExpired = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuView.prototype.setPropertyValue = function(propName, value) {
|
MenuView.prototype.setPropertyValue = function(propName, value) {
|
||||||
switch(propName) {
|
switch(propName) {
|
||||||
case 'itemSpacing' : this.setItemSpacing(value); break;
|
case 'itemSpacing' : this.setItemSpacing(value); break;
|
||||||
case 'items' : this.setItems(value); break;
|
case 'items' : this.setItems(value); break;
|
||||||
case 'focusItems' : this.setFocusItems(value); break;
|
case 'focusItems' : this.setFocusItems(value); break;
|
||||||
case 'hotKeys' : this.setHotKeys(value); break;
|
case 'hotKeys' : this.setHotKeys(value); break;
|
||||||
case 'hotKeySubmit' : this.hotKeySubmit = value; break;
|
case 'hotKeySubmit' : this.hotKeySubmit = value; break;
|
||||||
case 'justify' : this.justify = value; break;
|
case 'justify' : this.justify = value; break;
|
||||||
case 'focusItemIndex' : this.focusedItemIndex = value; break;
|
case 'focusItemIndex' : this.focusedItemIndex = value; break;
|
||||||
|
|
||||||
case 'itemFormat' :
|
case 'itemFormat' :
|
||||||
case 'focusItemFormat' :
|
case 'focusItemFormat' :
|
||||||
this[propName] = value;
|
this[propName] = value;
|
||||||
break;
|
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) {
|
MenuView.prototype.setHotKeys = function(hotKeys) {
|
||||||
if(_.isObject(hotKeys)) {
|
if(_.isObject(hotKeys)) {
|
||||||
if(this.caseInsensitiveHotKeys) {
|
if(this.caseInsensitiveHotKeys) {
|
||||||
this.hotKeys = {};
|
this.hotKeys = {};
|
||||||
for(var key in hotKeys) {
|
for(var key in hotKeys) {
|
||||||
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
this.hotKeys[key.toLowerCase()] = hotKeys[key];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.hotKeys = hotKeys;
|
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½
|
// ENiGMA½
|
||||||
const MenuModule = require('./menu_module.js').MenuModule;
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
const {
|
const {
|
||||||
getSortedAvailMessageConferences,
|
getSortedAvailMessageConferences,
|
||||||
getAvailableMessageAreasByConfTag,
|
getAvailableMessageAreasByConfTag,
|
||||||
getSortedAvailMessageAreasByConfTag,
|
getSortedAvailMessageAreasByConfTag,
|
||||||
} = require('./message_area.js');
|
} = require('./message_area.js');
|
||||||
const Errors = require('./enig_error.js').Errors;
|
const Errors = require('./enig_error.js').Errors;
|
||||||
const Message = require('./message.js');
|
const Message = require('./message.js');
|
||||||
|
@ -15,134 +15,134 @@ const Message = require('./message.js');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message Base Search',
|
name : 'Message Base Search',
|
||||||
desc : 'Module for quickly searching the message base',
|
desc : 'Module for quickly searching the message base',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
search : {
|
search : {
|
||||||
searchTerms : 1,
|
searchTerms : 1,
|
||||||
search : 2,
|
search : 2,
|
||||||
conf : 3,
|
conf : 3,
|
||||||
area : 4,
|
area : 4,
|
||||||
to : 5,
|
to : 5,
|
||||||
from : 6,
|
from : 6,
|
||||||
advSearch : 7,
|
advSearch : 7,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class MessageBaseSearch extends MenuModule {
|
exports.getModule = class MessageBaseSearch extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
search : (formData, extraArgs, cb) => {
|
search : (formData, extraArgs, cb) => {
|
||||||
return this.searchNow(formData, cb);
|
return this.searchNow(formData, cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.prepViewController('search', 0, mciData.menu, (err, vc) => {
|
this.prepViewController('search', 0, mciData.menu, (err, vc) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const confView = vc.getView(MciViewIds.search.conf);
|
const confView = vc.getView(MciViewIds.search.conf);
|
||||||
const areaView = vc.getView(MciViewIds.search.area);
|
const areaView = vc.getView(MciViewIds.search.area);
|
||||||
|
|
||||||
if(!confView || !areaView) {
|
if(!confView || !areaView) {
|
||||||
return cb(Errors.DoesNotExist('Missing one or more required views'));
|
return cb(Errors.DoesNotExist('Missing one or more required views'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const availConfs = [ { text : '-ALL-', data : '' } ].concat(
|
const availConfs = [ { text : '-ALL-', data : '' } ].concat(
|
||||||
getSortedAvailMessageConferences(this.client).map(conf => Object.assign(conf, { text : conf.conf.name, data : conf.confTag } )) || []
|
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);
|
confView.setItems(availConfs);
|
||||||
areaView.setItems(availAreas);
|
areaView.setItems(availAreas);
|
||||||
|
|
||||||
confView.setFocusItemIndex(0);
|
confView.setFocusItemIndex(0);
|
||||||
areaView.setFocusItemIndex(0);
|
areaView.setFocusItemIndex(0);
|
||||||
|
|
||||||
confView.on('index update', idx => {
|
confView.on('index update', idx => {
|
||||||
availAreas = [ { text : '-ALL-', data : '' } ].concat(
|
availAreas = [ { text : '-ALL-', data : '' } ].concat(
|
||||||
getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, { client : this.client }).map(
|
getSortedAvailMessageAreasByConfTag(availConfs[idx].confTag, { client : this.client }).map(
|
||||||
area => Object.assign(area, { text : area.area.name, data : area.areaTag } )
|
area => Object.assign(area, { text : area.area.name, data : area.areaTag } )
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
areaView.setItems(availAreas);
|
areaView.setItems(availAreas);
|
||||||
areaView.setFocusItemIndex(0);
|
areaView.setFocusItemIndex(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
vc.switchFocus(MciViewIds.search.searchTerms);
|
vc.switchFocus(MciViewIds.search.searchTerms);
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
searchNow(formData, cb) {
|
searchNow(formData, cb) {
|
||||||
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
const isAdvanced = formData.submitId === MciViewIds.search.advSearch;
|
||||||
const value = formData.value;
|
const value = formData.value;
|
||||||
|
|
||||||
const filter = {
|
const filter = {
|
||||||
resultType : 'messageList',
|
resultType : 'messageList',
|
||||||
sort : 'modTimestamp',
|
sort : 'modTimestamp',
|
||||||
terms : value.searchTerms,
|
terms : value.searchTerms,
|
||||||
//extraFields : [ 'area_tag', 'message_uuid', 'reply_to_message_id', 'to_user_name', 'from_user_name', 'subject', 'modified_timestamp' ],
|
//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
|
limit : 2048, // :TODO: best way to handle this? we should probably let the user know if some results are returned
|
||||||
};
|
};
|
||||||
|
|
||||||
if(isAdvanced) {
|
if(isAdvanced) {
|
||||||
filter.toUserName = value.toUserName;
|
filter.toUserName = value.toUserName;
|
||||||
filter.fromUserName = value.fromUserName;
|
filter.fromUserName = value.fromUserName;
|
||||||
|
|
||||||
if(value.confTag && !value.areaTag) {
|
if(value.confTag && !value.areaTag) {
|
||||||
// areaTag may be a string or array of strings
|
// areaTag may be a string or array of strings
|
||||||
// getAvailableMessageAreasByConfTag() returns a obj - we only need tags
|
// getAvailableMessageAreasByConfTag() returns a obj - we only need tags
|
||||||
filter.areaTag = _.map(
|
filter.areaTag = _.map(
|
||||||
getAvailableMessageAreasByConfTag(value.confTag, { client : this.client } ),
|
getAvailableMessageAreasByConfTag(value.confTag, { client : this.client } ),
|
||||||
(area, areaTag) => areaTag
|
(area, areaTag) => areaTag
|
||||||
);
|
);
|
||||||
} else if(value.areaTag) {
|
} else if(value.areaTag) {
|
||||||
filter.areaTag = value.areaTag; // specific conf + area
|
filter.areaTag = value.areaTag; // specific conf + area
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Message.findMessages(filter, (err, messageList) => {
|
Message.findMessages(filter, (err, messageList) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(0 === messageList.length) {
|
if(0 === messageList.length) {
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
this.menuConfig.config.noResultsMenu || 'messageSearchNoResults',
|
||||||
{ menuFlags : [ 'popParent' ] },
|
{ menuFlags : [ 'popParent' ] },
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const menuOpts = {
|
const menuOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
messageList,
|
messageList,
|
||||||
noUpdateLastReadId : true
|
noUpdateLastReadId : true
|
||||||
},
|
},
|
||||||
menuFlags : [ 'popParent' ],
|
menuFlags : [ 'popParent' ],
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(
|
return this.gotoMenu(
|
||||||
this.menuConfig.config.messageListMenu || 'messageAreaMessageList',
|
this.menuConfig.config.messageListMenu || 'messageAreaMessageList',
|
||||||
menuOpts,
|
menuOpts,
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,33 +10,33 @@ exports.startup = startup;
|
||||||
exports.resolveMimeType = resolveMimeType;
|
exports.resolveMimeType = resolveMimeType;
|
||||||
|
|
||||||
function startup(cb) {
|
function startup(cb) {
|
||||||
//
|
//
|
||||||
// Add in types (not yet) supported by mime-db -- and therefor, mime-types
|
// Add in types (not yet) supported by mime-db -- and therefor, mime-types
|
||||||
//
|
//
|
||||||
const ADDITIONAL_EXT_MIMETYPES = {
|
const ADDITIONAL_EXT_MIMETYPES = {
|
||||||
ans : 'text/x-ansi',
|
ans : 'text/x-ansi',
|
||||||
gz : 'application/gzip', // not in mime-types 2.1.15 :(
|
gz : 'application/gzip', // not in mime-types 2.1.15 :(
|
||||||
lzx : 'application/x-lzx', // :TODO: submit to mime-types
|
lzx : 'application/x-lzx', // :TODO: submit to mime-types
|
||||||
};
|
};
|
||||||
|
|
||||||
_.forEach(ADDITIONAL_EXT_MIMETYPES, (mimeType, ext) => {
|
_.forEach(ADDITIONAL_EXT_MIMETYPES, (mimeType, ext) => {
|
||||||
// don't override any entries
|
// don't override any entries
|
||||||
if(!_.isString(mimeTypes.types[ext])) {
|
if(!_.isString(mimeTypes.types[ext])) {
|
||||||
mimeTypes[ext] = mimeType;
|
mimeTypes[ext] = mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!mimeTypes.extensions[mimeType]) {
|
if(!mimeTypes.extensions[mimeType]) {
|
||||||
mimeTypes.extensions[mimeType] = [ ext ];
|
mimeTypes.extensions[mimeType] = [ ext ];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveMimeType(query) {
|
function resolveMimeType(query) {
|
||||||
if(mimeTypes.extensions[query]) {
|
if(mimeTypes.extensions[query]) {
|
||||||
return query; // alreaed a mime-type
|
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;
|
exports.getEnigmaUserAgent = getEnigmaUserAgent;
|
||||||
|
|
||||||
function isProduction() {
|
function isProduction() {
|
||||||
var env = process.env.NODE_ENV || 'dev';
|
var env = process.env.NODE_ENV || 'dev';
|
||||||
return 'production' === env;
|
return 'production' === env;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDevelopment() {
|
function isDevelopment() {
|
||||||
return (!(isProduction()));
|
return (!(isProduction()));
|
||||||
}
|
}
|
||||||
|
|
||||||
function valueWithDefault(val, defVal) {
|
function valueWithDefault(val, defVal) {
|
||||||
return (typeof val !== 'undefined' ? val : defVal);
|
return (typeof val !== 'undefined' ? val : defVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolvePath(path) {
|
function resolvePath(path) {
|
||||||
if(path.substr(0, 2) === '~/') {
|
if(path.substr(0, 2) === '~/') {
|
||||||
var mswCombined = process.env.HOMEDRIVE + process.env.HOMEPATH;
|
var mswCombined = process.env.HOMEDRIVE + process.env.HOMEPATH;
|
||||||
path = (process.env.HOME || mswCombined || process.env.HOMEPATH || process.env.HOMEDIR || process.cwd()) + path.substr(1);
|
path = (process.env.HOME || mswCombined || process.env.HOMEPATH || process.env.HOMEDIR || process.cwd()) + path.substr(1);
|
||||||
}
|
}
|
||||||
return paths.resolve(path);
|
return paths.resolve(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCleanEnigmaVersion() {
|
function getCleanEnigmaVersion() {
|
||||||
return packageJson.version
|
return packageJson.version
|
||||||
.replace(/-/g, '.')
|
.replace(/-/g, '.')
|
||||||
.replace(/alpha/,'a')
|
.replace(/alpha/,'a')
|
||||||
.replace(/beta/,'b')
|
.replace(/beta/,'b')
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
// See also ftn_util.js getTearLine() & getProductIdentifier()
|
// See also ftn_util.js getTearLine() & getProductIdentifier()
|
||||||
function getEnigmaUserAgent() {
|
function getEnigmaUserAgent() {
|
||||||
// can't have 1/2 or ½ in User-Agent according to RFC 1945 :(
|
// can't have 1/2 or ½ in User-Agent according to RFC 1945 :(
|
||||||
const version = getCleanEnigmaVersion();
|
const version = getCleanEnigmaVersion();
|
||||||
const nodeVer = process.version.substr(1); // remove 'v' prefix
|
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 {
|
exports.MessageAreaConfTempSwitcher = Sup => class extends Sup {
|
||||||
|
|
||||||
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
|
tempMessageConfAndAreaSwitch(messageAreaTag, recordPrevious = true) {
|
||||||
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
|
messageAreaTag = messageAreaTag || get(this, 'config.messageAreaTag', this.messageAreaTag);
|
||||||
if(!messageAreaTag) {
|
if(!messageAreaTag) {
|
||||||
return; // nothing to do!
|
return; // nothing to do!
|
||||||
}
|
}
|
||||||
|
|
||||||
if(recordPrevious) {
|
if(recordPrevious) {
|
||||||
this.prevMessageConfAndArea = {
|
this.prevMessageConfAndArea = {
|
||||||
confTag : this.client.user.properties.message_conf_tag,
|
confTag : this.client.user.properties.message_conf_tag,
|
||||||
areaTag : this.client.user.properties.message_area_tag,
|
areaTag : this.client.user.properties.message_area_tag,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) {
|
if(!messageArea.tempChangeMessageConfAndArea(this.client, messageAreaTag)) {
|
||||||
this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch');
|
this.client.log.warn( { messageAreaTag : messageArea }, 'Failed to perform temporary message area/conf switch');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tempMessageConfAndAreaRestore() {
|
tempMessageConfAndAreaRestore() {
|
||||||
if(this.prevMessageConfAndArea) {
|
if(this.prevMessageConfAndArea) {
|
||||||
this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag;
|
this.client.user.properties.message_conf_tag = this.prevMessageConfAndArea.confTag;
|
||||||
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
this.client.user.properties.message_area_tag = this.prevMessageConfAndArea.areaTag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,93 +18,93 @@ exports.loadModulesForCategory = loadModulesForCategory;
|
||||||
exports.getModulePaths = getModulePaths;
|
exports.getModulePaths = getModulePaths;
|
||||||
|
|
||||||
function loadModuleEx(options, cb) {
|
function loadModuleEx(options, cb) {
|
||||||
assert(_.isObject(options));
|
assert(_.isObject(options));
|
||||||
assert(_.isString(options.name));
|
assert(_.isString(options.name));
|
||||||
assert(_.isString(options.path));
|
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) {
|
if(_.isObject(modConfig) && false === modConfig.enabled) {
|
||||||
const err = new Error(`Module "${options.name}" is disabled`);
|
const err = new Error(`Module "${options.name}" is disabled`);
|
||||||
err.code = 'EENIGMODDISABLED';
|
err.code = 'EENIGMODDISABLED';
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Modules are allowed to live in /path/to/<moduleName>/<moduleName>.js or
|
// Modules are allowed to live in /path/to/<moduleName>/<moduleName>.js or
|
||||||
// simply in /path/to/<moduleName>.js. This allows for more advanced modules
|
// simply in /path/to/<moduleName>.js. This allows for more advanced modules
|
||||||
// to have their own containing folder, package.json & dependencies, etc.
|
// to have their own containing folder, package.json & dependencies, etc.
|
||||||
//
|
//
|
||||||
let mod;
|
let mod;
|
||||||
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
|
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
|
||||||
try {
|
try {
|
||||||
mod = require(modPath);
|
mod = require(modPath);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if('MODULE_NOT_FOUND' === e.code) {
|
if('MODULE_NOT_FOUND' === e.code) {
|
||||||
modPath = paths.join(options.path, options.name, `${options.name}.js`);
|
modPath = paths.join(options.path, options.name, `${options.name}.js`);
|
||||||
try {
|
try {
|
||||||
mod = require(modPath);
|
mod = require(modPath);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isObject(mod.moduleInfo)) {
|
if(!_.isObject(mod.moduleInfo)) {
|
||||||
return cb(new Error('Module is missing "moduleInfo" section'));
|
return cb(new Error('Module is missing "moduleInfo" section'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!_.isFunction(mod.getModule)) {
|
if(!_.isFunction(mod.getModule)) {
|
||||||
return cb(new Error('Invalid or missing "getModule" method for module!'));
|
return cb(new Error('Invalid or missing "getModule" method for module!'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null, mod);
|
return cb(null, mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadModule(name, category, cb) {
|
function loadModule(name, category, cb) {
|
||||||
const path = Config().paths[category];
|
const path = Config().paths[category];
|
||||||
|
|
||||||
if(!_.isString(path)) {
|
if(!_.isString(path)) {
|
||||||
return cb(new Error(`Not sure where to look for "${name}" of category "${category}"`));
|
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) {
|
loadModuleEx( { name : name, path : path, category : category }, function loaded(err, mod) {
|
||||||
return cb(err, mod);
|
return cb(err, mod);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadModulesForCategory(category, iterator, complete) {
|
function loadModulesForCategory(category, iterator, complete) {
|
||||||
|
|
||||||
fs.readdir(Config().paths[category], (err, files) => {
|
fs.readdir(Config().paths[category], (err, files) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return iterator(err);
|
return iterator(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsModules = files.filter(file => {
|
const jsModules = files.filter(file => {
|
||||||
return '.js' === paths.extname(file);
|
return '.js' === paths.extname(file);
|
||||||
});
|
});
|
||||||
|
|
||||||
async.each(jsModules, (file, next) => {
|
async.each(jsModules, (file, next) => {
|
||||||
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
|
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
|
||||||
iterator(err, mod);
|
iterator(err, mod);
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
}, err => {
|
}, err => {
|
||||||
if(complete) {
|
if(complete) {
|
||||||
return complete(err);
|
return complete(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getModulePaths() {
|
function getModulePaths() {
|
||||||
const config = Config();
|
const config = Config();
|
||||||
return [
|
return [
|
||||||
config.paths.mods,
|
config.paths.mods,
|
||||||
config.paths.loginServers,
|
config.paths.loginServers,
|
||||||
config.paths.contentServers,
|
config.paths.contentServers,
|
||||||
config.paths.scannerTossers,
|
config.paths.scannerTossers,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,9 @@ const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message Area List',
|
name : 'Message Area List',
|
||||||
desc : 'Module for listing / choosing message areas',
|
desc : 'Module for listing / choosing message areas',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -35,73 +35,73 @@ exports.moduleInfo = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
AreaList : 1,
|
AreaList : 1,
|
||||||
SelAreaInfo1 : 2,
|
SelAreaInfo1 : 2,
|
||||||
SelAreaInfo2 : 3,
|
SelAreaInfo2 : 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class MessageAreaListModule extends MenuModule {
|
exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
|
this.messageAreas = messageArea.getSortedAvailMessageAreasByConfTag(
|
||||||
this.client.user.properties.message_conf_tag,
|
this.client.user.properties.message_conf_tag,
|
||||||
{ client : this.client }
|
{ client : this.client }
|
||||||
);
|
);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
changeArea : function(formData, extraArgs, cb) {
|
changeArea : function(formData, extraArgs, cb) {
|
||||||
if(1 === formData.submitId) {
|
if(1 === formData.submitId) {
|
||||||
let area = self.messageAreas[formData.value.area];
|
let area = self.messageAreas[formData.value.area];
|
||||||
const areaTag = area.areaTag;
|
const areaTag = area.areaTag;
|
||||||
area = area.area; // what we want is actually embedded
|
area = area.area; // what we want is actually embedded
|
||||||
|
|
||||||
messageArea.changeMessageArea(self.client, areaTag, err => {
|
messageArea.changeMessageArea(self.client, areaTag, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`);
|
self.client.term.pipeWrite(`\n|00Cannot change area: ${err.message}\n`);
|
||||||
|
|
||||||
self.prevMenuOnTimeout(1000, cb);
|
self.prevMenuOnTimeout(1000, cb);
|
||||||
} else {
|
} else {
|
||||||
if(_.isString(area.art)) {
|
if(_.isString(area.art)) {
|
||||||
const dispOptions = {
|
const dispOptions = {
|
||||||
client : self.client,
|
client : self.client,
|
||||||
name : area.art,
|
name : area.art,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.client.term.rawWrite(resetScreen());
|
self.client.term.rawWrite(resetScreen());
|
||||||
|
|
||||||
displayThemeArt(dispOptions, () => {
|
displayThemeArt(dispOptions, () => {
|
||||||
// pause by default, unless explicitly told not to
|
// pause by default, unless explicitly told not to
|
||||||
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
if(_.has(area, 'options.pause') && false === area.options.pause) {
|
||||||
return self.prevMenuOnTimeout(1000, cb);
|
return self.prevMenuOnTimeout(1000, cb);
|
||||||
} else {
|
} else {
|
||||||
self.pausePrompt( () => {
|
self.pausePrompt( () => {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
prevMenuOnTimeout(timeout, cb) {
|
prevMenuOnTimeout(timeout, cb) {
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return this.prevMenu(cb);
|
return this.prevMenu(cb);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
|
// :TODO: these concepts have been replaced with the {someKey} style formatting - update me!
|
||||||
updateGeneralAreaInfoViews(areaIndex) {
|
updateGeneralAreaInfoViews(areaIndex) {
|
||||||
/*
|
/*
|
||||||
const areaInfo = self.messageAreas[areaIndex];
|
const areaInfo = self.messageAreas[areaIndex];
|
||||||
|
|
||||||
[ MciViewIds.SelAreaInfo1, MciViewIds.SelAreaInfo2 ].forEach(mciId => {
|
[ MciViewIds.SelAreaInfo1, MciViewIds.SelAreaInfo2 ].forEach(mciId => {
|
||||||
|
@ -111,71 +111,71 @@ exports.getModule = class MessageAreaListModule extends MenuModule {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.menu,
|
mciMap : mciData.menu,
|
||||||
formId : 0,
|
formId : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) {
|
vc.loadFromMenuConfig(loadOpts, function startingViewReady(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function populateAreaListView(callback) {
|
function populateAreaListView(callback) {
|
||||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||||
|
|
||||||
const areaListView = vc.getView(MciViewIds.AreaList);
|
const areaListView = vc.getView(MciViewIds.AreaList);
|
||||||
if(!areaListView) {
|
if(!areaListView) {
|
||||||
return callback(Errors.MissingMci('A MenuView compatible MCI code is required'));
|
return callback(Errors.MissingMci('A MenuView compatible MCI code is required'));
|
||||||
}
|
}
|
||||||
let i = 1;
|
let i = 1;
|
||||||
areaListView.setItems(_.map(self.messageAreas, v => {
|
areaListView.setItems(_.map(self.messageAreas, v => {
|
||||||
return stringFormat(listFormat, {
|
return stringFormat(listFormat, {
|
||||||
index : i++,
|
index : i++,
|
||||||
areaTag : v.area.areaTag,
|
areaTag : v.area.areaTag,
|
||||||
name : v.area.name,
|
name : v.area.name,
|
||||||
desc : v.area.desc,
|
desc : v.area.desc,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
i = 1;
|
i = 1;
|
||||||
areaListView.setFocusItems(_.map(self.messageAreas, v => {
|
areaListView.setFocusItems(_.map(self.messageAreas, v => {
|
||||||
return stringFormat(focusListFormat, {
|
return stringFormat(focusListFormat, {
|
||||||
index : i++,
|
index : i++,
|
||||||
areaTag : v.area.areaTag,
|
areaTag : v.area.areaTag,
|
||||||
name : v.area.name,
|
name : v.area.name,
|
||||||
desc : v.area.desc,
|
desc : v.area.desc,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
areaListView.on('index update', areaIndex => {
|
areaListView.on('index update', areaIndex => {
|
||||||
self.updateGeneralAreaInfoViews(areaIndex);
|
self.updateGeneralAreaInfoViews(areaIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
areaListView.redraw();
|
areaListView.redraw();
|
||||||
|
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,60 +8,60 @@ const _ = require('lodash');
|
||||||
const async = require('async');
|
const async = require('async');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message Area Post',
|
name : 'Message Area Post',
|
||||||
desc : 'Module for posting a new message to an area',
|
desc : 'Module for posting a new message to an area',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
exports.getModule = class AreaPostFSEModule extends FullScreenEditorModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// we're posting, so always start with 'edit' mode
|
// we're posting, so always start with 'edit' mode
|
||||||
this.editorMode = 'edit';
|
this.editorMode = 'edit';
|
||||||
|
|
||||||
this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) {
|
this.menuMethods.editModeMenuSave = function(formData, extraArgs, cb) {
|
||||||
|
|
||||||
var msg;
|
var msg;
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function getMessageObject(callback) {
|
function getMessageObject(callback) {
|
||||||
self.getMessage(function gotMsg(err, msgObj) {
|
self.getMessage(function gotMsg(err, msgObj) {
|
||||||
msg = msgObj;
|
msg = msgObj;
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function saveMessage(callback) {
|
function saveMessage(callback) {
|
||||||
return persistMessage(msg, callback);
|
return persistMessage(msg, callback);
|
||||||
},
|
},
|
||||||
function updateStats(callback) {
|
function updateStats(callback) {
|
||||||
self.updateUserStats(callback);
|
self.updateUserStats(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO:... sooooo now what?
|
// :TODO:... sooooo now what?
|
||||||
} else {
|
} else {
|
||||||
// note: not logging 'from' here as it's part of client.log.xxxx()
|
// note: not logging 'from' here as it's part of client.log.xxxx()
|
||||||
self.client.log.info(
|
self.client.log.info(
|
||||||
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
|
{ to : msg.toUserName, subject : msg.subject, uuid : msg.uuid },
|
||||||
'Message persisted'
|
'Message persisted'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.nextMenu(cb);
|
return self.nextMenu(cb);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
enter() {
|
enter() {
|
||||||
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
|
if(_.isString(this.client.user.properties.message_area_tag) && !_.isString(this.messageAreaTag)) {
|
||||||
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
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.getModule = AreaReplyFSEModule;
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message Area Reply',
|
name : 'Message Area Reply',
|
||||||
desc : 'Module for replying to an area message',
|
desc : 'Module for replying to an area message',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
function AreaReplyFSEModule(options) {
|
function AreaReplyFSEModule(options) {
|
||||||
FullScreenEditorModule.call(this, options);
|
FullScreenEditorModule.call(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(AreaReplyFSEModule, FullScreenEditorModule);
|
require('util').inherits(AreaReplyFSEModule, FullScreenEditorModule);
|
||||||
|
|
|
@ -9,137 +9,137 @@ const Message = require('./message.js');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message Area View',
|
name : 'Message Area View',
|
||||||
desc : 'Module for viewing an area message',
|
desc : 'Module for viewing an area message',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
exports.getModule = class AreaViewFSEModule extends FullScreenEditorModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.editorType = 'area';
|
this.editorType = 'area';
|
||||||
this.editorMode = 'view';
|
this.editorMode = 'view';
|
||||||
|
|
||||||
if(_.isObject(options.extraArgs)) {
|
if(_.isObject(options.extraArgs)) {
|
||||||
this.messageList = options.extraArgs.messageList;
|
this.messageList = options.extraArgs.messageList;
|
||||||
this.messageIndex = options.extraArgs.messageIndex;
|
this.messageIndex = options.extraArgs.messageIndex;
|
||||||
this.lastMessageNextExit = options.extraArgs.lastMessageNextExit;
|
this.lastMessageNextExit = options.extraArgs.lastMessageNextExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messageList = this.messageList || [];
|
this.messageList = this.messageList || [];
|
||||||
this.messageIndex = this.messageIndex || 0;
|
this.messageIndex = this.messageIndex || 0;
|
||||||
this.messageTotal = this.messageList.length;
|
this.messageTotal = this.messageList.length;
|
||||||
|
|
||||||
if(this.messageList.length > 0) {
|
if(this.messageList.length > 0) {
|
||||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
// assign *additional* menuMethods
|
// assign *additional* menuMethods
|
||||||
Object.assign(this.menuMethods, {
|
Object.assign(this.menuMethods, {
|
||||||
nextMessage : (formData, extraArgs, cb) => {
|
nextMessage : (formData, extraArgs, cb) => {
|
||||||
if(self.messageIndex + 1 < self.messageList.length) {
|
if(self.messageIndex + 1 < self.messageList.length) {
|
||||||
self.messageIndex++;
|
self.messageIndex++;
|
||||||
|
|
||||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
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.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?
|
// auto-exit if no more to go?
|
||||||
if(self.lastMessageNextExit) {
|
if(self.lastMessageNextExit) {
|
||||||
self.lastMessageReached = true;
|
self.lastMessageReached = true;
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
prevMessage : (formData, extraArgs, cb) => {
|
prevMessage : (formData, extraArgs, cb) => {
|
||||||
if(self.messageIndex > 0) {
|
if(self.messageIndex > 0) {
|
||||||
self.messageIndex--;
|
self.messageIndex--;
|
||||||
|
|
||||||
this.messageAreaTag = this.messageList[this.messageIndex].areaTag;
|
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.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) => {
|
movementKeyPressed : (formData, extraArgs, cb) => {
|
||||||
const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic #
|
const bodyView = self.viewControllers.body.getView(1); // :TODO: use const here vs magic #
|
||||||
|
|
||||||
// :TODO: Create methods for up/down vs using keyPressXXXXX
|
// :TODO: Create methods for up/down vs using keyPressXXXXX
|
||||||
switch(formData.key.name) {
|
switch(formData.key.name) {
|
||||||
case 'down arrow' : bodyView.scrollDocumentUp(); break;
|
case 'down arrow' : bodyView.scrollDocumentUp(); break;
|
||||||
case 'up arrow' : bodyView.scrollDocumentDown(); break;
|
case 'up arrow' : bodyView.scrollDocumentDown(); break;
|
||||||
case 'page up' : bodyView.keyPressPageUp(); break;
|
case 'page up' : bodyView.keyPressPageUp(); break;
|
||||||
case 'page down' : bodyView.keyPressPageDown(); break;
|
case 'page down' : bodyView.keyPressPageDown(); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: need to stop down/page down if doing so would push the last
|
// :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...
|
// visible page off the screen at all .... this should be handled by MLTEV though...
|
||||||
|
|
||||||
return cb(null);
|
return cb(null);
|
||||||
},
|
},
|
||||||
|
|
||||||
replyMessage : (formData, extraArgs, cb) => {
|
replyMessage : (formData, extraArgs, cb) => {
|
||||||
if(_.isString(extraArgs.menu)) {
|
if(_.isString(extraArgs.menu)) {
|
||||||
const modOpts = {
|
const modOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
messageAreaTag : self.messageAreaTag,
|
messageAreaTag : self.messageAreaTag,
|
||||||
replyToMessage : self.message,
|
replyToMessage : self.message,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return self.gotoMenu(extraArgs.menu, modOpts, cb);
|
return self.gotoMenu(extraArgs.menu, modOpts, cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
self.client.log(extraArgs, 'Missing extraArgs.menu');
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
loadMessageByUuid(uuid, cb) {
|
loadMessageByUuid(uuid, cb) {
|
||||||
const msg = new Message();
|
const msg = new Message();
|
||||||
msg.load( { uuid : uuid, user : this.client.user }, () => {
|
msg.load( { uuid : uuid, user : this.client.user }, () => {
|
||||||
this.setMessage(msg);
|
this.setMessage(msg);
|
||||||
|
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
finishedLoading() {
|
finishedLoading() {
|
||||||
this.loadMessageByUuid(this.messageList[this.messageIndex].messageUuid);
|
this.loadMessageByUuid(this.messageList[this.messageIndex].messageUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaveState() {
|
getSaveState() {
|
||||||
return {
|
return {
|
||||||
messageList : this.messageList,
|
messageList : this.messageList,
|
||||||
messageIndex : this.messageIndex,
|
messageIndex : this.messageIndex,
|
||||||
messageTotal : this.messageList.length,
|
messageTotal : this.messageList.length,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreSavedState(savedState) {
|
restoreSavedState(savedState) {
|
||||||
this.messageList = savedState.messageList;
|
this.messageList = savedState.messageList;
|
||||||
this.messageIndex = savedState.messageIndex;
|
this.messageIndex = savedState.messageIndex;
|
||||||
this.messageTotal = savedState.messageTotal;
|
this.messageTotal = savedState.messageTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuResult() {
|
getMenuResult() {
|
||||||
return {
|
return {
|
||||||
messageIndex : this.messageIndex,
|
messageIndex : this.messageIndex,
|
||||||
lastMessageReached : this.lastMessageReached,
|
lastMessageReached : this.lastMessageReached,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,135 +14,135 @@ const async = require('async');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message Conference List',
|
name : 'Message Conference List',
|
||||||
desc : 'Module for listing / choosing message conferences',
|
desc : 'Module for listing / choosing message conferences',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
ConfList : 1,
|
ConfList : 1,
|
||||||
|
|
||||||
// :TODO:
|
// :TODO:
|
||||||
// # areas in conf .... see Obv/2, iNiQ, ...
|
// # areas in conf .... see Obv/2, iNiQ, ...
|
||||||
//
|
//
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class MessageConfListModule extends MenuModule {
|
exports.getModule = class MessageConfListModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client);
|
this.messageConfs = messageArea.getSortedAvailMessageConferences(this.client);
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
changeConference : function(formData, extraArgs, cb) {
|
changeConference : function(formData, extraArgs, cb) {
|
||||||
if(1 === formData.submitId) {
|
if(1 === formData.submitId) {
|
||||||
let conf = self.messageConfs[formData.value.conf];
|
let conf = self.messageConfs[formData.value.conf];
|
||||||
const confTag = conf.confTag;
|
const confTag = conf.confTag;
|
||||||
conf = conf.conf; // what we want is embedded
|
conf = conf.conf; // what we want is embedded
|
||||||
|
|
||||||
messageArea.changeMessageConference(self.client, confTag, err => {
|
messageArea.changeMessageConference(self.client, confTag, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
|
self.client.term.pipeWrite(`\n|00Cannot change conference: ${err.message}\n`);
|
||||||
|
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
if(_.isString(conf.art)) {
|
if(_.isString(conf.art)) {
|
||||||
const dispOptions = {
|
const dispOptions = {
|
||||||
client : self.client,
|
client : self.client,
|
||||||
name : conf.art,
|
name : conf.art,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.client.term.rawWrite(resetScreen());
|
self.client.term.rawWrite(resetScreen());
|
||||||
|
|
||||||
displayThemeArt(dispOptions, () => {
|
displayThemeArt(dispOptions, () => {
|
||||||
// pause by default, unless explicitly told not to
|
// pause by default, unless explicitly told not to
|
||||||
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
if(_.has(conf, 'options.pause') && false === conf.options.pause) {
|
||||||
return self.prevMenuOnTimeout(1000, cb);
|
return self.prevMenuOnTimeout(1000, cb);
|
||||||
} else {
|
} else {
|
||||||
self.pausePrompt( () => {
|
self.pausePrompt( () => {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
prevMenuOnTimeout(timeout, cb) {
|
prevMenuOnTimeout(timeout, cb) {
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
return this.prevMenu(cb);
|
return this.prevMenu(cb);
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.areaList = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
let loadOpts = {
|
let loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.menu,
|
mciMap : mciData.menu,
|
||||||
formId : 0,
|
formId : 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
vc.loadFromMenuConfig(loadOpts, callback);
|
vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
},
|
},
|
||||||
function populateConfListView(callback) {
|
function populateConfListView(callback) {
|
||||||
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
const listFormat = self.menuConfig.config.listFormat || '{index} ) - {name}';
|
||||||
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
const focusListFormat = self.menuConfig.config.focusListFormat || listFormat;
|
||||||
|
|
||||||
const confListView = vc.getView(MciViewIds.ConfList);
|
const confListView = vc.getView(MciViewIds.ConfList);
|
||||||
let i = 1;
|
let i = 1;
|
||||||
confListView.setItems(_.map(self.messageConfs, v => {
|
confListView.setItems(_.map(self.messageConfs, v => {
|
||||||
return stringFormat(listFormat, {
|
return stringFormat(listFormat, {
|
||||||
index : i++,
|
index : i++,
|
||||||
confTag : v.conf.confTag,
|
confTag : v.conf.confTag,
|
||||||
name : v.conf.name,
|
name : v.conf.name,
|
||||||
desc : v.conf.desc,
|
desc : v.conf.desc,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
i = 1;
|
i = 1;
|
||||||
confListView.setFocusItems(_.map(self.messageConfs, v => {
|
confListView.setFocusItems(_.map(self.messageConfs, v => {
|
||||||
return stringFormat(focusListFormat, {
|
return stringFormat(focusListFormat, {
|
||||||
index : i++,
|
index : i++,
|
||||||
confTag : v.conf.confTag,
|
confTag : v.conf.confTag,
|
||||||
name : v.conf.name,
|
name : v.conf.name,
|
||||||
desc : v.conf.desc,
|
desc : v.conf.desc,
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
confListView.redraw();
|
confListView.redraw();
|
||||||
|
|
||||||
callback(null);
|
callback(null);
|
||||||
},
|
},
|
||||||
function populateTextViews(callback) {
|
function populateTextViews(callback) {
|
||||||
// :TODO: populate other avail MCI, e.g. current conf name
|
// :TODO: populate other avail MCI, e.g. current conf name
|
||||||
callback(null);
|
callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
function complete(err) {
|
||||||
cb(err);
|
cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
376
core/msg_list.js
376
core/msg_list.js
|
@ -30,229 +30,229 @@ const moment = require('moment');
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Message List',
|
name : 'Message List',
|
||||||
desc : 'Module for listing/browsing available messages',
|
desc : 'Module for listing/browsing available messages',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
msgList : 1, // VM1
|
msgList : 1, // VM1
|
||||||
msgInfo1 : 2, // TL2
|
msgInfo1 : 2, // TL2
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
|
exports.getModule = class MessageListModule extends MessageAreaConfTempSwitcher(MenuModule) {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
// :TODO: consider this pattern in base MenuModule - clean up code all over
|
// :TODO: consider this pattern in base MenuModule - clean up code all over
|
||||||
this.config = Object.assign({}, _.get(options, 'menuConfig.config'), options.extraArgs);
|
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 = {
|
this.menuMethods = {
|
||||||
selectMessage : (formData, extraArgs, cb) => {
|
selectMessage : (formData, extraArgs, cb) => {
|
||||||
if(MciViewIds.msgList === formData.submitId) {
|
if(MciViewIds.msgList === formData.submitId) {
|
||||||
this.initialFocusIndex = formData.value.message;
|
this.initialFocusIndex = formData.value.message;
|
||||||
|
|
||||||
const modOpts = {
|
const modOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
messageAreaTag : this.getSelectedAreaTag(formData.value.message),// this.config.messageAreaTag,
|
messageAreaTag : this.getSelectedAreaTag(formData.value.message),// this.config.messageAreaTag,
|
||||||
messageList : this.config.messageList,
|
messageList : this.config.messageList,
|
||||||
messageIndex : formData.value.message,
|
messageIndex : formData.value.message,
|
||||||
lastMessageNextExit : true,
|
lastMessageNextExit : true,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if(_.isBoolean(this.config.noUpdateLastReadId)) {
|
if(_.isBoolean(this.config.noUpdateLastReadId)) {
|
||||||
modOpts.extraArgs.noUpdateLastReadId = this.config.noUpdateLastReadId;
|
modOpts.extraArgs.noUpdateLastReadId = this.config.noUpdateLastReadId;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Provide a serializer so we don't dump *huge* bits of information to the log
|
// 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
|
// due to the size of |messageList|. See https://github.com/trentm/node-bunyan/issues/189
|
||||||
//
|
//
|
||||||
const self = this;
|
const self = this;
|
||||||
modOpts.extraArgs.toJSON = function() {
|
modOpts.extraArgs.toJSON = function() {
|
||||||
const logMsgList = (self.config.messageList.length <= 4) ?
|
const logMsgList = (self.config.messageList.length <= 4) ?
|
||||||
self.config.messageList :
|
self.config.messageList :
|
||||||
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2));
|
self.config.messageList.slice(0, 2).concat(self.config.messageList.slice(-2));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// note |this| is scope of toJSON()!
|
// note |this| is scope of toJSON()!
|
||||||
messageAreaTag : this.messageAreaTag,
|
messageAreaTag : this.messageAreaTag,
|
||||||
apprevMessageList : logMsgList,
|
apprevMessageList : logMsgList,
|
||||||
messageCount : this.messageList.length,
|
messageCount : this.messageList.length,
|
||||||
messageIndex : this.messageIndex,
|
messageIndex : this.messageIndex,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(this.config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
|
return this.gotoMenu(this.config.menuViewPost || 'messageAreaViewPost', modOpts, cb);
|
||||||
} else {
|
} else {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
fullExit : (formData, extraArgs, cb) => {
|
fullExit : (formData, extraArgs, cb) => {
|
||||||
this.menuResult = { fullExit : true };
|
this.menuResult = { fullExit : true };
|
||||||
return this.prevMenu(cb);
|
return this.prevMenu(cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getSelectedAreaTag(listIndex) {
|
getSelectedAreaTag(listIndex) {
|
||||||
return this.config.messageList[listIndex].areaTag || this.config.messageAreaTag;
|
return this.config.messageList[listIndex].areaTag || this.config.messageAreaTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
enter() {
|
enter() {
|
||||||
if(this.lastMessageReachedExit) {
|
if(this.lastMessageReachedExit) {
|
||||||
return this.prevMenu();
|
return this.prevMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
super.enter();
|
super.enter();
|
||||||
|
|
||||||
//
|
//
|
||||||
// Config can specify |messageAreaTag| else it comes from
|
// Config can specify |messageAreaTag| else it comes from
|
||||||
// the user's current area. If |messageList| is supplied,
|
// the user's current area. If |messageList| is supplied,
|
||||||
// each item is expected to contain |areaTag|, so we use that
|
// each item is expected to contain |areaTag|, so we use that
|
||||||
// instead in those cases.
|
// instead in those cases.
|
||||||
//
|
//
|
||||||
if(!Array.isArray(this.config.messageList)) {
|
if(!Array.isArray(this.config.messageList)) {
|
||||||
if(this.config.messageAreaTag) {
|
if(this.config.messageAreaTag) {
|
||||||
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
|
this.tempMessageConfAndAreaSwitch(this.config.messageAreaTag);
|
||||||
} else {
|
} else {
|
||||||
this.config.messageAreaTag = this.client.user.properties.message_area_tag;
|
this.config.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
leave() {
|
leave() {
|
||||||
this.tempMessageConfAndAreaRestore();
|
this.tempMessageConfAndAreaRestore();
|
||||||
super.leave();
|
super.leave();
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||||
let configProvidedMessageList = false;
|
let configProvidedMessageList = false;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.menu
|
mciMap : mciData.menu
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
},
|
},
|
||||||
function fetchMessagesInArea(callback) {
|
function fetchMessagesInArea(callback) {
|
||||||
//
|
//
|
||||||
// Config can supply messages else we'll need to populate the list now
|
// Config can supply messages else we'll need to populate the list now
|
||||||
//
|
//
|
||||||
if(_.isArray(self.config.messageList)) {
|
if(_.isArray(self.config.messageList)) {
|
||||||
configProvidedMessageList = true;
|
configProvidedMessageList = true;
|
||||||
return callback(0 === self.config.messageList.length ? new Error('No messages in area') : null);
|
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) {
|
messageArea.getMessageListForArea(self.client, self.config.messageAreaTag, function msgs(err, msgList) {
|
||||||
if(!msgList || 0 === msgList.length) {
|
if(!msgList || 0 === msgList.length) {
|
||||||
return callback(new Error('No messages in area'));
|
return callback(new Error('No messages in area'));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config.messageList = msgList;
|
self.config.messageList = msgList;
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function getLastReadMesageId(callback) {
|
function getLastReadMesageId(callback) {
|
||||||
// messageList entries can contain |isNew| if they want to be considered new
|
// messageList entries can contain |isNew| if they want to be considered new
|
||||||
if(configProvidedMessageList) {
|
if(configProvidedMessageList) {
|
||||||
self.lastReadId = 0;
|
self.lastReadId = 0;
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.config.messageAreaTag, function lastRead(err, lastReadId) {
|
messageArea.getMessageAreaLastReadId(self.client.user.userId, self.config.messageAreaTag, function lastRead(err, lastReadId) {
|
||||||
self.lastReadId = lastReadId || 0;
|
self.lastReadId = lastReadId || 0;
|
||||||
return callback(null); // ignore any errors, e.g. missing value
|
return callback(null); // ignore any errors, e.g. missing value
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function updateMessageListObjects(callback) {
|
function updateMessageListObjects(callback) {
|
||||||
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || self.client.currentTheme.helpers.getDateTimeFormat();
|
const dateTimeFormat = self.menuConfig.config.dateTimeFormat || self.client.currentTheme.helpers.getDateTimeFormat();
|
||||||
const newIndicator = self.menuConfig.config.newIndicator || '*';
|
const newIndicator = self.menuConfig.config.newIndicator || '*';
|
||||||
const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues
|
const regIndicator = new Array(newIndicator.length + 1).join(' '); // fill with space to avoid draw issues
|
||||||
|
|
||||||
let msgNum = 1;
|
let msgNum = 1;
|
||||||
self.config.messageList.forEach( (listItem, index) => {
|
self.config.messageList.forEach( (listItem, index) => {
|
||||||
listItem.msgNum = msgNum++;
|
listItem.msgNum = msgNum++;
|
||||||
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
|
listItem.ts = moment(listItem.modTimestamp).format(dateTimeFormat);
|
||||||
const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId;
|
const isNew = _.isBoolean(listItem.isNew) ? listItem.isNew : listItem.messageId > self.lastReadId;
|
||||||
listItem.newIndicator = isNew ? newIndicator : regIndicator;
|
listItem.newIndicator = isNew ? newIndicator : regIndicator;
|
||||||
|
|
||||||
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
|
if(_.isUndefined(self.initialFocusIndex) && listItem.messageId > self.lastReadId) {
|
||||||
self.initialFocusIndex = index;
|
self.initialFocusIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
listItem.text = `${listItem.msgNum} - ${listItem.subject} from ${listItem.fromUserName}`; // default text
|
listItem.text = `${listItem.msgNum} - ${listItem.subject} from ${listItem.fromUserName}`; // default text
|
||||||
});
|
});
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function populateList(callback) {
|
function populateList(callback) {
|
||||||
const msgListView = vc.getView(MciViewIds.msgList);
|
const msgListView = vc.getView(MciViewIds.msgList);
|
||||||
// :TODO: replace with standard custom info MCI - msgNumSelected, msgNumTotal, areaName, areaDesc, confName, confDesc, ...
|
// :TODO: replace with standard custom info MCI - msgNumSelected, msgNumTotal, areaName, areaDesc, confName, confDesc, ...
|
||||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||||
|
|
||||||
msgListView.setItems(self.config.messageList);
|
msgListView.setItems(self.config.messageList);
|
||||||
|
|
||||||
msgListView.on('index update', idx => {
|
msgListView.on('index update', idx => {
|
||||||
self.setViewText(
|
self.setViewText(
|
||||||
'allViews',
|
'allViews',
|
||||||
MciViewIds.msgInfo1,
|
MciViewIds.msgInfo1,
|
||||||
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.config.messageList.length } ));
|
stringFormat(messageInfo1Format, { msgNumSelected : (idx + 1), msgNumTotal : self.config.messageList.length } ));
|
||||||
});
|
});
|
||||||
|
|
||||||
if(self.initialFocusIndex > 0) {
|
if(self.initialFocusIndex > 0) {
|
||||||
// note: causes redraw()
|
// note: causes redraw()
|
||||||
msgListView.setFocusItemIndex(self.initialFocusIndex);
|
msgListView.setFocusItemIndex(self.initialFocusIndex);
|
||||||
} else {
|
} else {
|
||||||
msgListView.redraw();
|
msgListView.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function drawOtherViews(callback) {
|
function drawOtherViews(callback) {
|
||||||
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
const messageInfo1Format = self.menuConfig.config.messageInfo1Format || '{msgNumSelected} / {msgNumTotal}';
|
||||||
self.setViewText(
|
self.setViewText(
|
||||||
'allViews',
|
'allViews',
|
||||||
MciViewIds.msgInfo1,
|
MciViewIds.msgInfo1,
|
||||||
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.config.messageList.length } ));
|
stringFormat(messageInfo1Format, { msgNumSelected : self.initialFocusIndex + 1, msgNumTotal : self.config.messageList.length } ));
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.error( { error : err.message }, 'Error loading message list');
|
self.client.log.error( { error : err.message }, 'Error loading message list');
|
||||||
}
|
}
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaveState() {
|
getSaveState() {
|
||||||
return { initialFocusIndex : this.initialFocusIndex };
|
return { initialFocusIndex : this.initialFocusIndex };
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreSavedState(savedState) {
|
restoreSavedState(savedState) {
|
||||||
if(savedState) {
|
if(savedState) {
|
||||||
this.initialFocusIndex = savedState.initialFocusIndex;
|
this.initialFocusIndex = savedState.initialFocusIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getMenuResult() {
|
getMenuResult() {
|
||||||
return this.menuResult;
|
return this.menuResult;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,53 +14,53 @@ exports.recordMessage = recordMessage;
|
||||||
let msgNetworkModules = [];
|
let msgNetworkModules = [];
|
||||||
|
|
||||||
function startup(cb) {
|
function startup(cb) {
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadModules(callback) {
|
function loadModules(callback) {
|
||||||
loadModulesForCategory('scannerTossers', (err, module) => {
|
loadModulesForCategory('scannerTossers', (err, module) => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
const modInst = new module.getModule();
|
const modInst = new module.getModule();
|
||||||
|
|
||||||
modInst.startup(err => {
|
modInst.startup(err => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
msgNetworkModules.push(modInst);
|
msgNetworkModules.push(modInst);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, err => {
|
}, err => {
|
||||||
callback(err);
|
callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
cb
|
cb
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shutdown(cb) {
|
function shutdown(cb) {
|
||||||
async.each(
|
async.each(
|
||||||
msgNetworkModules,
|
msgNetworkModules,
|
||||||
(msgNetModule, next) => {
|
(msgNetModule, next) => {
|
||||||
msgNetModule.shutdown( () => {
|
msgNetModule.shutdown( () => {
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
msgNetworkModules = [];
|
msgNetworkModules = [];
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function recordMessage(message, cb) {
|
function recordMessage(message, cb) {
|
||||||
//
|
//
|
||||||
// Give all message network modules (scanner/tossers)
|
// Give all message network modules (scanner/tossers)
|
||||||
// a chance to do something with |message|. Any or all can
|
// a chance to do something with |message|. Any or all can
|
||||||
// choose to ignore it.
|
// choose to ignore it.
|
||||||
//
|
//
|
||||||
async.each(msgNetworkModules, (modInst, next) => {
|
async.each(msgNetworkModules, (modInst, next) => {
|
||||||
modInst.record(message);
|
modInst.record(message);
|
||||||
next();
|
next();
|
||||||
}, err => {
|
}, err => {
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -7,17 +7,17 @@ var PluginModule = require('./plugin_module.js').PluginModule;
|
||||||
exports.MessageScanTossModule = MessageScanTossModule;
|
exports.MessageScanTossModule = MessageScanTossModule;
|
||||||
|
|
||||||
function MessageScanTossModule() {
|
function MessageScanTossModule() {
|
||||||
PluginModule.call(this);
|
PluginModule.call(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
require('util').inherits(MessageScanTossModule, PluginModule);
|
require('util').inherits(MessageScanTossModule, PluginModule);
|
||||||
|
|
||||||
MessageScanTossModule.prototype.startup = function(cb) {
|
MessageScanTossModule.prototype.startup = function(cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageScanTossModule.prototype.shutdown = function(cb) {
|
MessageScanTossModule.prototype.shutdown = function(cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
MessageScanTossModule.prototype.record = function(/*message*/) {
|
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');
|
const async = require('async');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'New Scan',
|
name : 'New Scan',
|
||||||
desc : 'Performs a new scan against various areas of the system',
|
desc : 'Performs a new scan against various areas of the system',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -30,239 +30,239 @@ exports.moduleInfo = {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const MciCodeIds = {
|
const MciCodeIds = {
|
||||||
ScanStatusLabel : 1, // TL1
|
ScanStatusLabel : 1, // TL1
|
||||||
ScanStatusList : 2, // VM2 (appends)
|
ScanStatusList : 2, // VM2 (appends)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Steps = {
|
const Steps = {
|
||||||
MessageConfs : 'messageConferences',
|
MessageConfs : 'messageConferences',
|
||||||
FileBase : 'fileBase',
|
FileBase : 'fileBase',
|
||||||
|
|
||||||
Finished : 'finished',
|
Finished : 'finished',
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class NewScanModule extends MenuModule {
|
exports.getModule = class NewScanModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
|
|
||||||
this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false);
|
this.newScanFullExit = _.get(options, 'lastMenuResult.fullExit', false);
|
||||||
|
|
||||||
this.currentStep = Steps.MessageConfs;
|
this.currentStep = Steps.MessageConfs;
|
||||||
this.currentScanAux = {};
|
this.currentScanAux = {};
|
||||||
|
|
||||||
// :TODO: Make this conf/area specific:
|
// :TODO: Make this conf/area specific:
|
||||||
const config = this.menuConfig.config;
|
const config = this.menuConfig.config;
|
||||||
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
this.scanStartFmt = config.scanStartFmt || 'Scanning {confName} - {areaName}...';
|
||||||
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
this.scanFinishNoneFmt = config.scanFinishNoneFmt || 'Nothing new';
|
||||||
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
this.scanFinishNewFmt = config.scanFinishNewFmt || '{count} entries found';
|
||||||
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
this.scanCompleteMsg = config.scanCompleteMsg || 'Finished newscan';
|
||||||
}
|
}
|
||||||
|
|
||||||
updateScanStatus(statusText) {
|
updateScanStatus(statusText) {
|
||||||
this.setViewText('allViews', MciCodeIds.ScanStatusLabel, statusText);
|
this.setViewText('allViews', MciCodeIds.ScanStatusLabel, statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
newScanMessageConference(cb) {
|
newScanMessageConference(cb) {
|
||||||
// lazy init
|
// lazy init
|
||||||
if(!this.sortedMessageConfs) {
|
if(!this.sortedMessageConfs) {
|
||||||
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
const getAvailOpts = { includeSystemInternal : true }; // find new private messages, bulletins, etc.
|
||||||
|
|
||||||
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => {
|
this.sortedMessageConfs = _.map(msgArea.getAvailableMessageConferences(this.client, getAvailOpts), (v, k) => {
|
||||||
return {
|
return {
|
||||||
confTag : k,
|
confTag : k,
|
||||||
conf : v,
|
conf : v,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
//
|
//
|
||||||
// Sort conferences by name, other than 'system_internal' which should
|
// Sort conferences by name, other than 'system_internal' which should
|
||||||
// always come first such that we display private mails/etc. before
|
// always come first such that we display private mails/etc. before
|
||||||
// other conferences & areas
|
// other conferences & areas
|
||||||
//
|
//
|
||||||
this.sortedMessageConfs.sort((a, b) => {
|
this.sortedMessageConfs.sort((a, b) => {
|
||||||
if('system_internal' === a.confTag) {
|
if('system_internal' === a.confTag) {
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
return a.conf.name.localeCompare(b.conf.name, { sensitivity : false, numeric : true } );
|
return a.conf.name.localeCompare(b.conf.name, { sensitivity : false, numeric : true } );
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.currentScanAux.conf = this.currentScanAux.conf || 0;
|
this.currentScanAux.conf = this.currentScanAux.conf || 0;
|
||||||
this.currentScanAux.area = this.currentScanAux.area || 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, () => {
|
this.newScanMessageArea(currentConf, () => {
|
||||||
if(this.sortedMessageConfs.length > this.currentScanAux.conf + 1) {
|
if(this.sortedMessageConfs.length > this.currentScanAux.conf + 1) {
|
||||||
this.currentScanAux.conf += 1;
|
this.currentScanAux.conf += 1;
|
||||||
this.currentScanAux.area = 0;
|
this.currentScanAux.area = 0;
|
||||||
|
|
||||||
return this.newScanMessageConference(cb); // recursive to next conf
|
return this.newScanMessageConference(cb); // recursive to next conf
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateScanStatus(this.scanCompleteMsg);
|
this.updateScanStatus(this.scanCompleteMsg);
|
||||||
return cb(Errors.DoesNotExist('No more conferences'));
|
return cb(Errors.DoesNotExist('No more conferences'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
newScanMessageArea(conf, cb) {
|
newScanMessageArea(conf, cb) {
|
||||||
// :TODO: it would be nice to cache this - must be done by conf!
|
// :TODO: it would be nice to cache this - must be done by conf!
|
||||||
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } );
|
const sortedAreas = msgArea.getSortedAvailMessageAreasByConfTag(conf.confTag, { client : this.client } );
|
||||||
const currentArea = sortedAreas[this.currentScanAux.area];
|
const currentArea = sortedAreas[this.currentScanAux.area];
|
||||||
|
|
||||||
//
|
//
|
||||||
// Scan and update index until we find something. If results are found,
|
// Scan and update index until we find something. If results are found,
|
||||||
// we'll goto the list module & show them.
|
// we'll goto the list module & show them.
|
||||||
//
|
//
|
||||||
const self = this;
|
const self = this;
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function checkAndUpdateIndex(callback) {
|
function checkAndUpdateIndex(callback) {
|
||||||
// Advance to next area if possible
|
// Advance to next area if possible
|
||||||
if(sortedAreas.length >= self.currentScanAux.area + 1) {
|
if(sortedAreas.length >= self.currentScanAux.area + 1) {
|
||||||
self.currentScanAux.area += 1;
|
self.currentScanAux.area += 1;
|
||||||
return callback(null);
|
return callback(null);
|
||||||
} else {
|
} else {
|
||||||
self.updateScanStatus(self.scanCompleteMsg);
|
self.updateScanStatus(self.scanCompleteMsg);
|
||||||
return callback(Errors.DoesNotExist('No more areas')); // this will stop our scan
|
return callback(Errors.DoesNotExist('No more areas')); // this will stop our scan
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function updateStatusScanStarted(callback) {
|
function updateStatusScanStarted(callback) {
|
||||||
self.updateScanStatus(stringFormat(self.scanStartFmt, {
|
self.updateScanStatus(stringFormat(self.scanStartFmt, {
|
||||||
confName : conf.conf.name,
|
confName : conf.conf.name,
|
||||||
confDesc : conf.conf.desc,
|
confDesc : conf.conf.desc,
|
||||||
areaName : currentArea.area.name,
|
areaName : currentArea.area.name,
|
||||||
areaDesc : currentArea.area.desc
|
areaDesc : currentArea.area.desc
|
||||||
}));
|
}));
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function getNewMessagesCountInArea(callback) {
|
function getNewMessagesCountInArea(callback) {
|
||||||
msgArea.getNewMessageCountInAreaForUser(
|
msgArea.getNewMessageCountInAreaForUser(
|
||||||
self.client.user.userId, currentArea.areaTag, (err, newMessageCount) => {
|
self.client.user.userId, currentArea.areaTag, (err, newMessageCount) => {
|
||||||
callback(err, newMessageCount);
|
callback(err, newMessageCount);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function displayMessageList(newMessageCount) {
|
function displayMessageList(newMessageCount) {
|
||||||
if(newMessageCount <= 0) {
|
if(newMessageCount <= 0) {
|
||||||
return self.newScanMessageArea(conf, cb); // next area, if any
|
return self.newScanMessageArea(conf, cb); // next area, if any
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextModuleOpts = {
|
const nextModuleOpts = {
|
||||||
extraArgs: {
|
extraArgs: {
|
||||||
messageAreaTag : currentArea.areaTag,
|
messageAreaTag : currentArea.areaTag,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
return self.gotoMenu(self.menuConfig.config.newScanMessageList || 'newScanMessageList', nextModuleOpts);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
newScanFileBase(cb) {
|
newScanFileBase(cb) {
|
||||||
// :TODO: add in steps
|
// :TODO: add in steps
|
||||||
const filterCriteria = {
|
const filterCriteria = {
|
||||||
newerThanFileId : FileBaseFilters.getFileBaseLastViewedFileIdByUser(this.client.user),
|
newerThanFileId : FileBaseFilters.getFileBaseLastViewedFileIdByUser(this.client.user),
|
||||||
areaTag : getAvailableFileAreaTags(this.client),
|
areaTag : getAvailableFileAreaTags(this.client),
|
||||||
order : 'ascending', // oldest first
|
order : 'ascending', // oldest first
|
||||||
};
|
};
|
||||||
|
|
||||||
FileEntry.findFiles(
|
FileEntry.findFiles(
|
||||||
filterCriteria,
|
filterCriteria,
|
||||||
(err, fileIds) => {
|
(err, fileIds) => {
|
||||||
if(err || 0 === fileIds.length) {
|
if(err || 0 === fileIds.length) {
|
||||||
return cb(err ? err : Errors.DoesNotExist('No more new files'));
|
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 = {
|
const menuOpts = {
|
||||||
extraArgs : {
|
extraArgs : {
|
||||||
fileList : fileIds,
|
fileList : fileIds,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.gotoMenu(this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList', menuOpts);
|
return this.gotoMenu(this.menuConfig.config.newScanFileBaseList || 'newScanFileBaseList', menuOpts);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaveState() {
|
getSaveState() {
|
||||||
return {
|
return {
|
||||||
currentStep : this.currentStep,
|
currentStep : this.currentStep,
|
||||||
currentScanAux : this.currentScanAux,
|
currentScanAux : this.currentScanAux,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
restoreSavedState(savedState) {
|
restoreSavedState(savedState) {
|
||||||
this.currentStep = savedState.currentStep;
|
this.currentStep = savedState.currentStep;
|
||||||
this.currentScanAux = savedState.currentScanAux;
|
this.currentScanAux = savedState.currentScanAux;
|
||||||
}
|
}
|
||||||
|
|
||||||
performScanCurrentStep(cb) {
|
performScanCurrentStep(cb) {
|
||||||
switch(this.currentStep) {
|
switch(this.currentStep) {
|
||||||
case Steps.MessageConfs :
|
case Steps.MessageConfs :
|
||||||
this.newScanMessageConference( () => {
|
this.newScanMessageConference( () => {
|
||||||
this.currentStep = Steps.FileBase;
|
this.currentStep = Steps.FileBase;
|
||||||
return this.performScanCurrentStep(cb);
|
return this.performScanCurrentStep(cb);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Steps.FileBase :
|
case Steps.FileBase :
|
||||||
this.newScanFileBase( () => {
|
this.newScanFileBase( () => {
|
||||||
this.currentStep = Steps.Finished;
|
this.currentStep = Steps.Finished;
|
||||||
return this.performScanCurrentStep(cb);
|
return this.performScanCurrentStep(cb);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default : return cb(null);
|
default : return cb(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
if(this.newScanFullExit) {
|
if(this.newScanFullExit) {
|
||||||
// user has canceled the entire scan @ message list view
|
// user has canceled the entire scan @ message list view
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.mciReady(mciData, err => {
|
super.mciReady(mciData, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
const vc = self.viewControllers.allViews = new ViewController( { client : self.client } );
|
||||||
|
|
||||||
// :TODO: display scan step/etc.
|
// :TODO: display scan step/etc.
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function loadFromConfig(callback) {
|
function loadFromConfig(callback) {
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : mciData.menu,
|
mciMap : mciData.menu,
|
||||||
noInput : true,
|
noInput : true,
|
||||||
};
|
};
|
||||||
|
|
||||||
vc.loadFromMenuConfig(loadOpts, callback);
|
vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
},
|
},
|
||||||
function performCurrentStepScan(callback) {
|
function performCurrentStepScan(callback) {
|
||||||
return self.performScanCurrentStep(callback);
|
return self.performScanCurrentStep(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.error( { error : err.toString() }, 'Error during new scan');
|
self.client.log.error( { error : err.toString() }, 'Error during new scan');
|
||||||
}
|
}
|
||||||
return cb(err);
|
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');
|
const messageArea = require('./message_area.js');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'NUA',
|
name : 'NUA',
|
||||||
desc : 'New User Application',
|
desc : 'New User Application',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
userName : 1,
|
userName : 1,
|
||||||
password : 9,
|
password : 9,
|
||||||
confirm : 10,
|
confirm : 10,
|
||||||
errMsg : 11,
|
errMsg : 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class NewUserAppModule extends MenuModule {
|
exports.getModule = class NewUserAppModule extends MenuModule {
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
//
|
//
|
||||||
// Validation stuff
|
// Validation stuff
|
||||||
//
|
//
|
||||||
validatePassConfirmMatch : function(data, cb) {
|
validatePassConfirmMatch : function(data, cb) {
|
||||||
const passwordView = self.viewControllers.menu.getView(MciViewIds.password);
|
const passwordView = self.viewControllers.menu.getView(MciViewIds.password);
|
||||||
return cb(passwordView.getData() === data ? null : new Error('Passwords do not match'));
|
return cb(passwordView.getData() === data ? null : new Error('Passwords do not match'));
|
||||||
},
|
},
|
||||||
|
|
||||||
viewValidationListener : function(err, cb) {
|
viewValidationListener : function(err, cb) {
|
||||||
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
const errMsgView = self.viewControllers.menu.getView(MciViewIds.errMsg);
|
||||||
let newFocusId;
|
let newFocusId;
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
errMsgView.setText(err.message);
|
errMsgView.setText(err.message);
|
||||||
err.view.clearText();
|
err.view.clearText();
|
||||||
|
|
||||||
if(err.view.getId() === MciViewIds.confirm) {
|
if(err.view.getId() === MciViewIds.confirm) {
|
||||||
newFocusId = MciViewIds.password;
|
newFocusId = MciViewIds.password;
|
||||||
self.viewControllers.menu.getView(MciViewIds.password).clearText();
|
self.viewControllers.menu.getView(MciViewIds.password).clearText();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
errMsgView.clearText();
|
errMsgView.clearText();
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(newFocusId);
|
return cb(newFocusId);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Submit handlers
|
// Submit handlers
|
||||||
//
|
//
|
||||||
submitApplication : function(formData, extraArgs, cb) {
|
submitApplication : function(formData, extraArgs, cb) {
|
||||||
const newUser = new User();
|
const newUser = new User();
|
||||||
const config = Config();
|
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
|
// 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 confTag = messageArea.getDefaultMessageConferenceTag(self.client, true); // true=disableAcsCheck
|
||||||
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
let areaTag = messageArea.getDefaultMessageAreaTagByConfTag(self.client, confTag, true); // true=disableAcsCheck
|
||||||
|
|
||||||
// can't store undefined!
|
// can't store undefined!
|
||||||
confTag = confTag || '';
|
confTag = confTag || '';
|
||||||
areaTag = areaTag || '';
|
areaTag = areaTag || '';
|
||||||
|
|
||||||
newUser.properties = {
|
newUser.properties = {
|
||||||
real_name : formData.value.realName,
|
real_name : formData.value.realName,
|
||||||
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
|
birthdate : new Date(Date.parse(formData.value.birthdate)).toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||||
sex : formData.value.sex,
|
sex : formData.value.sex,
|
||||||
location : formData.value.location,
|
location : formData.value.location,
|
||||||
affiliation : formData.value.affils,
|
affiliation : formData.value.affils,
|
||||||
email_address : formData.value.email,
|
email_address : formData.value.email,
|
||||||
web_address : formData.value.web,
|
web_address : formData.value.web,
|
||||||
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
|
account_created : new Date().toISOString(), // :TODO: Use moment & explicit ISO string format
|
||||||
|
|
||||||
message_conf_tag : confTag,
|
message_conf_tag : confTag,
|
||||||
message_area_tag : areaTag,
|
message_area_tag : areaTag,
|
||||||
|
|
||||||
term_height : self.client.term.termHeight,
|
term_height : self.client.term.termHeight,
|
||||||
term_width : self.client.term.termWidth,
|
term_width : self.client.term.termWidth,
|
||||||
|
|
||||||
// :TODO: Other defaults
|
// :TODO: Other defaults
|
||||||
// :TODO: should probably have a place to create defaults/etc.
|
// :TODO: should probably have a place to create defaults/etc.
|
||||||
};
|
};
|
||||||
|
|
||||||
if('*' === config.defaults.theme) {
|
if('*' === config.defaults.theme) {
|
||||||
newUser.properties.theme_id = theme.getRandomTheme();
|
newUser.properties.theme_id = theme.getRandomTheme();
|
||||||
} else {
|
} else {
|
||||||
newUser.properties.theme_id = config.defaults.theme;
|
newUser.properties.theme_id = config.defaults.theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: User.create() should validate email uniqueness!
|
// :TODO: User.create() should validate email uniqueness!
|
||||||
newUser.create(formData.value.password, err => {
|
newUser.create(formData.value.password, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
self.client.log.info( { error : err, username : formData.value.username }, 'New user creation failed');
|
||||||
|
|
||||||
self.gotoMenu(extraArgs.error, err => {
|
self.gotoMenu(extraArgs.error, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return self.prevMenu(cb);
|
return self.prevMenu(cb);
|
||||||
}
|
}
|
||||||
return cb(null);
|
return cb(null);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created');
|
self.client.log.info( { username : formData.value.username, userId : newUser.userId }, 'New user created');
|
||||||
|
|
||||||
// Cache SysOp information now
|
// Cache SysOp information now
|
||||||
// :TODO: Similar to bbs.js. DRY
|
// :TODO: Similar to bbs.js. DRY
|
||||||
if(newUser.isSysOp()) {
|
if(newUser.isSysOp()) {
|
||||||
config.general.sysOp = {
|
config.general.sysOp = {
|
||||||
username : formData.value.username,
|
username : formData.value.username,
|
||||||
properties : newUser.properties,
|
properties : newUser.properties,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(User.AccountStatus.inactive === self.client.user.properties.account_status) {
|
if(User.AccountStatus.inactive === self.client.user.properties.account_status) {
|
||||||
return self.gotoMenu(extraArgs.inactive, cb);
|
return self.gotoMenu(extraArgs.inactive, cb);
|
||||||
} else {
|
} else {
|
||||||
//
|
//
|
||||||
// If active now, we need to call login() to authenticate
|
// If active now, we need to call login() to authenticate
|
||||||
//
|
//
|
||||||
return login(self, formData, extraArgs, cb);
|
return login(self, formData, extraArgs, cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
mciReady(mciData, cb) {
|
mciReady(mciData, cb) {
|
||||||
return this.standardMCIReadyHandler(mciData, cb);
|
return this.standardMCIReadyHandler(mciData, cb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
const MenuModule = require('./menu_module.js').MenuModule;
|
const MenuModule = require('./menu_module.js').MenuModule;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getModDatabasePath,
|
getModDatabasePath,
|
||||||
getTransactionDatabase
|
getTransactionDatabase
|
||||||
} = require('./database.js');
|
} = require('./database.js');
|
||||||
|
|
||||||
const ViewController = require('./view_controller.js').ViewController;
|
const ViewController = require('./view_controller.js').ViewController;
|
||||||
|
@ -30,136 +30,136 @@ const moment = require('moment');
|
||||||
|
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Onelinerz',
|
name : 'Onelinerz',
|
||||||
desc : 'Standard local onelinerz',
|
desc : 'Standard local onelinerz',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
packageName : 'codes.l33t.enigma.onelinerz',
|
packageName : 'codes.l33t.enigma.onelinerz',
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciViewIds = {
|
const MciViewIds = {
|
||||||
ViewForm : {
|
ViewForm : {
|
||||||
Entries : 1,
|
Entries : 1,
|
||||||
AddPrompt : 2,
|
AddPrompt : 2,
|
||||||
},
|
},
|
||||||
AddForm : {
|
AddForm : {
|
||||||
NewEntry : 1,
|
NewEntry : 1,
|
||||||
EntryPreview : 2,
|
EntryPreview : 2,
|
||||||
AddPrompt : 3,
|
AddPrompt : 3,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FormIds = {
|
const FormIds = {
|
||||||
View : 0,
|
View : 0,
|
||||||
Add : 1,
|
Add : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class OnelinerzModule extends MenuModule {
|
exports.getModule = class OnelinerzModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
viewAddScreen : function(formData, extraArgs, cb) {
|
viewAddScreen : function(formData, extraArgs, cb) {
|
||||||
return self.displayAddScreen(cb);
|
return self.displayAddScreen(cb);
|
||||||
},
|
},
|
||||||
|
|
||||||
addEntry : function(formData, extraArgs, cb) {
|
addEntry : function(formData, extraArgs, cb) {
|
||||||
if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) {
|
if(_.isString(formData.value.oneliner) && formData.value.oneliner.length > 0) {
|
||||||
const oneliner = formData.value.oneliner.trim(); // remove any trailing ws
|
const oneliner = formData.value.oneliner.trim(); // remove any trailing ws
|
||||||
|
|
||||||
self.storeNewOneliner(oneliner, err => {
|
self.storeNewOneliner(oneliner, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
self.client.log.warn( { error : err.message }, 'Failed saving oneliner');
|
self.client.log.warn( { error : err.message }, 'Failed saving oneliner');
|
||||||
}
|
}
|
||||||
|
|
||||||
self.clearAddForm();
|
self.clearAddForm();
|
||||||
return self.displayViewScreen(true, cb); // true=cls
|
return self.displayViewScreen(true, cb); // true=cls
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// empty message - treat as if cancel was hit
|
// empty message - treat as if cancel was hit
|
||||||
return self.displayViewScreen(true, cb); // true=cls
|
return self.displayViewScreen(true, cb); // true=cls
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelAdd : function(formData, extraArgs, cb) {
|
cancelAdd : function(formData, extraArgs, cb) {
|
||||||
self.clearAddForm();
|
self.clearAddForm();
|
||||||
return self.displayViewScreen(true, cb); // true=cls
|
return self.displayViewScreen(true, cb); // true=cls
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function beforeDisplayArt(callback) {
|
function beforeDisplayArt(callback) {
|
||||||
return self.beforeArt(callback);
|
return self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function display(callback) {
|
function display(callback) {
|
||||||
return self.displayViewScreen(false, callback);
|
return self.displayViewScreen(false, callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||||
}
|
}
|
||||||
self.finishedLoading();
|
self.finishedLoading();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayViewScreen(clearScreen, cb) {
|
displayViewScreen(clearScreen, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
if(self.viewControllers.add) {
|
if(self.viewControllers.add) {
|
||||||
self.viewControllers.add.setFocus(false);
|
self.viewControllers.add.setFocus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(clearScreen) {
|
if(clearScreen) {
|
||||||
self.client.term.rawWrite(ansi.resetScreen());
|
self.client.term.rawWrite(ansi.resetScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
self.menuConfig.config.art.entries,
|
self.menuConfig.config.art.entries,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font, trailingLF : false },
|
{ font : self.menuConfig.font, trailingLF : false },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initOrRedrawViewController(artData, callback) {
|
function initOrRedrawViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers.add)) {
|
if(_.isUndefined(self.viewControllers.add)) {
|
||||||
const vc = self.addViewController(
|
const vc = self.addViewController(
|
||||||
'view',
|
'view',
|
||||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds.View,
|
formId : FormIds.View,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
} else {
|
} else {
|
||||||
self.viewControllers.view.setFocus(true);
|
self.viewControllers.view.setFocus(true);
|
||||||
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
|
self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt).redraw();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function fetchEntries(callback) {
|
function fetchEntries(callback) {
|
||||||
const entriesView = self.viewControllers.view.getView(MciViewIds.ViewForm.Entries);
|
const entriesView = self.viewControllers.view.getView(MciViewIds.ViewForm.Entries);
|
||||||
const limit = entriesView.dimens.height;
|
const limit = entriesView.dimens.height;
|
||||||
let entries = [];
|
let entries = [];
|
||||||
|
|
||||||
self.db.each(
|
self.db.each(
|
||||||
`SELECT *
|
`SELECT *
|
||||||
FROM (
|
FROM (
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM onelinerz
|
FROM onelinerz
|
||||||
|
@ -167,172 +167,172 @@ exports.getModule = class OnelinerzModule extends MenuModule {
|
||||||
LIMIT ${limit}
|
LIMIT ${limit}
|
||||||
)
|
)
|
||||||
ORDER BY timestamp ASC;`,
|
ORDER BY timestamp ASC;`,
|
||||||
(err, row) => {
|
(err, row) => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
row.timestamp = moment(row.timestamp); // convert -> moment
|
row.timestamp = moment(row.timestamp); // convert -> moment
|
||||||
entries.push(row);
|
entries.push(row);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
return callback(err, entriesView, entries);
|
return callback(err, entriesView, entries);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function populateEntries(entriesView, entries, callback) {
|
function populateEntries(entriesView, entries, callback) {
|
||||||
const listFormat = self.menuConfig.config.listFormat || '{username}@{ts}: {oneliner}';// :TODO: should be userName to be consistent
|
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';
|
const tsFormat = self.menuConfig.config.timestampFormat || 'ddd h:mma';
|
||||||
|
|
||||||
entriesView.setItems(entries.map( e => {
|
entriesView.setItems(entries.map( e => {
|
||||||
return stringFormat(listFormat, {
|
return stringFormat(listFormat, {
|
||||||
userId : e.user_id,
|
userId : e.user_id,
|
||||||
username : e.user_name,
|
username : e.user_name,
|
||||||
oneliner : e.oneliner,
|
oneliner : e.oneliner,
|
||||||
ts : e.timestamp.format(tsFormat),
|
ts : e.timestamp.format(tsFormat),
|
||||||
} );
|
} );
|
||||||
}));
|
}));
|
||||||
|
|
||||||
entriesView.redraw();
|
entriesView.redraw();
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function finalPrep(callback) {
|
function finalPrep(callback) {
|
||||||
const promptView = self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt);
|
const promptView = self.viewControllers.view.getView(MciViewIds.ViewForm.AddPrompt);
|
||||||
promptView.setFocusItemIndex(1); // default to NO
|
promptView.setFocusItemIndex(1); // default to NO
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAddScreen(cb) {
|
displayAddScreen(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
self.viewControllers.view.setFocus(false);
|
self.viewControllers.view.setFocus(false);
|
||||||
self.client.term.rawWrite(ansi.resetScreen());
|
self.client.term.rawWrite(ansi.resetScreen());
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
self.menuConfig.config.art.add,
|
self.menuConfig.config.art.add,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font },
|
{ font : self.menuConfig.font },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initOrRedrawViewController(artData, callback) {
|
function initOrRedrawViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers.add)) {
|
if(_.isUndefined(self.viewControllers.add)) {
|
||||||
const vc = self.addViewController(
|
const vc = self.addViewController(
|
||||||
'add',
|
'add',
|
||||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds.Add,
|
formId : FormIds.Add,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
} else {
|
} else {
|
||||||
self.viewControllers.add.setFocus(true);
|
self.viewControllers.add.setFocus(true);
|
||||||
self.viewControllers.add.redrawAll();
|
self.viewControllers.add.redrawAll();
|
||||||
self.viewControllers.add.switchFocus(MciViewIds.AddForm.NewEntry);
|
self.viewControllers.add.switchFocus(MciViewIds.AddForm.NewEntry);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAddForm() {
|
clearAddForm() {
|
||||||
this.setViewText('add', MciViewIds.AddForm.NewEntry, '');
|
this.setViewText('add', MciViewIds.AddForm.NewEntry, '');
|
||||||
this.setViewText('add', MciViewIds.AddForm.EntryPreview, '');
|
this.setViewText('add', MciViewIds.AddForm.EntryPreview, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
initDatabase(cb) {
|
initDatabase(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function openDatabase(callback) {
|
function openDatabase(callback) {
|
||||||
self.db = getTransactionDatabase(new sqlite3.Database(
|
self.db = getTransactionDatabase(new sqlite3.Database(
|
||||||
getModDatabasePath(exports.moduleInfo),
|
getModDatabasePath(exports.moduleInfo),
|
||||||
err => {
|
err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
function createTables(callback) {
|
function createTables(callback) {
|
||||||
self.db.run(
|
self.db.run(
|
||||||
`CREATE TABLE IF NOT EXISTS onelinerz (
|
`CREATE TABLE IF NOT EXISTS onelinerz (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
user_id INTEGER_NOT NULL,
|
user_id INTEGER_NOT NULL,
|
||||||
user_name VARCHAR NOT NULL,
|
user_name VARCHAR NOT NULL,
|
||||||
oneliner VARCHAR NOT NULL,
|
oneliner VARCHAR NOT NULL,
|
||||||
timestamp DATETIME NOT NULL
|
timestamp DATETIME NOT NULL
|
||||||
);`
|
);`
|
||||||
,
|
,
|
||||||
err => {
|
err => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
storeNewOneliner(oneliner, cb) {
|
storeNewOneliner(oneliner, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
const ts = moment().format('YYYY-MM-DDTHH:mm:ss.SSSZ');
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function addRec(callback) {
|
function addRec(callback) {
|
||||||
self.db.run(
|
self.db.run(
|
||||||
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
|
`INSERT INTO onelinerz (user_id, user_name, oneliner, timestamp)
|
||||||
VALUES (?, ?, ?, ?);`,
|
VALUES (?, ?, ?, ?);`,
|
||||||
[ self.client.user.userId, self.client.user.username, oneliner, ts ],
|
[ self.client.user.userId, self.client.user.username, oneliner, ts ],
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function removeOld(callback) {
|
function removeOld(callback) {
|
||||||
// keep 25 max most recent items - remove the older ones
|
// keep 25 max most recent items - remove the older ones
|
||||||
self.db.run(
|
self.db.run(
|
||||||
`DELETE FROM onelinerz
|
`DELETE FROM onelinerz
|
||||||
WHERE id IN (
|
WHERE id IN (
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM onelinerz
|
FROM onelinerz
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
LIMIT -1 OFFSET 25
|
LIMIT -1 OFFSET 25
|
||||||
);`,
|
);`,
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeArt(cb) {
|
beforeArt(cb) {
|
||||||
super.beforeArt(err => {
|
super.beforeArt(err => {
|
||||||
return err ? cb(err) : this.initDatabase(cb);
|
return err ? cb(err) : this.initDatabase(cb);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,83 +16,83 @@ exports.getAreaAndStorage = getAreaAndStorage;
|
||||||
exports.looksLikePattern = looksLikePattern;
|
exports.looksLikePattern = looksLikePattern;
|
||||||
|
|
||||||
const exitCodes = exports.ExitCodes = {
|
const exitCodes = exports.ExitCodes = {
|
||||||
SUCCESS : 0,
|
SUCCESS : 0,
|
||||||
ERROR : -1,
|
ERROR : -1,
|
||||||
BAD_COMMAND : -2,
|
BAD_COMMAND : -2,
|
||||||
BAD_ARGS : -3,
|
BAD_ARGS : -3,
|
||||||
};
|
};
|
||||||
|
|
||||||
const argv = exports.argv = require('minimist')(process.argv.slice(2), {
|
const argv = exports.argv = require('minimist')(process.argv.slice(2), {
|
||||||
alias : {
|
alias : {
|
||||||
h : 'help',
|
h : 'help',
|
||||||
v : 'version',
|
v : 'version',
|
||||||
c : 'config',
|
c : 'config',
|
||||||
n : 'no-prompt',
|
n : 'no-prompt',
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function printUsageAndSetExitCode(errMsg, exitCode) {
|
function printUsageAndSetExitCode(errMsg, exitCode) {
|
||||||
if(_.isUndefined(exitCode)) {
|
if(_.isUndefined(exitCode)) {
|
||||||
exitCode = exitCodes.ERROR;
|
exitCode = exitCodes.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
process.exitCode = exitCode;
|
process.exitCode = exitCode;
|
||||||
|
|
||||||
if(errMsg) {
|
if(errMsg) {
|
||||||
console.error(errMsg);
|
console.error(errMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDefaultConfigPath() {
|
function getDefaultConfigPath() {
|
||||||
return './config/';
|
return './config/';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getConfigPath() {
|
function getConfigPath() {
|
||||||
const baseConfigPath = argv.config ? argv.config : config.getDefaultPath();
|
const baseConfigPath = argv.config ? argv.config : config.getDefaultPath();
|
||||||
return baseConfigPath + 'config.hjson';
|
return baseConfigPath + 'config.hjson';
|
||||||
}
|
}
|
||||||
|
|
||||||
function initConfig(cb) {
|
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) {
|
function initConfigAndDatabases(cb) {
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function init(callback) {
|
function init(callback) {
|
||||||
initConfig(callback);
|
initConfig(callback);
|
||||||
},
|
},
|
||||||
function initDb(callback) {
|
function initDb(callback) {
|
||||||
db.initializeDatabases(callback);
|
db.initializeDatabases(callback);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAreaAndStorage(tags) {
|
function getAreaAndStorage(tags) {
|
||||||
return tags.map(tag => {
|
return tags.map(tag => {
|
||||||
const parts = tag.toString().split('@');
|
const parts = tag.toString().split('@');
|
||||||
const entry = {
|
const entry = {
|
||||||
areaTag : parts[0],
|
areaTag : parts[0],
|
||||||
};
|
};
|
||||||
entry.pattern = entry.areaTag; // handy
|
entry.pattern = entry.areaTag; // handy
|
||||||
if(parts[1]) {
|
if(parts[1]) {
|
||||||
entry.storageTag = parts[1];
|
entry.storageTag = parts[1];
|
||||||
}
|
}
|
||||||
return entry;
|
return entry;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function looksLikePattern(tag) {
|
function looksLikePattern(tag) {
|
||||||
// globs can start with @
|
// globs can start with @
|
||||||
if(tag.indexOf('@') > 0) {
|
if(tag.indexOf('@') > 0) {
|
||||||
return false;
|
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;
|
exports.getHelpFor = getHelpFor;
|
||||||
|
|
||||||
const usageHelp = exports.USAGE_HELP = {
|
const usageHelp = exports.USAGE_HELP = {
|
||||||
General :
|
General :
|
||||||
`usage: optutil.js [--version] [--help]
|
`usage: optutil.js [--version] [--help]
|
||||||
<command> [<args>]
|
<command> [<args>]
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ commands:
|
||||||
fb file base management
|
fb file base management
|
||||||
mb message base management
|
mb message base management
|
||||||
`,
|
`,
|
||||||
User :
|
User :
|
||||||
`usage: optutil.js user <action> [<args>]
|
`usage: optutil.js user <action> [<args>]
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
|
@ -33,7 +33,7 @@ actions:
|
||||||
group USERNAME [+|-]GROUP adds (+) or removes (-) user from GROUP
|
group USERNAME [+|-]GROUP adds (+) or removes (-) user from GROUP
|
||||||
`,
|
`,
|
||||||
|
|
||||||
Config :
|
Config :
|
||||||
`usage: optutil.js config <action> [<args>]
|
`usage: optutil.js config <action> [<args>]
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
|
@ -46,7 +46,7 @@ import-areas args:
|
||||||
--uplinks UL1,UL2,... specify one or more comma separated uplinks
|
--uplinks UL1,UL2,... specify one or more comma separated uplinks
|
||||||
--type TYPE specifies area import type. valid options are "bbs" and "na"
|
--type TYPE specifies area import type. valid options are "bbs" and "na"
|
||||||
`,
|
`,
|
||||||
FileBase :
|
FileBase :
|
||||||
`usage: oputil.js fb <action> [<args>]
|
`usage: oputil.js fb <action> [<args>]
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
|
@ -80,7 +80,7 @@ info args:
|
||||||
remove args:
|
remove args:
|
||||||
--phys-file also remove underlying physical file
|
--phys-file also remove underlying physical file
|
||||||
`,
|
`,
|
||||||
FileOpsInfo :
|
FileOpsInfo :
|
||||||
`
|
`
|
||||||
general information:
|
general information:
|
||||||
AREA_TAG[@STORAGE_TAG] can specify an area tag and optionally, a storage specific tag
|
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
|
SHA full or partial SHA-256
|
||||||
FILE_ID a file identifier. see file.sqlite3
|
FILE_ID a file identifier. see file.sqlite3
|
||||||
`,
|
`,
|
||||||
MessageBase :
|
MessageBase :
|
||||||
`usage: oputil.js mb <action> [<args>]
|
`usage: oputil.js mb <action> [<args>]
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
|
@ -101,5 +101,5 @@ general information:
|
||||||
};
|
};
|
||||||
|
|
||||||
function getHelpFor(command) {
|
function getHelpFor(command) {
|
||||||
return usageHelp[command];
|
return usageHelp[command];
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,23 +14,23 @@ const getHelpFor = require('./oputil_help.js').getHelpFor;
|
||||||
|
|
||||||
module.exports = function() {
|
module.exports = function() {
|
||||||
|
|
||||||
process.exitCode = ExitCodes.SUCCESS;
|
process.exitCode = ExitCodes.SUCCESS;
|
||||||
|
|
||||||
if(true === argv.version) {
|
if(true === argv.version) {
|
||||||
return console.info(require('../package.json').version);
|
return console.info(require('../package.json').version);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(0 === argv._.length ||
|
if(0 === argv._.length ||
|
||||||
'help' === argv._[0])
|
'help' === argv._[0])
|
||||||
{
|
{
|
||||||
return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.SUCCESS);
|
return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(argv._[0]) {
|
switch(argv._[0]) {
|
||||||
case 'user' : return handleUserCommand();
|
case 'user' : return handleUserCommand();
|
||||||
case 'config' : return handleConfigCommand();
|
case 'config' : return handleConfigCommand();
|
||||||
case 'fb' : return handleFileBaseCommand();
|
case 'fb' : return handleFileBaseCommand();
|
||||||
case 'mb' : return handleMessageBaseCommand();
|
case 'mb' : return handleMessageBaseCommand();
|
||||||
default : return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.BAD_COMMAND);
|
default : return printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.BAD_COMMAND);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,127 +16,127 @@ const async = require('async');
|
||||||
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
exports.handleMessageBaseCommand = handleMessageBaseCommand;
|
||||||
|
|
||||||
function areaFix() {
|
function areaFix() {
|
||||||
//
|
//
|
||||||
// oputil mb areafix CMD1 CMD2 ... ADDR [--password PASS]
|
// oputil mb areafix CMD1 CMD2 ... ADDR [--password PASS]
|
||||||
//
|
//
|
||||||
if(argv._.length < 3) {
|
if(argv._.length < 3) {
|
||||||
return printUsageAndSetExitCode(
|
return printUsageAndSetExitCode(
|
||||||
getHelpFor('MessageBase'),
|
getHelpFor('MessageBase'),
|
||||||
ExitCodes.ERROR
|
ExitCodes.ERROR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function init(callback) {
|
function init(callback) {
|
||||||
return initConfigAndDatabases(callback);
|
return initConfigAndDatabases(callback);
|
||||||
},
|
},
|
||||||
function validateAddress(callback) {
|
function validateAddress(callback) {
|
||||||
const addrArg = argv._.slice(-1)[0];
|
const addrArg = argv._.slice(-1)[0];
|
||||||
const ftnAddr = Address.fromString(addrArg);
|
const ftnAddr = Address.fromString(addrArg);
|
||||||
|
|
||||||
if(!ftnAddr) {
|
if(!ftnAddr) {
|
||||||
return callback(Errors.Invalid(`"${addrArg}" is not a valid FTN address`));
|
return callback(Errors.Invalid(`"${addrArg}" is not a valid FTN address`));
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// We need to validate the address targets a system we know unless
|
// We need to validate the address targets a system we know unless
|
||||||
// the --force option is used
|
// the --force option is used
|
||||||
//
|
//
|
||||||
// :TODO:
|
// :TODO:
|
||||||
return callback(null, ftnAddr);
|
return callback(null, ftnAddr);
|
||||||
},
|
},
|
||||||
function fetchFromUser(ftnAddr, callback) {
|
function fetchFromUser(ftnAddr, callback) {
|
||||||
//
|
//
|
||||||
// --from USER || +op from system
|
// --from USER || +op from system
|
||||||
//
|
//
|
||||||
// If possible, we want the user ID of the supplied user as well
|
// If possible, we want the user ID of the supplied user as well
|
||||||
//
|
//
|
||||||
const User = require('../user.js');
|
const User = require('../user.js');
|
||||||
|
|
||||||
if(argv.from) {
|
if(argv.from) {
|
||||||
User.getUserIdAndNameByLookup(argv.from, (err, userId, fromName) => {
|
User.getUserIdAndNameByLookup(argv.from, (err, userId, fromName) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return callback(null, ftnAddr, argv.from, 0);
|
return callback(null, ftnAddr, argv.from, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fromName is the same as argv.from, but case may be differnet (yet correct)
|
// fromName is the same as argv.from, but case may be differnet (yet correct)
|
||||||
return callback(null, ftnAddr, fromName, userId);
|
return callback(null, ftnAddr, fromName, userId);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
User.getUserName(User.RootUserID, (err, fromName) => {
|
User.getUserName(User.RootUserID, (err, fromName) => {
|
||||||
return callback(null, ftnAddr, fromName || 'SysOp', err ? 0 : User.RootUserID);
|
return callback(null, ftnAddr, fromName || 'SysOp', err ? 0 : User.RootUserID);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function createMessage(ftnAddr, fromName, fromUserId, callback) {
|
function createMessage(ftnAddr, fromName, fromUserId, callback) {
|
||||||
//
|
//
|
||||||
// Build message as commands separated by line feed
|
// Build message as commands separated by line feed
|
||||||
//
|
//
|
||||||
// We need to remove quotes from arguments. These are required
|
// We need to remove quotes from arguments. These are required
|
||||||
// in the case of e.g. removing an area: "-SOME_AREA" would end
|
// in the case of e.g. removing an area: "-SOME_AREA" would end
|
||||||
// up confusing minimist, therefor they must be quoted: "'-SOME_AREA'"
|
// up confusing minimist, therefor they must be quoted: "'-SOME_AREA'"
|
||||||
//
|
//
|
||||||
const messageBody = argv._.slice(2, -1).map(arg => {
|
const messageBody = argv._.slice(2, -1).map(arg => {
|
||||||
return arg.replace(/["']/g, '');
|
return arg.replace(/["']/g, '');
|
||||||
}).join('\r\n') + '\n';
|
}).join('\r\n') + '\n';
|
||||||
|
|
||||||
const Message = require('../message.js');
|
const Message = require('../message.js');
|
||||||
|
|
||||||
const message = new Message({
|
const message = new Message({
|
||||||
toUserName : argv.to || 'AreaFix',
|
toUserName : argv.to || 'AreaFix',
|
||||||
fromUserName : fromName,
|
fromUserName : fromName,
|
||||||
subject : argv.password || '',
|
subject : argv.password || '',
|
||||||
message : messageBody,
|
message : messageBody,
|
||||||
areaTag : Message.WellKnownAreaTags.Private, // mark private
|
areaTag : Message.WellKnownAreaTags.Private, // mark private
|
||||||
meta : {
|
meta : {
|
||||||
System : {
|
System : {
|
||||||
[ Message.SystemMetaNames.RemoteToUser ] : ftnAddr.toString(), // where to send it
|
[ Message.SystemMetaNames.RemoteToUser ] : ftnAddr.toString(), // where to send it
|
||||||
[ Message.SystemMetaNames.ExternalFlavor ] : Message.AddressFlavor.FTN, // on FTN-style network
|
[ Message.SystemMetaNames.ExternalFlavor ] : Message.AddressFlavor.FTN, // on FTN-style network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(0 !== fromUserId) {
|
if(0 !== fromUserId) {
|
||||||
message.setLocalFromUserId(fromUserId);
|
message.setLocalFromUserId(fromUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return callback(null, message);
|
return callback(null, message);
|
||||||
},
|
},
|
||||||
function persistMessage(message, callback) {
|
function persistMessage(message, callback) {
|
||||||
message.persist(err => {
|
message.persist(err => {
|
||||||
if(!err) {
|
if(!err) {
|
||||||
console.log('AreaFix message persisted and will be exported at next scheduled scan');
|
console.log('AreaFix message persisted and will be exported at next scheduled scan');
|
||||||
}
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.ERROR;
|
process.exitCode = ExitCodes.ERROR;
|
||||||
console.error(`${err.message}${err.reason ? ': ' + err.reason : ''}`);
|
console.error(`${err.message}${err.reason ? ': ' + err.reason : ''}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMessageBaseCommand() {
|
function handleMessageBaseCommand() {
|
||||||
|
|
||||||
function errUsage() {
|
function errUsage() {
|
||||||
return printUsageAndSetExitCode(
|
return printUsageAndSetExitCode(
|
||||||
getHelpFor('MessageBase'),
|
getHelpFor('MessageBase'),
|
||||||
ExitCodes.ERROR
|
ExitCodes.ERROR
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(true === argv.help) {
|
if(true === argv.help) {
|
||||||
return errUsage();
|
return errUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = argv._[1];
|
const action = argv._[1];
|
||||||
|
|
||||||
return({
|
return({
|
||||||
areafix : areaFix,
|
areafix : areaFix,
|
||||||
}[action] || errUsage)();
|
}[action] || errUsage)();
|
||||||
}
|
}
|
|
@ -15,191 +15,191 @@ const _ = require('lodash');
|
||||||
exports.handleUserCommand = handleUserCommand;
|
exports.handleUserCommand = handleUserCommand;
|
||||||
|
|
||||||
function getUser(userName, cb) {
|
function getUser(userName, cb) {
|
||||||
const User = require('../../core/user.js');
|
const User = require('../../core/user.js');
|
||||||
User.getUserIdAndName(userName, (err, userId) => {
|
User.getUserIdAndName(userName, (err, userId) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.BAD_ARGS;
|
process.exitCode = ExitCodes.BAD_ARGS;
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
const u = new User();
|
const u = new User();
|
||||||
u.userId = userId;
|
u.userId = userId;
|
||||||
return cb(null, u);
|
return cb(null, u);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAndGetUser(userName, cb) {
|
function initAndGetUser(userName, cb) {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function init(callback) {
|
function init(callback) {
|
||||||
initConfigAndDatabases(callback);
|
initConfigAndDatabases(callback);
|
||||||
},
|
},
|
||||||
function getUserObject(callback) {
|
function getUserObject(callback) {
|
||||||
getUser(userName, (err, user) => {
|
getUser(userName, (err, user) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.BAD_ARGS;
|
process.exitCode = ExitCodes.BAD_ARGS;
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
return callback(null, user);
|
return callback(null, user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
(err, user) => {
|
(err, user) => {
|
||||||
return cb(err, user);
|
return cb(err, user);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAccountStatus(user, status) {
|
function setAccountStatus(user, status) {
|
||||||
if(argv._.length < 3) {
|
if(argv._.length < 3) {
|
||||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||||
const statusDesc = _.invert(AccountStatus)[status];
|
const statusDesc = _.invert(AccountStatus)[status];
|
||||||
user.persistProperty('account_status', status, err => {
|
user.persistProperty('account_status', status, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.ERROR;
|
process.exitCode = ExitCodes.ERROR;
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
} else {
|
} else {
|
||||||
console.info(`User status set to ${statusDesc}`);
|
console.info(`User status set to ${statusDesc}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUserPassword(user) {
|
function setUserPassword(user) {
|
||||||
if(argv._.length < 4) {
|
if(argv._.length < 4) {
|
||||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function validate(callback) {
|
function validate(callback) {
|
||||||
// :TODO: prompt if no password provided (more secure, no history, etc.)
|
// :TODO: prompt if no password provided (more secure, no history, etc.)
|
||||||
const password = argv._[argv._.length - 1];
|
const password = argv._[argv._.length - 1];
|
||||||
if(0 === password.length) {
|
if(0 === password.length) {
|
||||||
return callback(Errors.Invalid('Invalid password'));
|
return callback(Errors.Invalid('Invalid password'));
|
||||||
}
|
}
|
||||||
return callback(null, password);
|
return callback(null, password);
|
||||||
},
|
},
|
||||||
function set(password, callback) {
|
function set(password, callback) {
|
||||||
user.setNewAuthCredentials(password, err => {
|
user.setNewAuthCredentials(password, err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.BAD_ARGS;
|
process.exitCode = ExitCodes.BAD_ARGS;
|
||||||
}
|
}
|
||||||
return callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
} else {
|
} else {
|
||||||
console.info('New password set');
|
console.info('New password set');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeUser(user) {
|
function removeUser() {
|
||||||
console.error('NOT YET IMPLEMENTED');
|
console.error('NOT YET IMPLEMENTED');
|
||||||
}
|
}
|
||||||
|
|
||||||
function modUserGroups(user) {
|
function modUserGroups(user) {
|
||||||
if(argv._.length < 3) {
|
if(argv._.length < 3) {
|
||||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
let groupName = argv._[argv._.length - 1].toString().replace(/["']/g, ''); // remove any quotes - necessary to allow "-foo"
|
let groupName = argv._[argv._.length - 1].toString().replace(/["']/g, ''); // remove any quotes - necessary to allow "-foo"
|
||||||
let action = groupName[0]; // + or -
|
let action = groupName[0]; // + or -
|
||||||
|
|
||||||
if('-' === action || '+' === action) {
|
if('-' === action || '+' === action) {
|
||||||
groupName = groupName.substr(1);
|
groupName = groupName.substr(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
action = action || '+';
|
action = action || '+';
|
||||||
|
|
||||||
if(0 === groupName.length) {
|
if(0 === groupName.length) {
|
||||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Groups are currently arbritary, so do a slight validation
|
// Groups are currently arbritary, so do a slight validation
|
||||||
//
|
//
|
||||||
if(!/[A-Za-z0-9]+/.test(groupName)) {
|
if(!/[A-Za-z0-9]+/.test(groupName)) {
|
||||||
process.exitCode = ExitCodes.BAD_ARGS;
|
process.exitCode = ExitCodes.BAD_ARGS;
|
||||||
return console.error('Bad group name');
|
return console.error('Bad group name');
|
||||||
}
|
}
|
||||||
|
|
||||||
function done(err) {
|
function done(err) {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.BAD_ARGS;
|
process.exitCode = ExitCodes.BAD_ARGS;
|
||||||
console.error(err.message);
|
console.error(err.message);
|
||||||
} else {
|
} else {
|
||||||
console.info('User groups modified');
|
console.info('User groups modified');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserGroup = require('../../core/user_group.js');
|
const UserGroup = require('../../core/user_group.js');
|
||||||
if('-' === action) {
|
if('-' === action) {
|
||||||
UserGroup.removeUserFromGroup(user.userId, groupName, done);
|
UserGroup.removeUserFromGroup(user.userId, groupName, done);
|
||||||
} else {
|
} else {
|
||||||
UserGroup.addUserToGroup(user.userId, groupName, done);
|
UserGroup.addUserToGroup(user.userId, groupName, done);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function activateUser(user) {
|
function activateUser(user) {
|
||||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||||
return setAccountStatus(user, AccountStatus.active);
|
return setAccountStatus(user, AccountStatus.active);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deactivateUser(user) {
|
function deactivateUser(user) {
|
||||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||||
return setAccountStatus(user, AccountStatus.inactive);
|
return setAccountStatus(user, AccountStatus.inactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableUser(user) {
|
function disableUser(user) {
|
||||||
const AccountStatus = require('../../core/user.js').AccountStatus;
|
const AccountStatus = require('../../core/user.js').AccountStatus;
|
||||||
return setAccountStatus(user, AccountStatus.disabled);
|
return setAccountStatus(user, AccountStatus.disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUserCommand() {
|
function handleUserCommand() {
|
||||||
function errUsage() {
|
function errUsage() {
|
||||||
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
return printUsageAndSetExitCode(getHelpFor('User'), ExitCodes.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(true === argv.help) {
|
if(true === argv.help) {
|
||||||
return errUsage();
|
return errUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const action = argv._[1];
|
const action = argv._[1];
|
||||||
const usernameIdx = [ 'pass', 'passwd', 'password', 'group' ].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
const usernameIdx = [ 'pass', 'passwd', 'password', 'group' ].includes(action) ? argv._.length - 2 : argv._.length - 1;
|
||||||
const userName = argv._[usernameIdx];
|
const userName = argv._[usernameIdx];
|
||||||
|
|
||||||
if(!userName) {
|
if(!userName) {
|
||||||
return errUsage();
|
return errUsage();
|
||||||
}
|
}
|
||||||
|
|
||||||
initAndGetUser(userName, (err, user) => {
|
initAndGetUser(userName, (err, user) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
process.exitCode = ExitCodes.ERROR;
|
process.exitCode = ExitCodes.ERROR;
|
||||||
return console.error(err.message);
|
return console.error(err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ({
|
return ({
|
||||||
pass : setUserPassword,
|
pass : setUserPassword,
|
||||||
passwd : setUserPassword,
|
passwd : setUserPassword,
|
||||||
password : setUserPassword,
|
password : setUserPassword,
|
||||||
|
|
||||||
rm : removeUser,
|
rm : removeUser,
|
||||||
remove : removeUser,
|
remove : removeUser,
|
||||||
del : removeUser,
|
del : removeUser,
|
||||||
delete : removeUser,
|
delete : removeUser,
|
||||||
|
|
||||||
activate : activateUser,
|
activate : activateUser,
|
||||||
deactivate : deactivateUser,
|
deactivate : deactivateUser,
|
||||||
disable : disableUser,
|
disable : disableUser,
|
||||||
|
|
||||||
group : modUserGroups,
|
group : modUserGroups,
|
||||||
}[action] || errUsage)(user);
|
}[action] || errUsage)(user);
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -21,230 +21,230 @@ exports.getPredefinedMCIValue = getPredefinedMCIValue;
|
||||||
exports.init = init;
|
exports.init = init;
|
||||||
|
|
||||||
function init(cb) {
|
function init(cb) {
|
||||||
setNextRandomRumor(cb);
|
setNextRandomRumor(cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNextRandomRumor(cb) {
|
function setNextRandomRumor(cb) {
|
||||||
StatLog.getSystemLogEntries('system_rumorz', StatLog.Order.Random, 1, (err, entry) => {
|
StatLog.getSystemLogEntries('system_rumorz', StatLog.Order.Random, 1, (err, entry) => {
|
||||||
if(entry) {
|
if(entry) {
|
||||||
entry = entry[0];
|
entry = entry[0];
|
||||||
}
|
}
|
||||||
const randRumor = entry && entry.log_value ? entry.log_value : '';
|
const randRumor = entry && entry.log_value ? entry.log_value : '';
|
||||||
StatLog.setNonPeristentSystemStat('random_rumor', randRumor);
|
StatLog.setNonPeristentSystemStat('random_rumor', randRumor);
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(null);
|
return cb(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserRatio(client, propA, propB) {
|
function getUserRatio(client, propA, propB) {
|
||||||
const a = StatLog.getUserStatNum(client.user, propA);
|
const a = StatLog.getUserStatNum(client.user, propA);
|
||||||
const b = StatLog.getUserStatNum(client.user, propB);
|
const b = StatLog.getUserStatNum(client.user, propB);
|
||||||
const ratio = ~~((a / b) * 100);
|
const ratio = ~~((a / b) * 100);
|
||||||
return `${ratio}%`;
|
return `${ratio}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function userStatAsString(client, statName, defaultValue) {
|
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) {
|
function sysStatAsString(statName, defaultValue) {
|
||||||
return (StatLog.getSystemStat(statName) || defaultValue).toLocaleString();
|
return (StatLog.getSystemStat(statName) || defaultValue).toLocaleString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const PREDEFINED_MCI_GENERATORS = {
|
const PREDEFINED_MCI_GENERATORS = {
|
||||||
//
|
//
|
||||||
// Board
|
// Board
|
||||||
//
|
//
|
||||||
BN : function boardName() { return Config().general.boardName; },
|
BN : function boardName() { return Config().general.boardName; },
|
||||||
|
|
||||||
// ENiGMA
|
// ENiGMA
|
||||||
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
|
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
|
||||||
VN : function version() { return packageJson.version; },
|
VN : function version() { return packageJson.version; },
|
||||||
|
|
||||||
// +op info
|
// +op info
|
||||||
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
|
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
|
||||||
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
|
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
|
||||||
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
|
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
|
||||||
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
|
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
|
||||||
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
|
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
|
||||||
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
|
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
|
||||||
// :TODO: op age, web, ?????
|
// :TODO: op age, web, ?????
|
||||||
|
|
||||||
//
|
//
|
||||||
// Current user / session
|
// Current user / session
|
||||||
//
|
//
|
||||||
UN : function userName(client) { return client.user.username; },
|
UN : function userName(client) { return client.user.username; },
|
||||||
UI : function userId(client) { return client.user.userId.toString(); },
|
UI : function userId(client) { return client.user.userId.toString(); },
|
||||||
UG : function groups(client) { return _.values(client.user.groups).join(', '); },
|
UG : function groups(client) { return _.values(client.user.groups).join(', '); },
|
||||||
UR : function realName(client) { return userStatAsString(client, 'real_name', ''); },
|
UR : function realName(client) { return userStatAsString(client, 'real_name', ''); },
|
||||||
LO : function location(client) { return userStatAsString(client, 'location', ''); },
|
LO : function location(client) { return userStatAsString(client, 'location', ''); },
|
||||||
UA : function age(client) { return client.user.getAge().toString(); },
|
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
|
BD : function birthdate(client) { return moment(client.user.properties.birthdate).format(client.currentTheme.helpers.getDateFormat()); }, // iNiQUiTY
|
||||||
US : function sex(client) { return userStatAsString(client, 'sex', ''); },
|
US : function sex(client) { return userStatAsString(client, 'sex', ''); },
|
||||||
UE : function emailAddres(client) { return userStatAsString(client, 'email_address', ''); },
|
UE : function emailAddres(client) { return userStatAsString(client, 'email_address', ''); },
|
||||||
UW : function webAddress(client) { return userStatAsString(client, 'web_address', ''); },
|
UW : function webAddress(client) { return userStatAsString(client, 'web_address', ''); },
|
||||||
UF : function affils(client) { return userStatAsString(client, 'affiliation', ''); },
|
UF : function affils(client) { return userStatAsString(client, 'affiliation', ''); },
|
||||||
UT : function themeId(client) { return userStatAsString(client, 'theme_id', ''); },
|
UT : function themeId(client) { return userStatAsString(client, 'theme_id', ''); },
|
||||||
UC : function loginCount(client) { return userStatAsString(client, 'login_count', 0); },
|
UC : function loginCount(client) { return userStatAsString(client, 'login_count', 0); },
|
||||||
ND : function connectedNode(client) { return client.node.toString(); },
|
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
|
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; },
|
ST : function serverName(client) { return client.session.serverName; },
|
||||||
FN : function activeFileBaseFilterName(client) {
|
FN : function activeFileBaseFilterName(client) {
|
||||||
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
||||||
return activeFilter ? activeFilter.name : '';
|
return activeFilter ? activeFilter.name : '';
|
||||||
},
|
},
|
||||||
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
||||||
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
||||||
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
||||||
return formatByteSize(byteSize, true); // true=withAbbr
|
return formatByteSize(byteSize, true); // true=withAbbr
|
||||||
},
|
},
|
||||||
UP : function userNumUploads(client) { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
UP : function userNumUploads(client) { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
||||||
UK : function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes
|
UK : function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes
|
||||||
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
||||||
return formatByteSize(byteSize, true); // true=withAbbr
|
return formatByteSize(byteSize, true); // true=withAbbr
|
||||||
},
|
},
|
||||||
NR : function userUpDownRatio(client) { // Obv/2
|
NR : function userUpDownRatio(client) { // Obv/2
|
||||||
return getUserRatio(client, 'ul_total_count', 'dl_total_count');
|
return getUserRatio(client, 'ul_total_count', 'dl_total_count');
|
||||||
},
|
},
|
||||||
KR : function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio
|
KR : function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio
|
||||||
return getUserRatio(client, 'ul_total_bytes', 'dl_total_bytes');
|
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()); },
|
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); },
|
PS : function userPostCount(client) { return userStatAsString(client, 'post_count', 0); },
|
||||||
PC : function userPostCallRatio(client) { return getUserRatio(client, 'post_count', 'login_count'); },
|
PC : function userPostCallRatio(client) { return getUserRatio(client, 'post_count', 'login_count'); },
|
||||||
|
|
||||||
MD : function currentMenuDescription(client) {
|
MD : function currentMenuDescription(client) {
|
||||||
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
MA : function messageAreaName(client) {
|
MA : function messageAreaName(client) {
|
||||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||||
return area ? area.name : '';
|
return area ? area.name : '';
|
||||||
},
|
},
|
||||||
MC : function messageConfName(client) {
|
MC : function messageConfName(client) {
|
||||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||||
return conf ? conf.name : '';
|
return conf ? conf.name : '';
|
||||||
},
|
},
|
||||||
ML : function messageAreaDescription(client) {
|
ML : function messageAreaDescription(client) {
|
||||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||||
return area ? area.desc : '';
|
return area ? area.desc : '';
|
||||||
},
|
},
|
||||||
CM : function messageConfDescription(client) {
|
CM : function messageConfDescription(client) {
|
||||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||||
return conf ? conf.desc : '';
|
return conf ? conf.desc : '';
|
||||||
},
|
},
|
||||||
|
|
||||||
SH : function termHeight(client) { return client.term.termHeight.toString(); },
|
SH : function termHeight(client) { return client.term.termHeight.toString(); },
|
||||||
SW : function termWidth(client) { return client.term.termWidth.toString(); },
|
SW : function termWidth(client) { return client.term.termWidth.toString(); },
|
||||||
|
|
||||||
//
|
//
|
||||||
// Date/Time
|
// Date/Time
|
||||||
//
|
//
|
||||||
// :TODO: change to CD for 'Current Date'
|
// :TODO: change to CD for 'Current Date'
|
||||||
DT : function date(client) { return moment().format(client.currentTheme.helpers.getDateFormat()); },
|
DT : function date(client) { return moment().format(client.currentTheme.helpers.getDateFormat()); },
|
||||||
CT : function time(client) { return moment().format(client.currentTheme.helpers.getTimeFormat()) ;},
|
CT : function time(client) { return moment().format(client.currentTheme.helpers.getTimeFormat()) ;},
|
||||||
|
|
||||||
//
|
//
|
||||||
// OS/System Info
|
// OS/System Info
|
||||||
//
|
//
|
||||||
OS : function operatingSystem() {
|
OS : function operatingSystem() {
|
||||||
return {
|
return {
|
||||||
linux : 'Linux',
|
linux : 'Linux',
|
||||||
darwin : 'Mac OS X',
|
darwin : 'Mac OS X',
|
||||||
win32 : 'Windows',
|
win32 : 'Windows',
|
||||||
sunos : 'SunOS',
|
sunos : 'SunOS',
|
||||||
freebsd : 'FreeBSD',
|
freebsd : 'FreeBSD',
|
||||||
}[os.platform()] || os.type();
|
}[os.platform()] || os.type();
|
||||||
},
|
},
|
||||||
|
|
||||||
OA : function systemArchitecture() { return os.arch(); },
|
OA : function systemArchitecture() { return os.arch(); },
|
||||||
|
|
||||||
SC : function systemCpuModel() {
|
SC : function systemCpuModel() {
|
||||||
//
|
//
|
||||||
// Clean up CPU strings a bit for better display
|
// Clean up CPU strings a bit for better display
|
||||||
//
|
//
|
||||||
return os.cpus()[0].model
|
return os.cpus()[0].model
|
||||||
.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
||||||
.replace(/\s+(?= )/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
|
// :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; },
|
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() {
|
RR : function randomRumor() {
|
||||||
// start the process of picking another random one
|
// start the process of picking another random one
|
||||||
setNextRandomRumor();
|
setNextRandomRumor();
|
||||||
|
|
||||||
return StatLog.getSystemStat('random_rumor');
|
return StatLog.getSystemStat('random_rumor');
|
||||||
},
|
},
|
||||||
|
|
||||||
//
|
//
|
||||||
// System File Base, Up/Download Info
|
// System File Base, Up/Download Info
|
||||||
//
|
//
|
||||||
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
||||||
//
|
//
|
||||||
SD : function systemNumDownloads() { return sysStatAsString('dl_total_count', 0); },
|
SD : function systemNumDownloads() { return sysStatAsString('dl_total_count', 0); },
|
||||||
SO : function systemByteDownload() {
|
SO : function systemByteDownload() {
|
||||||
const byteSize = StatLog.getSystemStatNum('dl_total_bytes');
|
const byteSize = StatLog.getSystemStatNum('dl_total_bytes');
|
||||||
return formatByteSize(byteSize, true); // true=withAbbr
|
return formatByteSize(byteSize, true); // true=withAbbr
|
||||||
},
|
},
|
||||||
SU : function systemNumUploads() { return sysStatAsString('ul_total_count', 0); },
|
SU : function systemNumUploads() { return sysStatAsString('ul_total_count', 0); },
|
||||||
SP : function systemByteUpload() {
|
SP : function systemByteUpload() {
|
||||||
const byteSize = StatLog.getSystemStatNum('ul_total_bytes');
|
const byteSize = StatLog.getSystemStatNum('ul_total_bytes');
|
||||||
return formatByteSize(byteSize, true); // true=withAbbr
|
return formatByteSize(byteSize, true); // true=withAbbr
|
||||||
},
|
},
|
||||||
TF : function totalFilesOnSystem() {
|
TF : function totalFilesOnSystem() {
|
||||||
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
||||||
return _.get(areaStats, 'totalFiles', 0).toLocaleString();
|
return _.get(areaStats, 'totalFiles', 0).toLocaleString();
|
||||||
},
|
},
|
||||||
TB : function totalBytesOnSystem() {
|
TB : function totalBytesOnSystem() {
|
||||||
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
const areaStats = StatLog.getSystemStat('file_base_area_stats');
|
||||||
const totalBytes = parseInt(_.get(areaStats, 'totalBytes', 0));
|
const totalBytes = parseInt(_.get(areaStats, 'totalBytes', 0));
|
||||||
return formatByteSize(totalBytes, true); // true=withAbbr
|
return formatByteSize(totalBytes, true); // true=withAbbr
|
||||||
},
|
},
|
||||||
|
|
||||||
// :TODO: PT - Messages posted *today* (Obv/2)
|
// :TODO: PT - Messages posted *today* (Obv/2)
|
||||||
// -> Include FTN/etc.
|
// -> Include FTN/etc.
|
||||||
// :TODO: NT - New users today (Obv/2)
|
// :TODO: NT - New users today (Obv/2)
|
||||||
// :TODO: CT - Calls *today* (Obv/2)
|
// :TODO: CT - Calls *today* (Obv/2)
|
||||||
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
||||||
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
||||||
// :TODO: TP - total message/posts on the system (Obv/2)
|
// :TODO: TP - total message/posts on the system (Obv/2)
|
||||||
// -> Include FTN/etc.
|
// -> Include FTN/etc.
|
||||||
// :TODO: LC - name of last caller to system (Obv/2)
|
// :TODO: LC - name of last caller to system (Obv/2)
|
||||||
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Special handling for XY
|
// Special handling for XY
|
||||||
//
|
//
|
||||||
XY : function xyHack() { return; /* nothing */ },
|
XY : function xyHack() { return; /* nothing */ },
|
||||||
};
|
};
|
||||||
|
|
||||||
function getPredefinedMCIValue(client, code) {
|
function getPredefinedMCIValue(client, code) {
|
||||||
|
|
||||||
if(!client || !code) {
|
if(!client || !code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const generator = PREDEFINED_MCI_GENERATORS[code];
|
const generator = PREDEFINED_MCI_GENERATORS[code];
|
||||||
|
|
||||||
if(generator) {
|
if(generator) {
|
||||||
let value;
|
let value;
|
||||||
try {
|
try {
|
||||||
value = generator(client);
|
value = generator(client);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
Log.error( { code : code, exception : e.message }, 'Exception caught generating predefined MCI value' );
|
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');
|
const _ = require('lodash');
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Rumorz',
|
name : 'Rumorz',
|
||||||
desc : 'Standard local rumorz',
|
desc : 'Standard local rumorz',
|
||||||
author : 'NuSkooler',
|
author : 'NuSkooler',
|
||||||
packageName : 'codes.l33t.enigma.rumorz',
|
packageName : 'codes.l33t.enigma.rumorz',
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATLOG_KEY_RUMORZ = 'system_rumorz';
|
const STATLOG_KEY_RUMORZ = 'system_rumorz';
|
||||||
|
|
||||||
const FormIds = {
|
const FormIds = {
|
||||||
View : 0,
|
View : 0,
|
||||||
Add : 1,
|
Add : 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const MciCodeIds = {
|
const MciCodeIds = {
|
||||||
ViewForm : {
|
ViewForm : {
|
||||||
Entries : 1,
|
Entries : 1,
|
||||||
AddPrompt : 2,
|
AddPrompt : 2,
|
||||||
},
|
},
|
||||||
AddForm : {
|
AddForm : {
|
||||||
NewEntry : 1,
|
NewEntry : 1,
|
||||||
EntryPreview : 2,
|
EntryPreview : 2,
|
||||||
AddPrompt : 3,
|
AddPrompt : 3,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getModule = class RumorzModule extends MenuModule {
|
exports.getModule = class RumorzModule extends MenuModule {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
this.menuMethods = {
|
this.menuMethods = {
|
||||||
viewAddScreen : (formData, extraArgs, cb) => {
|
viewAddScreen : (formData, extraArgs, cb) => {
|
||||||
return this.displayAddScreen(cb);
|
return this.displayAddScreen(cb);
|
||||||
},
|
},
|
||||||
|
|
||||||
addEntry : (formData, extraArgs, cb) => {
|
addEntry : (formData, extraArgs, cb) => {
|
||||||
if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) {
|
if(_.isString(formData.value.rumor) && renderStringLength(formData.value.rumor) > 0) {
|
||||||
const rumor = formData.value.rumor.trim(); // remove any trailing ws
|
const rumor = formData.value.rumor.trim(); // remove any trailing ws
|
||||||
|
|
||||||
StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, StatLog.KeepType.Forever, () => {
|
StatLog.appendSystemLogEntry(STATLOG_KEY_RUMORZ, rumor, StatLog.KeepDays.Forever, StatLog.KeepType.Forever, () => {
|
||||||
this.clearAddForm();
|
this.clearAddForm();
|
||||||
return this.displayViewScreen(true, cb); // true=cls
|
return this.displayViewScreen(true, cb); // true=cls
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// empty message - treat as if cancel was hit
|
// empty message - treat as if cancel was hit
|
||||||
return this.displayViewScreen(true, cb); // true=cls
|
return this.displayViewScreen(true, cb); // true=cls
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
cancelAdd : (formData, extraArgs, cb) => {
|
cancelAdd : (formData, extraArgs, cb) => {
|
||||||
this.clearAddForm();
|
this.clearAddForm();
|
||||||
return this.displayViewScreen(true, cb); // true=cls
|
return this.displayViewScreen(true, cb); // true=cls
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get config() { return this.menuConfig.config; }
|
get config() { return this.menuConfig.config; }
|
||||||
|
|
||||||
clearAddForm() {
|
clearAddForm() {
|
||||||
const newEntryView = this.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
const newEntryView = this.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||||
const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
const previewView = this.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||||
|
|
||||||
newEntryView.setText('');
|
newEntryView.setText('');
|
||||||
|
|
||||||
// preview is optional
|
// preview is optional
|
||||||
if(previewView) {
|
if(previewView) {
|
||||||
previewView.setText('');
|
previewView.setText('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initSequence() {
|
initSequence() {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.series(
|
async.series(
|
||||||
[
|
[
|
||||||
function beforeDisplayArt(callback) {
|
function beforeDisplayArt(callback) {
|
||||||
self.beforeArt(callback);
|
self.beforeArt(callback);
|
||||||
},
|
},
|
||||||
function display(callback) {
|
function display(callback) {
|
||||||
self.displayViewScreen(false, callback);
|
self.displayViewScreen(false, callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(err) {
|
if(err) {
|
||||||
// :TODO: Handle me -- initSequence() should really take a completion callback
|
// :TODO: Handle me -- initSequence() should really take a completion callback
|
||||||
}
|
}
|
||||||
self.finishedLoading();
|
self.finishedLoading();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayViewScreen(clearScreen, cb) {
|
displayViewScreen(clearScreen, cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
if(self.viewControllers.add) {
|
if(self.viewControllers.add) {
|
||||||
self.viewControllers.add.setFocus(false);
|
self.viewControllers.add.setFocus(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(clearScreen) {
|
if(clearScreen) {
|
||||||
self.client.term.rawWrite(resetScreen());
|
self.client.term.rawWrite(resetScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
self.config.art.entries,
|
self.config.art.entries,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font, trailingLF : false },
|
{ font : self.menuConfig.font, trailingLF : false },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initOrRedrawViewController(artData, callback) {
|
function initOrRedrawViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers.add)) {
|
if(_.isUndefined(self.viewControllers.add)) {
|
||||||
const vc = self.addViewController(
|
const vc = self.addViewController(
|
||||||
'view',
|
'view',
|
||||||
new ViewController( { client : self.client, formId : FormIds.View } )
|
new ViewController( { client : self.client, formId : FormIds.View } )
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds.View,
|
formId : FormIds.View,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
} else {
|
} else {
|
||||||
self.viewControllers.view.setFocus(true);
|
self.viewControllers.view.setFocus(true);
|
||||||
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
|
self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt).redraw();
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function fetchEntries(callback) {
|
function fetchEntries(callback) {
|
||||||
const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries);
|
const entriesView = self.viewControllers.view.getView(MciCodeIds.ViewForm.Entries);
|
||||||
|
|
||||||
StatLog.getSystemLogEntries(STATLOG_KEY_RUMORZ, StatLog.Order.Timestamp, (err, entries) => {
|
StatLog.getSystemLogEntries(STATLOG_KEY_RUMORZ, StatLog.Order.Timestamp, (err, entries) => {
|
||||||
return callback(err, entriesView, entries);
|
return callback(err, entriesView, entries);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function populateEntries(entriesView, entries, callback) {
|
function populateEntries(entriesView, entries, callback) {
|
||||||
const config = self.config;
|
const config = self.config;
|
||||||
const listFormat = config.listFormat || '{rumor}';
|
const listFormat = config.listFormat || '{rumor}';
|
||||||
const focusListFormat = config.focusListFormat || listFormat;
|
const focusListFormat = config.focusListFormat || listFormat;
|
||||||
|
|
||||||
entriesView.setItems(entries.map( e => stringFormat(listFormat, { rumor : e.log_value } ) ) );
|
entriesView.setItems(entries.map( e => stringFormat(listFormat, { rumor : e.log_value } ) ) );
|
||||||
entriesView.setFocusItems(entries.map(e => stringFormat(focusListFormat, { rumor : e.log_value } ) ) );
|
entriesView.setFocusItems(entries.map(e => stringFormat(focusListFormat, { rumor : e.log_value } ) ) );
|
||||||
entriesView.redraw();
|
entriesView.redraw();
|
||||||
|
|
||||||
return callback(null);
|
return callback(null);
|
||||||
},
|
},
|
||||||
function finalPrep(callback) {
|
function finalPrep(callback) {
|
||||||
const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt);
|
const promptView = self.viewControllers.view.getView(MciCodeIds.ViewForm.AddPrompt);
|
||||||
promptView.setFocusItemIndex(1); // default to NO
|
promptView.setFocusItemIndex(1); // default to NO
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAddScreen(cb) {
|
displayAddScreen(cb) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function clearAndDisplayArt(callback) {
|
function clearAndDisplayArt(callback) {
|
||||||
self.viewControllers.view.setFocus(false);
|
self.viewControllers.view.setFocus(false);
|
||||||
self.client.term.rawWrite(resetScreen());
|
self.client.term.rawWrite(resetScreen());
|
||||||
|
|
||||||
theme.displayThemedAsset(
|
theme.displayThemedAsset(
|
||||||
self.config.art.add,
|
self.config.art.add,
|
||||||
self.client,
|
self.client,
|
||||||
{ font : self.menuConfig.font },
|
{ font : self.menuConfig.font },
|
||||||
(err, artData) => {
|
(err, artData) => {
|
||||||
return callback(err, artData);
|
return callback(err, artData);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
function initOrRedrawViewController(artData, callback) {
|
function initOrRedrawViewController(artData, callback) {
|
||||||
if(_.isUndefined(self.viewControllers.add)) {
|
if(_.isUndefined(self.viewControllers.add)) {
|
||||||
const vc = self.addViewController(
|
const vc = self.addViewController(
|
||||||
'add',
|
'add',
|
||||||
new ViewController( { client : self.client, formId : FormIds.Add } )
|
new ViewController( { client : self.client, formId : FormIds.Add } )
|
||||||
);
|
);
|
||||||
|
|
||||||
const loadOpts = {
|
const loadOpts = {
|
||||||
callingMenu : self,
|
callingMenu : self,
|
||||||
mciMap : artData.mciMap,
|
mciMap : artData.mciMap,
|
||||||
formId : FormIds.Add,
|
formId : FormIds.Add,
|
||||||
};
|
};
|
||||||
|
|
||||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||||
} else {
|
} else {
|
||||||
self.viewControllers.add.setFocus(true);
|
self.viewControllers.add.setFocus(true);
|
||||||
self.viewControllers.add.redrawAll();
|
self.viewControllers.add.redrawAll();
|
||||||
self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry);
|
self.viewControllers.add.switchFocus(MciCodeIds.AddForm.NewEntry);
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function initPreviewUpdates(callback) {
|
function initPreviewUpdates(callback) {
|
||||||
const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
const previewView = self.viewControllers.add.getView(MciCodeIds.AddForm.EntryPreview);
|
||||||
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
const entryView = self.viewControllers.add.getView(MciCodeIds.AddForm.NewEntry);
|
||||||
if(previewView) {
|
if(previewView) {
|
||||||
let timerId;
|
let timerId;
|
||||||
entryView.on('key press', () => {
|
entryView.on('key press', () => {
|
||||||
clearTimeout(timerId);
|
clearTimeout(timerId);
|
||||||
timerId = setTimeout( () => {
|
timerId = setTimeout( () => {
|
||||||
const focused = self.viewControllers.add.getFocusedView();
|
const focused = self.viewControllers.add.getFocusedView();
|
||||||
if(focused === entryView) {
|
if(focused === entryView) {
|
||||||
previewView.setText(entryView.getData());
|
previewView.setText(entryView.getData());
|
||||||
focused.setFocus(true);
|
focused.setFocus(true);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return callback(null);
|
return callback(null);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
err => {
|
err => {
|
||||||
if(cb) {
|
if(cb) {
|
||||||
return cb(err);
|
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 ];
|
const SAUCE_VALID_DATA_TYPES = [0, 1, 2, 3, 4, 5, 6, 7, 8 ];
|
||||||
|
|
||||||
function readSAUCE(data, cb) {
|
function readSAUCE(data, cb) {
|
||||||
if(data.length < SAUCE_SIZE) {
|
if(data.length < SAUCE_SIZE) {
|
||||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
||||||
}
|
}
|
||||||
|
|
||||||
let sauceRec;
|
let sauceRec;
|
||||||
try {
|
try {
|
||||||
sauceRec = new Parser()
|
sauceRec = new Parser()
|
||||||
.buffer('id', { length : 5 } )
|
.buffer('id', { length : 5 } )
|
||||||
.buffer('version', { length : 2 } )
|
.buffer('version', { length : 2 } )
|
||||||
.buffer('title', { length: 35 } )
|
.buffer('title', { length: 35 } )
|
||||||
.buffer('author', { length : 20 } )
|
.buffer('author', { length : 20 } )
|
||||||
.buffer('group', { length: 20 } )
|
.buffer('group', { length: 20 } )
|
||||||
.buffer('date', { length: 8 } )
|
.buffer('date', { length: 8 } )
|
||||||
.uint32le('fileSize')
|
.uint32le('fileSize')
|
||||||
.int8('dataType')
|
.int8('dataType')
|
||||||
.int8('fileType')
|
.int8('fileType')
|
||||||
.uint16le('tinfo1')
|
.uint16le('tinfo1')
|
||||||
.uint16le('tinfo2')
|
.uint16le('tinfo2')
|
||||||
.uint16le('tinfo3')
|
.uint16le('tinfo3')
|
||||||
.uint16le('tinfo4')
|
.uint16le('tinfo4')
|
||||||
.int8('numComments')
|
.int8('numComments')
|
||||||
.int8('flags')
|
.int8('flags')
|
||||||
// :TODO: does this need to be optional?
|
// :TODO: does this need to be optional?
|
||||||
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
|
.buffer('tinfos', { length: 22 } ) // SAUCE 00.5
|
||||||
.parse(data.slice(data.length - SAUCE_SIZE));
|
.parse(data.slice(data.length - SAUCE_SIZE));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
return cb(Errors.Invalid('Invalid SAUCE record'));
|
return cb(Errors.Invalid('Invalid SAUCE record'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if(!SAUCE_ID.equals(sauceRec.id)) {
|
if(!SAUCE_ID.equals(sauceRec.id)) {
|
||||||
return cb(Errors.DoesNotExist('No SAUCE record present'));
|
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) {
|
if('00' !== ver) {
|
||||||
return cb(Errors.Invalid(`Unsupported SAUCE version: ${ver}`));
|
return cb(Errors.Invalid(`Unsupported SAUCE version: ${ver}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(-1 === SAUCE_VALID_DATA_TYPES.indexOf(sauceRec.dataType)) {
|
if(-1 === SAUCE_VALID_DATA_TYPES.indexOf(sauceRec.dataType)) {
|
||||||
return cb(Errors.Invalid(`Unsupported SAUCE DataType: ${sauceRec.dataType}`));
|
return cb(Errors.Invalid(`Unsupported SAUCE DataType: ${sauceRec.dataType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const sauce = {
|
const sauce = {
|
||||||
id : iconv.decode(sauceRec.id, 'cp437'),
|
id : iconv.decode(sauceRec.id, 'cp437'),
|
||||||
version : iconv.decode(sauceRec.version, 'cp437').trim(),
|
version : iconv.decode(sauceRec.version, 'cp437').trim(),
|
||||||
title : iconv.decode(sauceRec.title, 'cp437').trim(),
|
title : iconv.decode(sauceRec.title, 'cp437').trim(),
|
||||||
author : iconv.decode(sauceRec.author, 'cp437').trim(),
|
author : iconv.decode(sauceRec.author, 'cp437').trim(),
|
||||||
group : iconv.decode(sauceRec.group, 'cp437').trim(),
|
group : iconv.decode(sauceRec.group, 'cp437').trim(),
|
||||||
date : iconv.decode(sauceRec.date, 'cp437').trim(),
|
date : iconv.decode(sauceRec.date, 'cp437').trim(),
|
||||||
fileSize : sauceRec.fileSize,
|
fileSize : sauceRec.fileSize,
|
||||||
dataType : sauceRec.dataType,
|
dataType : sauceRec.dataType,
|
||||||
fileType : sauceRec.fileType,
|
fileType : sauceRec.fileType,
|
||||||
tinfo1 : sauceRec.tinfo1,
|
tinfo1 : sauceRec.tinfo1,
|
||||||
tinfo2 : sauceRec.tinfo2,
|
tinfo2 : sauceRec.tinfo2,
|
||||||
tinfo3 : sauceRec.tinfo3,
|
tinfo3 : sauceRec.tinfo3,
|
||||||
tinfo4 : sauceRec.tinfo4,
|
tinfo4 : sauceRec.tinfo4,
|
||||||
numComments : sauceRec.numComments,
|
numComments : sauceRec.numComments,
|
||||||
flags : sauceRec.flags,
|
flags : sauceRec.flags,
|
||||||
tinfos : sauceRec.tinfos,
|
tinfos : sauceRec.tinfos,
|
||||||
};
|
};
|
||||||
|
|
||||||
const dt = SAUCE_DATA_TYPES[sauce.dataType];
|
const dt = SAUCE_DATA_TYPES[sauce.dataType];
|
||||||
if(dt && dt.parser) {
|
if(dt && dt.parser) {
|
||||||
sauce[dt.name] = dt.parser(sauce);
|
sauce[dt.name] = dt.parser(sauce);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cb(null, sauce);
|
return cb(null, sauce);
|
||||||
}
|
}
|
||||||
|
|
||||||
// :TODO: These need completed:
|
// :TODO: These need completed:
|
||||||
const SAUCE_DATA_TYPES = {
|
const SAUCE_DATA_TYPES = {
|
||||||
0 : { name : 'None' },
|
0 : { name : 'None' },
|
||||||
1 : { name : 'Character', parser : parseCharacterSAUCE },
|
1 : { name : 'Character', parser : parseCharacterSAUCE },
|
||||||
2 : 'Bitmap',
|
2 : 'Bitmap',
|
||||||
3 : 'Vector',
|
3 : 'Vector',
|
||||||
4 : 'Audio',
|
4 : 'Audio',
|
||||||
5 : 'BinaryText',
|
5 : 'BinaryText',
|
||||||
6 : 'XBin',
|
6 : 'XBin',
|
||||||
7 : 'Archive',
|
7 : 'Archive',
|
||||||
8 : 'Executable',
|
8 : 'Executable',
|
||||||
};
|
};
|
||||||
|
|
||||||
const SAUCE_CHARACTER_FILE_TYPES = {
|
const SAUCE_CHARACTER_FILE_TYPES = {
|
||||||
0 : 'ASCII',
|
0 : 'ASCII',
|
||||||
1 : 'ANSi',
|
1 : 'ANSi',
|
||||||
2 : 'ANSiMation',
|
2 : 'ANSiMation',
|
||||||
3 : 'RIP script',
|
3 : 'RIP script',
|
||||||
4 : 'PCBoard',
|
4 : 'PCBoard',
|
||||||
5 : 'Avatar',
|
5 : 'Avatar',
|
||||||
6 : 'HTML',
|
6 : 'HTML',
|
||||||
7 : 'Source',
|
7 : 'Source',
|
||||||
8 : 'TundraDraw',
|
8 : 'TundraDraw',
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -129,53 +129,53 @@ const SAUCE_CHARACTER_FILE_TYPES = {
|
||||||
// Note that this is the same mapping that x84 uses. Be compatible!
|
// Note that this is the same mapping that x84 uses. Be compatible!
|
||||||
//
|
//
|
||||||
const SAUCE_FONT_TO_ENCODING_HINT = {
|
const SAUCE_FONT_TO_ENCODING_HINT = {
|
||||||
'Amiga MicroKnight' : 'amiga',
|
'Amiga MicroKnight' : 'amiga',
|
||||||
'Amiga MicroKnight+' : 'amiga',
|
'Amiga MicroKnight+' : 'amiga',
|
||||||
'Amiga mOsOul' : 'amiga',
|
'Amiga mOsOul' : 'amiga',
|
||||||
'Amiga P0T-NOoDLE' : 'amiga',
|
'Amiga P0T-NOoDLE' : 'amiga',
|
||||||
'Amiga Topaz 1' : 'amiga',
|
'Amiga Topaz 1' : 'amiga',
|
||||||
'Amiga Topaz 1+' : 'amiga',
|
'Amiga Topaz 1+' : 'amiga',
|
||||||
'Amiga Topaz 2' : 'amiga',
|
'Amiga Topaz 2' : 'amiga',
|
||||||
'Amiga Topaz 2+' : 'amiga',
|
'Amiga Topaz 2+' : 'amiga',
|
||||||
'Atari ATASCII' : 'atari',
|
'Atari ATASCII' : 'atari',
|
||||||
'IBM EGA43' : 'cp437',
|
'IBM EGA43' : 'cp437',
|
||||||
'IBM EGA' : 'cp437',
|
'IBM EGA' : 'cp437',
|
||||||
'IBM VGA25G' : 'cp437',
|
'IBM VGA25G' : 'cp437',
|
||||||
'IBM VGA50' : 'cp437',
|
'IBM VGA50' : 'cp437',
|
||||||
'IBM VGA' : 'cp437',
|
'IBM VGA' : 'cp437',
|
||||||
};
|
};
|
||||||
|
|
||||||
[
|
[
|
||||||
'437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
'437', '720', '737', '775', '819', '850', '852', '855', '857', '858',
|
||||||
'860', '861', '862', '863', '864', '865', '866', '869', '872'
|
'860', '861', '862', '863', '864', '865', '866', '869', '872'
|
||||||
].forEach( page => {
|
].forEach( page => {
|
||||||
const codec = 'cp' + page;
|
const codec = 'cp' + page;
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA43 ' + page] = codec;
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM EGA ' + 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 VGA25g ' + page] = codec;
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA50 ' + page] = codec;
|
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA50 ' + page] = codec;
|
||||||
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA ' + page] = codec;
|
SAUCE_FONT_TO_ENCODING_HINT['IBM VGA ' + page] = codec;
|
||||||
});
|
});
|
||||||
|
|
||||||
function parseCharacterSAUCE(sauce) {
|
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) {
|
if(sauce.fileType === 0 || sauce.fileType === 1 || sauce.fileType === 2) {
|
||||||
// convience: create ansiFlags
|
// convience: create ansiFlags
|
||||||
sauce.ansiFlags = sauce.flags;
|
sauce.ansiFlags = sauce.flags;
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while(i < sauce.tinfos.length && sauce.tinfos[i] !== 0x00) {
|
while(i < sauce.tinfos.length && sauce.tinfos[i] !== 0x00) {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fontName = iconv.decode(sauce.tinfos.slice(0, i), 'cp437');
|
const fontName = iconv.decode(sauce.tinfos.slice(0, i), 'cp437');
|
||||||
if(fontName.length > 0) {
|
if(fontName.length > 0) {
|
||||||
result.fontName = fontName;
|
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