* Some WIP on file area releated stuff - various partially implemented pieces coming together
* Some changes to database.js: Triggers for FTS were not being created properly * Misc fixes & improvements
This commit is contained in:
parent
7da0abdc39
commit
5a0b291a02
|
@ -33,6 +33,10 @@ class ACS {
|
|||
return this.check(area.acs, 'read', ACS.Defaults.MessageAreaRead);
|
||||
}
|
||||
|
||||
hasFileAreaRead(area) {
|
||||
return this.check(area.acs, 'read', ACS.Defaults.FileAreaRead);
|
||||
}
|
||||
|
||||
getConditionalValue(condArray, memberName) {
|
||||
assert(_.isArray(condArray));
|
||||
assert(_.isString(memberName));
|
||||
|
@ -59,6 +63,8 @@ class ACS {
|
|||
ACS.Defaults = {
|
||||
MessageAreaRead : 'GM[users]',
|
||||
MessageConfRead : 'GM[users]',
|
||||
|
||||
FileAreaRead : 'GM[users]',
|
||||
};
|
||||
|
||||
module.exports = ACS;
|
22
core/bbs.js
22
core/bbs.js
|
@ -16,6 +16,8 @@ const async = require('async');
|
|||
const util = require('util');
|
||||
const _ = require('lodash');
|
||||
const mkdirs = require('fs-extra').mkdirs;
|
||||
const fs = require('fs');
|
||||
const paths = require('path');
|
||||
|
||||
// our main entry point
|
||||
exports.bbsMain = bbsMain;
|
||||
|
@ -71,14 +73,23 @@ function bbsMain() {
|
|||
if(err) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
}
|
||||
callback(err);
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function listenConnections(callback) {
|
||||
startListening(callback);
|
||||
return startListening(callback);
|
||||
}
|
||||
],
|
||||
function complete(err) {
|
||||
// note this is escaped:
|
||||
fs.readFile(paths.join(__dirname, '../misc/startup_banner.asc'), 'utf8', (err, banner) => {
|
||||
console.info('ENiGMA½ Copyright (c) 2014-2016 Bryan Ashby');
|
||||
if(!err) {
|
||||
console.info(banner);
|
||||
}
|
||||
console.info('System started!');
|
||||
});
|
||||
|
||||
if(err) {
|
||||
console.error('Error initializing: ' + util.inspect(err));
|
||||
}
|
||||
|
@ -87,7 +98,9 @@ function bbsMain() {
|
|||
}
|
||||
|
||||
function shutdownSystem() {
|
||||
logger.log.info('Process interrupted, shutting down...');
|
||||
const msg = 'Process interrupted. Shutting down...';
|
||||
console.info(msg);
|
||||
logger.log.info(msg);
|
||||
|
||||
async.series(
|
||||
[
|
||||
|
@ -114,7 +127,8 @@ function shutdownSystem() {
|
|||
}
|
||||
],
|
||||
() => {
|
||||
process.exit();
|
||||
console.info('Goodbye!');
|
||||
return process.exit();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -222,6 +222,33 @@ function getDefaultConfig() {
|
|||
}
|
||||
},
|
||||
|
||||
fileTransferProtocols : {
|
||||
zmodem8kSz : {
|
||||
name : 'ZModem 8k',
|
||||
type : 'external',
|
||||
external : {
|
||||
sendCmd : 'sz', // Avail on Debian/Ubuntu based systems as the package "lrzsz"
|
||||
sendArgs : [
|
||||
'--zmodem', '-y', '--try-8k', '--binary', '--restricted', '{filePath}'
|
||||
],
|
||||
escapeTelnet : true, // set to true to escape Telnet codes such as IAC
|
||||
}
|
||||
},
|
||||
|
||||
zmodem8kSexyz : {
|
||||
name : 'ZModem 8k',
|
||||
type : 'external',
|
||||
external : {
|
||||
// :TODO: Look into shipping sexyz binaries or at least hosting them somewhere for common systems
|
||||
sendCmd : 'sexyz',
|
||||
sendArgs : [
|
||||
'-telnet', 'sz', '{filePath}'
|
||||
],
|
||||
escapeTelnet : true, // set to true to escape Telnet codes such as IAC
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
messageAreaDefaults : {
|
||||
//
|
||||
|
|
|
@ -175,17 +175,23 @@ const DB_INIT_TABLE = {
|
|||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_update BEFORE UPDATE ON message BEGIN
|
||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||
END;
|
||||
END;`
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_before_delete BEFORE DELETE ON message BEGIN
|
||||
DELETE FROM message_fts WHERE docid=old.rowid;
|
||||
END;
|
||||
END;`
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_after_update AFTER UPDATE ON message BEGIN
|
||||
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||
END;
|
||||
END;`
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN
|
||||
dbs.message.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS message_after_insert AFTER INSERT ON message BEGIN
|
||||
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||
END;`
|
||||
);
|
||||
|
@ -250,7 +256,7 @@ const DB_INIT_TABLE = {
|
|||
file_sha1 VARCHAR NOT NULL,
|
||||
file_name, /* FTS @ file_fts */
|
||||
desc, /* FTS @ file_fts */
|
||||
desc_long, /* FTS @ file_fts */
|
||||
desc_long, /* FTS @ file_fts */
|
||||
upload_by_username VARCHAR NOT NULL,
|
||||
upload_timestamp DATETIME NOT NULL
|
||||
);`
|
||||
|
@ -273,18 +279,24 @@ const DB_INIT_TABLE = {
|
|||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_before_update BEFORE UPDATE ON file BEGIN
|
||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
||||
END;`
|
||||
);
|
||||
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||
END;
|
||||
END;`
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
||||
INSERT INTO file_fts(docid, file_name, desc, long_desc) VALUES(new.rowid, new.file_name, new.desc, new.long_desc);
|
||||
END;
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
||||
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
||||
END;`
|
||||
);
|
||||
|
||||
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.long_desc);
|
||||
dbs.file.run(
|
||||
`CREATE TRIGGER IF NOT EXISTS file_after_insert AFTER INSERT ON file BEGIN
|
||||
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.desc_long);
|
||||
END;`
|
||||
);
|
||||
|
||||
|
|
|
@ -100,6 +100,7 @@ Door.prototype.run = function() {
|
|||
}
|
||||
|
||||
// Expand arg strings, e.g. {dropFile} -> DOOR32.SYS
|
||||
// :TODO: Use .map() here
|
||||
let args = _.clone(self.exeInfo.args); // we need a copy so the original is not modified
|
||||
|
||||
for(let i = 0; i < args.length; ++i) {
|
||||
|
|
|
@ -19,6 +19,7 @@ class EnigError extends Error {
|
|||
}
|
||||
}
|
||||
|
||||
// :TODO: Just use EnigError for all
|
||||
class EnigMenuError extends EnigError { }
|
||||
|
||||
exports.EnigError = EnigError;
|
||||
|
@ -27,4 +28,5 @@ exports.EnigMenuError = EnigMenuError;
|
|||
exports.Errors = {
|
||||
General : (reason, reasonCode) => new EnigError('An error occurred', -33000, reason, reasonCode),
|
||||
MenuStack : (reason, reasonCode) => new EnigMenuError('Menu stack error', -33001, reason, reasonCode),
|
||||
DoesNotExist : (reason, reasonCode) => new EnigError('Object does not exist', -33002, reason, reasonCode),
|
||||
};
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const Config = require('./config.js').config;
|
||||
const Log = require('./logger.js').log;
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
||||
exports.getAvailableFileAreas = getAvailableFileAreas;
|
||||
|
||||
exports.getFileAreaByTag = getFileAreaByTag;
|
||||
|
||||
function getAvailableFileAreas(client, options) {
|
||||
options = options || { includeSystemInternal : false };
|
||||
|
||||
// perform ACS check per conf & omit system_internal if desired
|
||||
return _.omit(Config.fileAreas.areas, (area, areaTag) => {
|
||||
/* if(!options.includeSystemInternal && 'system_internal' === confTag) {
|
||||
return true;
|
||||
}*/
|
||||
|
||||
return !client.acs.hasFileAreaRead(area);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getFileAreaByTag(areaTag) {
|
||||
return Config.fileAreas.areas[areaTag];
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
const fileDb = require('./database.js').dbs.file;
|
||||
const Errors = require('./enig_error.js').Errors;
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
|
||||
const FILE_TABLE_MEMBERS = [
|
||||
'file_id', 'area_tag', 'file_sha1', 'file_name',
|
||||
'desc', 'desc_long', 'upload_by_username', 'upload_timestamp'
|
||||
];
|
||||
|
||||
module.exports = class FileEntry {
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
|
||||
this.fileId = options.fileId || 0;
|
||||
this.areaTag = options.areaTag || '';
|
||||
this.meta = {};
|
||||
this.hashTags = new Set();
|
||||
}
|
||||
|
||||
load(fileId, cb) {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function loadBasicEntry(callback) {
|
||||
fileDb.get(
|
||||
`SELECT ${FILE_TABLE_MEMBERS.join(', ')}
|
||||
FROM file
|
||||
WHERE file_id=?
|
||||
LIMIT 1;`,
|
||||
[ fileId ],
|
||||
(err, file) => {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(!file) {
|
||||
return callback(Errors.DoesNotExist('No file is available by that ID'));
|
||||
}
|
||||
|
||||
// assign props from |file|
|
||||
FILE_TABLE_MEMBERS.forEach(prop => {
|
||||
self[_.camelCase(prop)] = file[prop];
|
||||
});
|
||||
|
||||
return callback(null);
|
||||
}
|
||||
);
|
||||
},
|
||||
function loadMeta(callback) {
|
||||
return self.loadMeta(callback);
|
||||
},
|
||||
function loadHashTags(callback) {
|
||||
return self.loadHashTags(callback);
|
||||
}
|
||||
],
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadMeta(cb) {
|
||||
fileDb.each(
|
||||
`SELECT meta_name, meta_value
|
||||
FROM file_meta
|
||||
WHERE file_id=?;`,
|
||||
[ this.fileId ],
|
||||
(err, meta) => {
|
||||
if(meta) {
|
||||
this.meta[meta.meta_name] = meta.meta_value;
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadHashTags(cb) {
|
||||
fileDb.each(
|
||||
`SELECT ht.hash_tag_id, ht.hash_tag
|
||||
FROM hash_tag ht
|
||||
WHERE ht.hash_tag_id IN (
|
||||
SELECT hash_tag_id
|
||||
FROM file_hash_tag
|
||||
WHERE file_id=?
|
||||
);`,
|
||||
[ this.fileId ],
|
||||
(err, hashTag) => {
|
||||
if(hashTag) {
|
||||
this.hashTags.add(hashTag.hash_tag);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
return cb(err);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static findFiles(criteria, cb) {
|
||||
// :TODO: build search here - return [ fileid1, fileid2, ... ]
|
||||
// free form
|
||||
// areaTag
|
||||
// tags
|
||||
// order by
|
||||
// sort
|
||||
|
||||
let sql =
|
||||
`SELECT file_id
|
||||
FROM file`;
|
||||
|
||||
let sqlWhere = '';
|
||||
|
||||
function appendWhereClause(clause) {
|
||||
if(sqlWhere) {
|
||||
sqlWhere += ' AND ';
|
||||
} else {
|
||||
sqlWhere += ' WHERE ';
|
||||
}
|
||||
sqlWhere += clause;
|
||||
}
|
||||
|
||||
if(criteria.areaTag) {
|
||||
appendWhereClause(`area_tag="${criteria.areaTag}"`);
|
||||
}
|
||||
|
||||
if(criteria.search) {
|
||||
appendWhereClause(
|
||||
`file_id IN (
|
||||
SELECT rowid
|
||||
FROM file_fts
|
||||
WHERE file_fts MATCH "${criteria.search.replace(/"/g,'""')}"
|
||||
)`
|
||||
);
|
||||
}
|
||||
|
||||
if(Array.isArray(criteria.hashTags)) {
|
||||
appendWhereClause(
|
||||
`file_id IN (
|
||||
SELECT file_id
|
||||
FROM file_hash_tag
|
||||
WHERE hash_tag_id IN (
|
||||
SELECT hash_tag_id
|
||||
FROM hash_tag
|
||||
WHERE hash_tag IN (${criteria.hashTags.join(',')})
|
||||
)
|
||||
)`
|
||||
);
|
||||
}
|
||||
|
||||
// :TODO: criteria.orderBy
|
||||
// :TODO: criteria.sort
|
||||
|
||||
sql += sqlWhere + ';';
|
||||
const matchingFileIds = [];
|
||||
fileDb.each(sql, (err, fileId) => {
|
||||
if(fileId) {
|
||||
matchingFileIds.push(fileId.file_id);
|
||||
}
|
||||
}, err => {
|
||||
return cb(err, matchingFileIds);
|
||||
});
|
||||
}
|
||||
};
|
|
@ -6,6 +6,8 @@ const pad = require('./string_util.js').pad;
|
|||
const stylizeString = require('./string_util.js').stylizeString;
|
||||
const renderStringLength = require('./string_util.js').renderStringLength;
|
||||
const renderSubstr = require('./string_util.js').renderSubstr;
|
||||
const formatByteSize = require('./string_util.js').formatByteSize;
|
||||
const formatByteSizeAbbr = require('./string_util.js').formatByteSizeAbbr;
|
||||
|
||||
// deps
|
||||
const _ = require('lodash');
|
||||
|
@ -265,6 +267,12 @@ const transformers = {
|
|||
styleSmallI : (s) => stylizeString(s, 'small i'),
|
||||
styleMixed : (s) => stylizeString(s, 'mixed'),
|
||||
styleL33t : (s) => stylizeString(s, 'l33t'),
|
||||
|
||||
// toMegs(), toKilobytes(), ...
|
||||
// toList(), toCommaList(),
|
||||
sizeWithAbbr : (n) => formatByteSize(n, true, 2),
|
||||
sizeWithoutAbbr : (n) => formatByteSize(n, false, 2),
|
||||
sizeAbbr : (n) => formatByteSizeAbbr(n),
|
||||
};
|
||||
|
||||
function transformValue(transformerName, value) {
|
||||
|
|
|
@ -13,6 +13,8 @@ exports.debugEscapedString = debugEscapedString;
|
|||
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
||||
exports.renderSubstr = renderSubstr;
|
||||
exports.renderStringLength = renderStringLength;
|
||||
exports.formatByteSizeAbbr = formatByteSizeAbbr;
|
||||
exports.formatByteSize = formatByteSize;
|
||||
exports.cleanControlCodes = cleanControlCodes;
|
||||
|
||||
// :TODO: create Unicode verison of this
|
||||
|
@ -286,6 +288,23 @@ function renderStringLength(s) {
|
|||
return len;
|
||||
}
|
||||
|
||||
const SIZE_ABBRS = [ 'B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB' ]; // :)
|
||||
|
||||
function formatByteSizeAbbr(byteSize) {
|
||||
return SIZE_ABBRS[Math.floor(Math.log(byteSize) / Math.log(1024))];
|
||||
}
|
||||
|
||||
function formatByteSize(byteSize, withAbbr, decimals) {
|
||||
withAbbr = withAbbr || false;
|
||||
decimals = decimals || 3;
|
||||
const i = Math.floor(Math.log(byteSize) / Math.log(1024));
|
||||
let result = parseFloat((byteSize / Math.pow(1024, i)).toFixed(decimals));
|
||||
if(withAbbr) {
|
||||
result += ` ${SIZE_ABBRS[i]}`;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// :TODO: See notes in word_wrap.js about need to consolidate the various ANSI related RegExp's
|
||||
//const REGEXP_ANSI_CONTROL_CODES = /(\x1b\x5b)([\?=;0-9]*?)([0-9A-ORZcf-npsu=><])/g;
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// enigma-bbs
|
||||
const MenuModule = require('./menu_module.js').MenuModule;
|
||||
const Config = require('./config.js').config;
|
||||
const stringFormat = require('./string_format.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
const pty = require('ptyw.js');
|
||||
|
||||
/*
|
||||
Resources
|
||||
|
||||
ZModem
|
||||
* http://gallium.inria.fr/~doligez/zmodem/zmodem.txt
|
||||
* https://github.com/protomouse/synchronet/blob/master/src/sbbs3/zmodem.c
|
||||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'Transfer file',
|
||||
desc : 'Sends or receives a file(s)',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
exports.getModule = class TransferFileModule extends MenuModule {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.config = this.menuConfig.config || {};
|
||||
this.config.protocol = this.config.protocol || 'zmodem8kSz';
|
||||
this.config.direction = this.config.direction || 'send';
|
||||
|
||||
this.protocolConfig = Config.fileTransferProtocols[this.config.protocol];
|
||||
|
||||
// :TODO: bring in extraArgs for path(s) to send when sending; Allow to hard code in config (e.g. for info pack/static downloads)
|
||||
}
|
||||
|
||||
restorePipeAfterExternalProc(pipe) {
|
||||
if(!this.pipeRestored) {
|
||||
this.pipeRestored = true;
|
||||
|
||||
this.client.term.output.unpipe(pipe);
|
||||
this.client.term.output.resume();
|
||||
}
|
||||
}
|
||||
|
||||
sendFiles(cb) {
|
||||
async.eachSeries(this.sendQueue, (filePath, next) => {
|
||||
// :TODO: built in protocols
|
||||
// :TODO: use protocol passed in
|
||||
this.executeExternalProtocolHandler(filePath, err => {
|
||||
return next(err);
|
||||
});
|
||||
}, err => {
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
executeExternalProtocolHandler(filePath, cb) {
|
||||
const external = this.protocolConfig.external;
|
||||
const cmd = external[`${this.config.direction}Cmd`];
|
||||
const args = external[`${this.config.direction}Args`].map(arg => {
|
||||
return stringFormat(arg, {
|
||||
filePath : filePath,
|
||||
});
|
||||
});
|
||||
|
||||
/*this.client.term.rawWrite(new Buffer(
|
||||
[
|
||||
255, 253, 0, // IAC DO TRANSMIT_BINARY
|
||||
255, 251, 0, // IAC WILL TRANSMIT_BINARY
|
||||
]
|
||||
));*/
|
||||
|
||||
const externalProc = pty.spawn(cmd, args, {
|
||||
cols : this.client.term.termWidth,
|
||||
rows : this.client.term.termHeight,
|
||||
// :TODO: cwd
|
||||
// :TODO: anything else??
|
||||
//env : self.exeInfo.env,
|
||||
});
|
||||
|
||||
this.client.term.output.pipe(externalProc);
|
||||
|
||||
/*this.client.term.output.on('data', data => {
|
||||
// let tmp = data.toString('binary').replace(/\xff\xff/g, '\xff');
|
||||
// proc.write(new Buffer(tmp, 'binary'));
|
||||
proc.write(data);
|
||||
});
|
||||
*/
|
||||
externalProc.on('data', data => {
|
||||
// needed for things like sz/rz
|
||||
if(external.escapeTelnet) {
|
||||
const tmp = data.toString('binary').replace(/\xff/g, '\xff\xff');
|
||||
this.client.term.rawWrite(new Buffer(tmp, 'binary'));
|
||||
} else {
|
||||
this.client.term.rawWrite(data);
|
||||
}
|
||||
});
|
||||
|
||||
externalProc.once('close', () => {
|
||||
return this.restorePipeAfterExternalProc(externalProc);
|
||||
});
|
||||
|
||||
externalProc.once('exit', exitCode => {
|
||||
this.restorePipeAfterExternalProc(externalProc);
|
||||
externalProc.removeAllListeners();
|
||||
|
||||
return cb(null);
|
||||
});
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function validateConfig(callback) {
|
||||
// :TODO:
|
||||
return callback(null);
|
||||
},
|
||||
function transferFiles(callback) {
|
||||
self.sendQueue = [ '/home/nuskooler/Downloads/fdoor100.zip' ]; // :TODO: testing of course
|
||||
return self.sendFiles(callback);
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
_____________________ _____ ____________________ __________\_ /
|
||||
\__ ____/\_ ____ \ /____/ / _____ __ \ / ______/ // /___jp!
|
||||
// __|___// | \// |// | \// | | \// \ /___ /_____
|
||||
/____ _____| __________ ___|__| ____| \ / _____ \
|
||||
---- \______\ -- |______\ ------ /______/ ---- |______\ - |______\ /__/ // ___/
|
||||
/__ _\
|
||||
<*> ENiGMA½ // HTTPS://GITHUB.COM/NUSKOOLER/ENIGMA-BBS <*> /__/
|
||||
|
||||
-------------------------------------------------------------------------------
|
|
@ -0,0 +1,222 @@
|
|||
/* jslint node: true */
|
||||
'use strict';
|
||||
|
||||
// ENiGMA½
|
||||
const MenuModule = require('../core/menu_module.js').MenuModule;
|
||||
const ViewController = require('../core/view_controller.js').ViewController;
|
||||
const ansi = require('../core/ansi_term.js');
|
||||
const theme = require('../core/theme.js');
|
||||
const FileEntry = require('../core/file_entry.js');
|
||||
const stringFormat = require('../core/string_format.js');
|
||||
const FileArea = require('../core/file_area.js');
|
||||
|
||||
// deps
|
||||
const async = require('async');
|
||||
const _ = require('lodash');
|
||||
const moment = require('moment');
|
||||
|
||||
/*
|
||||
Misc TODO
|
||||
* Allow rating to be user defined colors & characters/etc.
|
||||
*
|
||||
|
||||
|
||||
Well known file entry meta values:
|
||||
* upload_by_username
|
||||
* upload_by_user_id
|
||||
* file_md5
|
||||
* file_sha256
|
||||
* file_crc32
|
||||
* est_release_year
|
||||
* dl_count
|
||||
* byte_size
|
||||
* user_rating
|
||||
*
|
||||
*/
|
||||
|
||||
exports.moduleInfo = {
|
||||
name : 'File Area List',
|
||||
desc : 'Lists contents of file an file area',
|
||||
author : 'NuSkooler',
|
||||
};
|
||||
|
||||
const FormIds = {
|
||||
browse : 0,
|
||||
details : 1,
|
||||
};
|
||||
|
||||
const MciViewIds = {
|
||||
browse : {
|
||||
desc : 1,
|
||||
navMenu : 2,
|
||||
// 10+: customs
|
||||
},
|
||||
};
|
||||
|
||||
exports.getModule = class FileAreaList extends MenuModule {
|
||||
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
const config = this.menuConfig.config;
|
||||
|
||||
if(options.extraArgs) {
|
||||
this.filterCriteria = options.extraArgs.filterCriteria;
|
||||
}
|
||||
|
||||
this.filterCriteria = this.filterCriteria || {
|
||||
// :TODO: set area tag - all in current area by default
|
||||
};
|
||||
}
|
||||
|
||||
enter() {
|
||||
super.enter();
|
||||
}
|
||||
|
||||
leave() {
|
||||
super.leave();
|
||||
}
|
||||
|
||||
initSequence() {
|
||||
const self = this;
|
||||
|
||||
async.series(
|
||||
[
|
||||
function beforeArt(callback) {
|
||||
return self.beforeArt(callback);
|
||||
},
|
||||
function display(callback) {
|
||||
return self.displayBrowsePage(false, callback);
|
||||
}
|
||||
],
|
||||
() => {
|
||||
self.finishedLoading();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
displayBrowsePage(clearScreen, cb) {
|
||||
const self = this;
|
||||
const config = this.menuConfig.config;
|
||||
|
||||
async.waterfall(
|
||||
[
|
||||
function clearAndDisplayArt(callback) {
|
||||
if (clearScreen) {
|
||||
self.client.term.rawWrite(ansi.resetScreen());
|
||||
}
|
||||
|
||||
theme.displayThemedAsset(
|
||||
//config.art.browse,
|
||||
'FBRWSE',
|
||||
self.client,
|
||||
{ font : self.menuConfig.font, trailingLF : false },
|
||||
(err, artData) => {
|
||||
return callback(err, artData);
|
||||
}
|
||||
);
|
||||
},
|
||||
function prepeareViewController(artData, callback) {
|
||||
if(_.isUndefined(self.viewControllers.browse)) {
|
||||
const vc = self.addViewController(
|
||||
'browse',
|
||||
new ViewController( { client : self.client, formId : FormIds.browse } )
|
||||
);
|
||||
|
||||
const loadOpts = {
|
||||
callingMenu : self,
|
||||
mciMap : artData.mciMap,
|
||||
formId : FormIds.browse,
|
||||
};
|
||||
|
||||
return vc.loadFromMenuConfig(loadOpts, callback);
|
||||
}
|
||||
|
||||
self.viewControllers.view.setFocus(true);
|
||||
self.viewControllers.view.getView(MciViewIds.view.BBSList).redraw();
|
||||
|
||||
return callback(null);
|
||||
},
|
||||
function fetchEntryData(callback) {
|
||||
return self.loadFileIds(callback);
|
||||
},
|
||||
function loadCurrentFileInfo(callback) {
|
||||
self.currentFileEntry = new FileEntry();
|
||||
|
||||
self.currentFileEntry.load( self.fileList[ self.fileListPosition ], err => {
|
||||
return callback(err);
|
||||
});
|
||||
},
|
||||
function populateViews(callback) {
|
||||
if(_.isString(self.currentFileEntry.desc)) {
|
||||
const descView = self.viewControllers.browse.getView(MciViewIds.browse.desc);
|
||||
if(descView) {
|
||||
descView.setText(self.currentFileEntry.desc);
|
||||
//descView.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
const currEntry = self.currentFileEntry;
|
||||
const uploadTimestampFormat = config.browseUploadTimestampFormat || config.uploadTimestampFormat || 'YYYY-MMM-DD';
|
||||
const area = FileArea.getFileAreaByTag(currEntry.areaTag);
|
||||
const hashTagsSep = config.hashTagsSep || ', ';
|
||||
const entryInfo = {
|
||||
fileId : currEntry.fileId,
|
||||
areaTag : currEntry.areaTag,
|
||||
areaName : area.name || 'N/A',
|
||||
areaDesc : area.desc || 'N/A',
|
||||
fileSha1 : currEntry.fileSha1,
|
||||
fileName : currEntry.fileName,
|
||||
desc : currEntry.desc,
|
||||
descLong : currEntry.descLong,
|
||||
uploadByUsername : currEntry.uploadByUsername,
|
||||
uploadTimestamp : moment(currEntry.uploadTimestamp).format(uploadTimestampFormat),
|
||||
hashTags : Array.from(currEntry.hashTags).join(hashTagsSep),
|
||||
};
|
||||
|
||||
const META_NUMBERS = [ 'byte_size', 'dl_count' ];
|
||||
_.forEach(self.currentFileEntry.meta, (value, name) => {
|
||||
if(META_NUMBERS.indexOf(name) > -1) {
|
||||
value = parseInt(value);
|
||||
}
|
||||
entryInfo[_.camelCase(name)] = value;
|
||||
});
|
||||
|
||||
|
||||
|
||||
// entryInfo.fileSize = 1241234; // :TODO: REMOVE ME!
|
||||
|
||||
// 10+ are custom textviews
|
||||
let textView;
|
||||
let customMciId = 10;
|
||||
|
||||
while( (textView = self.viewControllers.browse.getView(customMciId)) ) {
|
||||
const key = `browseInfoFormat${customMciId}`;
|
||||
const format = config[key];
|
||||
|
||||
if(format) {
|
||||
textView.setText(stringFormat(format, entryInfo));
|
||||
}
|
||||
|
||||
++customMciId;
|
||||
}
|
||||
}
|
||||
],
|
||||
err => {
|
||||
if(cb) {
|
||||
return cb(err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
loadFileIds(cb) {
|
||||
this.fileListPosition = 0;
|
||||
|
||||
FileEntry.findFiles(this.filterCriteria, (err, fileIds) => {
|
||||
this.fileList = fileIds;
|
||||
return cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
|
@ -127,7 +127,7 @@ MessageListModule.prototype.enter = function() {
|
|||
if(this.messageAreaTag) {
|
||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
|
||||
} else {
|
||||
this.messageAreaTag = this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
this.messageAreaTag = this.client.user.properties.message_area_tag;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue