* 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);
|
return this.check(area.acs, 'read', ACS.Defaults.MessageAreaRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasFileAreaRead(area) {
|
||||||
|
return this.check(area.acs, 'read', ACS.Defaults.FileAreaRead);
|
||||||
|
}
|
||||||
|
|
||||||
getConditionalValue(condArray, memberName) {
|
getConditionalValue(condArray, memberName) {
|
||||||
assert(_.isArray(condArray));
|
assert(_.isArray(condArray));
|
||||||
assert(_.isString(memberName));
|
assert(_.isString(memberName));
|
||||||
|
@ -59,6 +63,8 @@ class ACS {
|
||||||
ACS.Defaults = {
|
ACS.Defaults = {
|
||||||
MessageAreaRead : 'GM[users]',
|
MessageAreaRead : 'GM[users]',
|
||||||
MessageConfRead : 'GM[users]',
|
MessageConfRead : 'GM[users]',
|
||||||
|
|
||||||
|
FileAreaRead : 'GM[users]',
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ACS;
|
module.exports = ACS;
|
22
core/bbs.js
22
core/bbs.js
|
@ -16,6 +16,8 @@ const async = require('async');
|
||||||
const util = require('util');
|
const util = require('util');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const mkdirs = require('fs-extra').mkdirs;
|
const mkdirs = require('fs-extra').mkdirs;
|
||||||
|
const fs = require('fs');
|
||||||
|
const paths = require('path');
|
||||||
|
|
||||||
// our main entry point
|
// our main entry point
|
||||||
exports.bbsMain = bbsMain;
|
exports.bbsMain = bbsMain;
|
||||||
|
@ -71,14 +73,23 @@ function bbsMain() {
|
||||||
if(err) {
|
if(err) {
|
||||||
console.error('Error initializing: ' + util.inspect(err));
|
console.error('Error initializing: ' + util.inspect(err));
|
||||||
}
|
}
|
||||||
callback(err);
|
return callback(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function listenConnections(callback) {
|
function listenConnections(callback) {
|
||||||
startListening(callback);
|
return startListening(callback);
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err) {
|
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) {
|
if(err) {
|
||||||
console.error('Error initializing: ' + util.inspect(err));
|
console.error('Error initializing: ' + util.inspect(err));
|
||||||
}
|
}
|
||||||
|
@ -87,7 +98,9 @@ function bbsMain() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function shutdownSystem() {
|
function shutdownSystem() {
|
||||||
logger.log.info('Process interrupted, shutting down...');
|
const msg = 'Process interrupted. Shutting down...';
|
||||||
|
console.info(msg);
|
||||||
|
logger.log.info(msg);
|
||||||
|
|
||||||
async.series(
|
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 : {
|
messageAreaDefaults : {
|
||||||
//
|
//
|
||||||
|
|
|
@ -175,17 +175,23 @@ const DB_INIT_TABLE = {
|
||||||
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;`
|
||||||
|
);
|
||||||
|
|
||||||
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;
|
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);
|
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);
|
INSERT INTO message_fts(docid, subject, message) VALUES(new.rowid, new.subject, new.message);
|
||||||
END;`
|
END;`
|
||||||
);
|
);
|
||||||
|
@ -250,7 +256,7 @@ const DB_INIT_TABLE = {
|
||||||
file_sha1 VARCHAR NOT NULL,
|
file_sha1 VARCHAR NOT NULL,
|
||||||
file_name, /* FTS @ file_fts */
|
file_name, /* FTS @ file_fts */
|
||||||
desc, /* FTS @ file_fts */
|
desc, /* FTS @ file_fts */
|
||||||
desc_long, /* FTS @ file_fts */
|
desc_long, /* FTS @ file_fts */
|
||||||
upload_by_username VARCHAR NOT NULL,
|
upload_by_username VARCHAR NOT NULL,
|
||||||
upload_timestamp DATETIME NOT NULL
|
upload_timestamp DATETIME NOT NULL
|
||||||
);`
|
);`
|
||||||
|
@ -273,18 +279,24 @@ const DB_INIT_TABLE = {
|
||||||
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;`
|
||||||
|
);
|
||||||
CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
|
||||||
|
dbs.file.run(
|
||||||
|
`CREATE TRIGGER IF NOT EXISTS file_before_delete BEFORE DELETE ON file BEGIN
|
||||||
DELETE FROM file_fts WHERE docid=old.rowid;
|
DELETE FROM file_fts WHERE docid=old.rowid;
|
||||||
END;
|
END;`
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
dbs.file.run(
|
||||||
INSERT INTO file_fts(docid, file_name, desc, long_desc) VALUES(new.rowid, new.file_name, new.desc, new.long_desc);
|
`CREATE TRIGGER IF NOT EXISTS file_after_update AFTER UPDATE ON file BEGIN
|
||||||
END;
|
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
|
dbs.file.run(
|
||||||
INSERT INTO file_fts(docid, file_name, desc, desc_long) VALUES(new.rowid, new.file_name, new.desc, new.long_desc);
|
`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;`
|
END;`
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ Door.prototype.run = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand arg strings, e.g. {dropFile} -> DOOR32.SYS
|
// 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
|
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) {
|
||||||
|
|
|
@ -19,6 +19,7 @@ class EnigError extends Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// :TODO: Just use EnigError for all
|
||||||
class EnigMenuError extends EnigError { }
|
class EnigMenuError extends EnigError { }
|
||||||
|
|
||||||
exports.EnigError = EnigError;
|
exports.EnigError = EnigError;
|
||||||
|
@ -27,4 +28,5 @@ exports.EnigMenuError = EnigMenuError;
|
||||||
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 EnigMenuError('Menu stack error', -33001, 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 stylizeString = require('./string_util.js').stylizeString;
|
||||||
const renderStringLength = require('./string_util.js').renderStringLength;
|
const renderStringLength = require('./string_util.js').renderStringLength;
|
||||||
const renderSubstr = require('./string_util.js').renderSubstr;
|
const renderSubstr = require('./string_util.js').renderSubstr;
|
||||||
|
const formatByteSize = require('./string_util.js').formatByteSize;
|
||||||
|
const formatByteSizeAbbr = require('./string_util.js').formatByteSizeAbbr;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
@ -265,6 +267,12 @@ const transformers = {
|
||||||
styleSmallI : (s) => stylizeString(s, 'small i'),
|
styleSmallI : (s) => stylizeString(s, 'small i'),
|
||||||
styleMixed : (s) => stylizeString(s, 'mixed'),
|
styleMixed : (s) => stylizeString(s, 'mixed'),
|
||||||
styleL33t : (s) => stylizeString(s, 'l33t'),
|
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) {
|
function transformValue(transformerName, value) {
|
||||||
|
|
|
@ -13,6 +13,8 @@ exports.debugEscapedString = debugEscapedString;
|
||||||
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
exports.stringFromNullTermBuffer = stringFromNullTermBuffer;
|
||||||
exports.renderSubstr = renderSubstr;
|
exports.renderSubstr = renderSubstr;
|
||||||
exports.renderStringLength = renderStringLength;
|
exports.renderStringLength = renderStringLength;
|
||||||
|
exports.formatByteSizeAbbr = formatByteSizeAbbr;
|
||||||
|
exports.formatByteSize = formatByteSize;
|
||||||
exports.cleanControlCodes = cleanControlCodes;
|
exports.cleanControlCodes = cleanControlCodes;
|
||||||
|
|
||||||
// :TODO: create Unicode verison of this
|
// :TODO: create Unicode verison of this
|
||||||
|
@ -286,6 +288,23 @@ function renderStringLength(s) {
|
||||||
return len;
|
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
|
// :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;
|
//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) {
|
if(this.messageAreaTag) {
|
||||||
this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
|
this.tempMessageConfAndAreaSwitch(this.messageAreaTag);
|
||||||
} else {
|
} 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