Initial sync up with master after Prettier

This commit is contained in:
Bryan Ashby
2022-06-12 13:57:46 -06:00
177 changed files with 23158 additions and 17630 deletions

View File

@@ -2,30 +2,30 @@
'use strict';
// enigma-bbs
const MenuModule = require('./menu_module.js').MenuModule;
const Config = require('./config.js').get;
const stringFormat = require('./string_format.js');
const Errors = require('./enig_error.js').Errors;
const DownloadQueue = require('./download_queue.js');
const StatLog = require('./stat_log.js');
const FileEntry = require('./file_entry.js');
const Log = require('./logger.js').log;
const Events = require('./events.js');
const UserProps = require('./user_property.js');
const SysProps = require('./system_property.js');
const MenuModule = require('./menu_module.js').MenuModule;
const Config = require('./config.js').get;
const stringFormat = require('./string_format.js');
const Errors = require('./enig_error.js').Errors;
const DownloadQueue = require('./download_queue.js');
const StatLog = require('./stat_log.js');
const FileEntry = require('./file_entry.js');
const Log = require('./logger.js').log;
const Events = require('./events.js');
const UserProps = require('./user_property.js');
const SysProps = require('./system_property.js');
// deps
const async = require('async');
const _ = require('lodash');
const pty = require('node-pty');
const temptmp = require('temptmp').createTrackedSession('transfer_file');
const paths = require('path');
const fs = require('graceful-fs');
const fse = require('fs-extra');
const async = require('async');
const _ = require('lodash');
const pty = require('node-pty');
const temptmp = require('temptmp').createTrackedSession('transfer_file');
const paths = require('path');
const fs = require('graceful-fs');
const fse = require('fs-extra');
// some consts
const SYSTEM_EOL = require('os').EOL;
const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc.
const SYSTEM_EOL = require('os').EOL;
const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc.
/*
Notes
@@ -44,9 +44,9 @@ const TEMP_SUFFIX = 'enigtf-'; // temp CWD/etc.
*/
exports.moduleInfo = {
name : 'Transfer file',
desc : 'Sends or receives a file(s)',
author : 'NuSkooler',
name: 'Transfer file',
desc: 'Sends or receives a file(s)',
author: 'NuSkooler',
};
exports.getModule = class TransferFileModule extends MenuModule {
@@ -59,56 +59,58 @@ exports.getModule = class TransferFileModule extends MenuModule {
// Most options can be set via extraArgs or config block
//
const config = Config();
if(options.extraArgs) {
if(options.extraArgs.protocol) {
this.protocolConfig = config.fileTransferProtocols[options.extraArgs.protocol];
if (options.extraArgs) {
if (options.extraArgs.protocol) {
this.protocolConfig =
config.fileTransferProtocols[options.extraArgs.protocol];
}
if(options.extraArgs.direction) {
if (options.extraArgs.direction) {
this.direction = options.extraArgs.direction;
}
if(options.extraArgs.sendQueue) {
if (options.extraArgs.sendQueue) {
this.sendQueue = options.extraArgs.sendQueue;
}
if(options.extraArgs.recvFileName) {
if (options.extraArgs.recvFileName) {
this.recvFileName = options.extraArgs.recvFileName;
}
if(options.extraArgs.recvDirectory) {
if (options.extraArgs.recvDirectory) {
this.recvDirectory = options.extraArgs.recvDirectory;
}
} else {
if(this.config.protocol) {
if (this.config.protocol) {
this.protocolConfig = config.fileTransferProtocols[this.config.protocol];
}
if(this.config.direction) {
if (this.config.direction) {
this.direction = this.config.direction;
}
if(this.config.sendQueue) {
if (this.config.sendQueue) {
this.sendQueue = this.config.sendQueue;
}
if(this.config.recvFileName) {
if (this.config.recvFileName) {
this.recvFileName = this.config.recvFileName;
}
if(this.config.recvDirectory) {
if (this.config.recvDirectory) {
this.recvDirectory = this.config.recvDirectory;
}
}
this.protocolConfig = this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
this.direction = this.direction || 'send';
this.sendQueue = this.sendQueue || [];
this.protocolConfig =
this.protocolConfig || config.fileTransferProtocols.zmodem8kSz; // try for *something*
this.direction = this.direction || 'send';
this.sendQueue = this.sendQueue || [];
// Ensure sendQueue is an array of objects that contain at least a 'path' member
this.sendQueue = this.sendQueue.map(item => {
if(_.isString(item)) {
return { path : item };
if (_.isString(item)) {
return { path: item };
} else {
return item;
}
@@ -118,11 +120,11 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
isSending() {
return ('send' === this.direction);
return 'send' === this.direction;
}
restorePipeAfterExternalProc() {
if(!this.pipeRestored) {
if (!this.pipeRestored) {
this.pipeRestored = true;
this.client.restoreDataHandler();
@@ -134,14 +136,16 @@ exports.getModule = class TransferFileModule extends MenuModule {
// :TODO: Look into this further
const allFiles = this.sendQueue.map(f => f.path);
this.executeExternalProtocolHandlerForSend(allFiles, err => {
if(err) {
this.client.log.warn( { files : allFiles, error : err.message }, 'Error sending file(s)' );
if (err) {
this.client.log.warn(
{ files: allFiles, error: err.message },
'Error sending file(s)'
);
} else {
const sentFiles = [];
this.sendQueue.forEach(f => {
f.sent = true;
sentFiles.push(f.path);
});
this.client.log.info( { sentFiles : sentFiles }, `User "${self.client.user.username}" uploaded ${sentFiles.length} file(s)` );
@@ -196,29 +200,32 @@ exports.getModule = class TransferFileModule extends MenuModule {
// Move |src| -> |dst| renaming to file(1).ext, file(2).ext, etc.
// in the case of collisions.
//
const dstPath = paths.dirname(dst);
const dstFileExt = paths.extname(dst);
const dstPath = paths.dirname(dst);
const dstFileExt = paths.extname(dst);
const dstFileSuffix = paths.basename(dst, dstFileExt);
let renameIndex = 0;
let movedOk = false;
let renameIndex = 0;
let movedOk = false;
let tryDstPath;
async.until(
(callback) => callback(null, movedOk), // until moved OK
(cb) => {
if(0 === renameIndex) {
callback => callback(null, movedOk), // until moved OK
cb => {
if (0 === renameIndex) {
// try originally supplied path first
tryDstPath = dst;
} else {
tryDstPath = paths.join(dstPath, `${dstFileSuffix}(${renameIndex})${dstFileExt}`);
tryDstPath = paths.join(
dstPath,
`${dstFileSuffix}(${renameIndex})${dstFileExt}`
);
}
fse.move(src, tryDstPath, err => {
if(err) {
if('EEXIST' === err.code) {
if (err) {
if ('EEXIST' === err.code) {
renameIndex += 1;
return cb(null); // keep trying
return cb(null); // keep trying
}
return cb(err);
@@ -236,25 +243,27 @@ exports.getModule = class TransferFileModule extends MenuModule {
recvFiles(cb) {
this.executeExternalProtocolHandlerForRecv(err => {
if(err) {
if (err) {
return cb(err);
}
this.recvFilePaths = [];
if(this.recvFileName) {
if (this.recvFileName) {
//
// file name specified - we expect a single file in |this.recvDirectory|
// by the name of |this.recvFileName|
//
const recvFullPath = paths.join(this.recvDirectory, this.recvFileName);
fs.stat(recvFullPath, (err, stats) => {
if(err) {
if (err) {
return cb(err);
}
if(!stats.isFile()) {
return cb(Errors.Invalid('Expected file entry in recv directory'));
if (!stats.isFile()) {
return cb(
Errors.Invalid('Expected file entry in recv directory')
);
}
this.recvFilePaths.push(recvFullPath);
@@ -265,83 +274,96 @@ exports.getModule = class TransferFileModule extends MenuModule {
// Blind Upload (recv): files in |this.recvDirectory| should be named appropriately already
//
fs.readdir(this.recvDirectory, (err, files) => {
if(err) {
if (err) {
return cb(err);
}
// stat each to grab files only
async.each(files, (fileName, nextFile) => {
const recvFullPath = paths.join(this.recvDirectory, fileName);
async.each(
files,
(fileName, nextFile) => {
const recvFullPath = paths.join(this.recvDirectory, fileName);
fs.stat(recvFullPath, (err, stats) => {
if(err) {
this.client.log.warn('Failed to stat file', { path : recvFullPath } );
return nextFile(null); // just try the next one
}
fs.stat(recvFullPath, (err, stats) => {
if (err) {
this.client.log.warn('Failed to stat file', {
path: recvFullPath,
});
return nextFile(null); // just try the next one
}
if(stats.isFile()) {
this.recvFilePaths.push(recvFullPath);
}
if (stats.isFile()) {
this.recvFilePaths.push(recvFullPath);
}
return nextFile(null);
});
}, () => {
return cb(null);
});
return nextFile(null);
});
},
() => {
return cb(null);
}
);
});
}
});
}
pathWithTerminatingSeparator(path) {
if(path && paths.sep !== path.charAt(path.length - 1)) {
if (path && paths.sep !== path.charAt(path.length - 1)) {
path = path + paths.sep;
}
return path;
}
prepAndBuildSendArgs(filePaths, cb) {
const externalArgs = this.protocolConfig.external['sendArgs'];
const externalArgs = this.protocolConfig.external['sendArgs'];
async.waterfall(
[
function getTempFileListPath(callback) {
const hasFileList = externalArgs.find(ea => (ea.indexOf('{fileListPath}') > -1) );
if(!hasFileList) {
const hasFileList = externalArgs.find(
ea => ea.indexOf('{fileListPath}') > -1
);
if (!hasFileList) {
return callback(null, null);
}
temptmp.open( { prefix : TEMP_SUFFIX, suffix : '.txt' }, (err, tempFileInfo) => {
if(err) {
return callback(err); // failed to create it
}
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL), err => {
if(err) {
return callback(err);
temptmp.open(
{ prefix: TEMP_SUFFIX, suffix: '.txt' },
(err, tempFileInfo) => {
if (err) {
return callback(err); // failed to create it
}
fs.close(tempFileInfo.fd, err => {
return callback(err, tempFileInfo.path);
fs.write(tempFileInfo.fd, filePaths.join(SYSTEM_EOL), err => {
if (err) {
return callback(err);
}
fs.close(tempFileInfo.fd, err => {
return callback(err, tempFileInfo.path);
});
});
});
});
}
);
},
function createArgs(tempFileListPath, callback) {
// initial args: ignore {filePaths} as we must break that into it's own sep array items
const args = externalArgs.map(arg => {
return '{filePaths}' === arg ? arg : stringFormat(arg, {
fileListPath : tempFileListPath || '',
});
return '{filePaths}' === arg
? arg
: stringFormat(arg, {
fileListPath: tempFileListPath || '',
});
});
const filePathsPos = args.indexOf('{filePaths}');
if(filePathsPos > -1) {
if (filePathsPos > -1) {
// 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);
}
},
],
(err, args) => {
return cb(err, args);
@@ -350,47 +372,52 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
prepAndBuildRecvArgs(cb) {
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
const externalArgs = this.protocolConfig.external[argsKey];
const args = externalArgs.map(arg => stringFormat(arg, {
uploadDir : this.recvDirectory,
fileName : this.recvFileName || '',
}));
const argsKey = this.recvFileName ? 'recvArgsNonBatch' : 'recvArgs';
const externalArgs = this.protocolConfig.external[argsKey];
const args = externalArgs.map(arg =>
stringFormat(arg, {
uploadDir: this.recvDirectory,
fileName: this.recvFileName || '',
})
);
return cb(null, args);
}
executeExternalProtocolHandler(args, cb) {
const external = this.protocolConfig.external;
const cmd = external[`${this.direction}Cmd`];
const external = this.protocolConfig.external;
const cmd = external[`${this.direction}Cmd`];
// support for handlers that need IACs taken care of over Telnet/etc.
const processIACs =
external.processIACs ||
external.escapeTelnet; // deprecated name
const processIACs = external.processIACs || external.escapeTelnet; // deprecated name
// :TODO: we should only do this when over Telnet (or derived, such as WebSockets)?
const IAC = Buffer.from([255]);
const EscapedIAC = Buffer.from([255, 255]);
const IAC = Buffer.from([255]);
const EscapedIAC = Buffer.from([255, 255]);
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'
);
const spawnOpts = {
cols : this.client.term.termWidth,
rows : this.client.term.termHeight,
cwd : this.recvDirectory,
encoding : null, // don't bork our data!
cols: this.client.term.termWidth,
rows: this.client.term.termHeight,
cwd: this.recvDirectory,
encoding: null, // don't bork our data!
};
const externalProc = pty.spawn(cmd, args, spawnOpts);
let dataHits = 0;
const updateActivity = () => {
if (0 === (dataHits++ % 4)) {
if (0 === dataHits++ % 4) {
this.client.explicitActivityTimeUpdate();
}
};
@@ -399,7 +426,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
updateActivity();
// needed for things like sz/rz
if(processIACs) {
if (processIACs) {
let iacPos = data.indexOf(EscapedIAC);
if (-1 === iacPos) {
return externalProc.write(data);
@@ -430,7 +457,7 @@ exports.getModule = class TransferFileModule extends MenuModule {
updateActivity();
// needed for things like sz/rz
if(processIACs) {
if (processIACs) {
let iacPos = data.indexOf(IAC);
if (-1 === iacPos) {
return this.client.term.rawWrite(data);
@@ -459,23 +486,33 @@ exports.getModule = class TransferFileModule extends MenuModule {
return this.restorePipeAfterExternalProc();
});
externalProc.once('exit', (exitCode) => {
this.client.log.debug( { cmd : cmd, args : args, exitCode : exitCode }, 'Process exited' );
externalProc.once('exit', exitCode => {
this.client.log.debug(
{ cmd: cmd, args: args, exitCode: exitCode },
'Process exited'
);
this.restorePipeAfterExternalProc();
externalProc.removeAllListeners();
return cb(exitCode ? Errors.ExternalProcess(`Process exited with exit code ${exitCode}`, 'EBADEXIT') : null);
return cb(
exitCode
? Errors.ExternalProcess(
`Process exited with exit code ${exitCode}`,
'EBADEXIT'
)
: null
);
});
}
executeExternalProtocolHandlerForSend(filePaths, cb) {
if(!Array.isArray(filePaths)) {
filePaths = [ filePaths ];
if (!Array.isArray(filePaths)) {
filePaths = [filePaths];
}
this.prepAndBuildSendArgs(filePaths, (err, args) => {
if(err) {
if (err) {
return cb(err);
}
@@ -486,8 +523,8 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
executeExternalProtocolHandlerForRecv(cb) {
this.prepAndBuildRecvArgs( (err, args) => {
if(err) {
this.prepAndBuildRecvArgs((err, args) => {
if (err) {
return cb(err);
}
@@ -498,91 +535,115 @@ exports.getModule = class TransferFileModule extends MenuModule {
}
getMenuResult() {
if(this.isSending()) {
return { sentFileIds : this.sentFileIds };
if (this.isSending()) {
return { sentFileIds: this.sentFileIds };
} else {
return { recvFilePaths : this.recvFilePaths };
return { recvFilePaths: this.recvFilePaths };
}
}
updateSendStats(cb) {
let downloadBytes = 0;
let downloadCount = 0;
let fileIds = [];
let downloadBytes = 0;
let downloadCount = 0;
let fileIds = [];
async.each(this.sendQueue, (queueItem, next) => {
if(!queueItem.sent) {
return next(null);
}
if(queueItem.fileId) {
fileIds.push(queueItem.fileId);
}
if(_.isNumber(queueItem.byteSize)) {
downloadCount += 1;
downloadBytes += queueItem.byteSize;
return next(null);
}
// we just have a path - figure it out
fs.stat(queueItem.path, (err, stats) => {
if(err) {
this.client.log.warn( { error : err.message, path : queueItem.path }, 'File stat failed' );
} else {
downloadCount += 1;
downloadBytes += stats.size;
async.each(
this.sendQueue,
(queueItem, next) => {
if (!queueItem.sent) {
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
StatLog.incrementUserStat(this.client.user, UserProps.FileDlTotalCount, downloadCount);
StatLog.incrementUserStat(this.client.user, UserProps.FileDlTotalBytes, downloadBytes);
if (queueItem.fileId) {
fileIds.push(queueItem.fileId);
}
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, downloadCount);
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, downloadBytes);
if (_.isNumber(queueItem.byteSize)) {
downloadCount += 1;
downloadBytes += queueItem.byteSize;
return next(null);
}
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayCount, downloadCount);
StatLog.incrementNonPersistentSystemStat(SysProps.FileDlTodayBytes, downloadBytes);
// we just have a path - figure it out
fs.stat(queueItem.path, (err, stats) => {
if (err) {
this.client.log.warn(
{ error: err.message, path: queueItem.path },
'File stat failed'
);
} else {
downloadCount += 1;
downloadBytes += stats.size;
}
fileIds.forEach(fileId => {
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
});
return next(null);
});
},
() => {
// All stats/meta currently updated via fire & forget - if this is ever a issue, we can wait for callbacks
StatLog.incrementUserStat(
this.client.user,
UserProps.FileDlTotalCount,
downloadCount
);
StatLog.incrementUserStat(
this.client.user,
UserProps.FileDlTotalBytes,
downloadBytes
);
return cb(null);
});
StatLog.incrementSystemStat(SysProps.FileDlTotalCount, downloadCount);
StatLog.incrementSystemStat(SysProps.FileDlTotalBytes, downloadBytes);
fileIds.forEach(fileId => {
FileEntry.incrementAndPersistMetaValue(fileId, 'dl_count', 1);
});
return cb(null);
}
);
}
updateRecvStats(cb) {
let uploadBytes = 0;
let uploadCount = 0;
async.each(this.recvFilePaths, (filePath, next) => {
// we just have a path - figure it out
fs.stat(filePath, (err, stats) => {
if(err) {
this.client.log.warn( { error : err.message, path : filePath }, 'File stat failed' );
} else {
uploadCount += 1;
uploadBytes += stats.size;
}
async.each(
this.recvFilePaths,
(filePath, next) => {
// we just have a path - figure it out
fs.stat(filePath, (err, stats) => {
if (err) {
this.client.log.warn(
{ error: err.message, path: filePath },
'File stat failed'
);
} else {
uploadCount += 1;
uploadBytes += stats.size;
}
return next(null);
});
}, () => {
StatLog.incrementUserStat(this.client.user, UserProps.FileUlTotalCount, uploadCount);
StatLog.incrementUserStat(this.client.user, UserProps.FileUlTotalBytes, uploadBytes);
return next(null);
});
},
() => {
StatLog.incrementUserStat(
this.client.user,
UserProps.FileUlTotalCount,
uploadCount
);
StatLog.incrementUserStat(
this.client.user,
UserProps.FileUlTotalBytes,
uploadBytes
);
StatLog.incrementSystemStat(SysProps.FileUlTotalCount, uploadCount);
StatLog.incrementSystemStat(SysProps.FileUlTotalBytes, uploadBytes);
StatLog.incrementSystemStat(SysProps.FileUlTotalCount, uploadCount);
StatLog.incrementSystemStat(SysProps.FileUlTotalBytes, uploadBytes);
StatLog.incrementNonPersistentSystemStat(SysProps.FileUlTodayCount, uploadCount);
StatLog.incrementNonPersistentSystemStat(SysProps.FileUlTodayBytes, uploadBytes);
return cb(null);
});
return cb(null);
}
);
}
initSequence() {
@@ -593,41 +654,38 @@ exports.getModule = class TransferFileModule extends MenuModule {
async.series(
[
function validateConfig(callback) {
if(self.isSending()) {
if(!Array.isArray(self.sendQueue)) {
self.sendQueue = [ self.sendQueue ];
if (self.isSending()) {
if (!Array.isArray(self.sendQueue)) {
self.sendQueue = [self.sendQueue];
}
}
return callback(null);
},
function transferFiles(callback) {
if(self.isSending()) {
self.sendFiles( err => {
if(err) {
if (self.isSending()) {
self.sendFiles(err => {
if (err) {
return callback(err);
}
const sentFileIds = [];
self.sendQueue.forEach(queueItem => {
if(queueItem.sent && queueItem.fileId) {
if (queueItem.sent && queueItem.fileId) {
sentFileIds.push(queueItem.fileId);
}
});
if(sentFileIds.length > 0) {
if (sentFileIds.length > 0) {
// remove items we sent from the D/L queue
const dlQueue = new DownloadQueue(self.client);
const dlFileEntries = dlQueue.removeItems(sentFileIds);
// fire event for downloaded entries
Events.emit(
Events.getSystemEvents().UserDownload,
{
user : self.client.user,
files : dlFileEntries
}
);
Events.emit(Events.getSystemEvents().UserDownload, {
user: self.client.user,
files: dlFileEntries,
});
self.sentFileIds = sentFileIds;
}
@@ -635,29 +693,32 @@ exports.getModule = class TransferFileModule extends MenuModule {
return callback(null);
});
} else {
self.recvFiles( err => {
self.recvFiles(err => {
return callback(err);
});
}
},
function cleanupTempFiles(callback) {
temptmp.cleanup( paths => {
Log.debug( { paths : paths, sessionId : temptmp.sessionId }, 'Temporary files cleaned up' );
temptmp.cleanup(paths => {
Log.debug(
{ paths: paths, sessionId: temptmp.sessionId },
'Temporary files cleaned up'
);
});
return callback(null);
},
function updateUserAndSystemStats(callback) {
if(self.isSending()) {
if (self.isSending()) {
return self.updateSendStats(callback);
} else {
return self.updateRecvStats(callback);
}
}
},
],
err => {
if(err) {
self.client.log.warn( { error : err.message }, 'File transfer error');
if (err) {
self.client.log.warn({ error: err.message }, 'File transfer error');
}
return self.prevMenu();