Merge branch 'master' of ssh://numinibsd/git/base/enigma-bbs
This commit is contained in:
commit
02f997ff60
|
@ -2,9 +2,10 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ENiGMA½
|
// ENiGMA½
|
||||||
const Config = require('./config.js').config;
|
const Config = require('./config.js').config;
|
||||||
const stringFormat = require('./string_format.js');
|
const stringFormat = require('./string_format.js');
|
||||||
const Errors = require('./enig_error.js').Errors;
|
const Errors = require('./enig_error.js').Errors;
|
||||||
|
const resolveMimeType = require('./mime_util.js').resolveMimeType;
|
||||||
|
|
||||||
// base/modules
|
// base/modules
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -19,9 +20,6 @@ class Archiver {
|
||||||
this.decompress = config.decompress;
|
this.decompress = config.decompress;
|
||||||
this.list = config.list;
|
this.list = config.list;
|
||||||
this.extract = config.extract;
|
this.extract = config.extract;
|
||||||
|
|
||||||
/*this.sig = new Buffer(config.sig, 'hex');
|
|
||||||
this.offset = config.offset || 0;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ok() {
|
ok() {
|
||||||
|
@ -76,39 +74,33 @@ module.exports = class ArchiveUtil {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.has(Config, 'archives.formats')) {
|
if(_.isObject(Config.fileTypes)) {
|
||||||
Object.keys(Config.archives.formats).forEach(fmtKey => {
|
Object.keys(Config.fileTypes).forEach(mimeType => {
|
||||||
|
const fileType = Config.fileTypes[mimeType];
|
||||||
|
if(fileType.sig) {
|
||||||
|
fileType.sig = new Buffer(fileType.sig, 'hex');
|
||||||
|
fileType.offset = fileType.offset || 0;
|
||||||
|
|
||||||
Config.archives.formats[fmtKey].sig = new Buffer(Config.archives.formats[fmtKey].sig, 'hex');
|
// :TODO: this is broken: sig is NOT this long, it's sig.length long; offset needs to allow for -negative values as well
|
||||||
Config.archives.formats[fmtKey].offset = Config.archives.formats[fmtKey].offset || 0;
|
const sigLen =fileType.offset + fileType.sig.length;
|
||||||
|
if(sigLen > this.longestSignature) {
|
||||||
const sigLen = Config.archives.formats[fmtKey].offset + Config.archives.formats[fmtKey].sig.length;
|
this.longestSignature = sigLen;
|
||||||
if(sigLen > this.longestSignature) {
|
}
|
||||||
this.longestSignature = sigLen;
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
getArchiver(mimeTypeOrExtension) {
|
||||||
getArchiver(archType) {
|
mimeTypeOrExtension = resolveMimeType(mimeTypeOrExtension);
|
||||||
if(!archType || 0 === archType.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
archType = archType.toLowerCase();
|
if(!mimeTypeOrExtension) { // lookup returns false on failure
|
||||||
return this.archivers[archType];
|
|
||||||
}*/
|
|
||||||
|
|
||||||
getArchiver(archType) {
|
|
||||||
if(!archType || 0 === archType.length) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_.has(Config, [ 'archives', 'formats', archType, 'handler' ] ) &&
|
const archiveHandler = _.get( Config, [ 'fileTypes', mimeTypeOrExtension, 'archiveHandler'] );
|
||||||
_.has(Config, [ 'archives', 'archivers', Config.archives.formats[archType].handler ] ))
|
if(archiveHandler) {
|
||||||
{
|
return _.get( Config, [ 'archives', 'archivers', archiveHandler ] );
|
||||||
return Config.archives.archivers[ Config.archives.formats[archType].handler ];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,10 +113,6 @@ module.exports = class ArchiveUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
detectType(path, cb) {
|
detectType(path, cb) {
|
||||||
if(!_.has(Config, 'archives.formats')) {
|
|
||||||
return cb(Errors.DoesNotExist('No formats configured'));
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.open(path, 'r', (err, fd) => {
|
fs.open(path, 'r', (err, fd) => {
|
||||||
if(err) {
|
if(err) {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
|
@ -136,15 +124,19 @@ module.exports = class ArchiveUtil {
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
const archFormat = _.findKey(Config.archives.formats, archFormat => {
|
const archFormat = _.findKey(Config.fileTypes, fileTypeInfo => {
|
||||||
const lenNeeded = archFormat.offset + archFormat.sig.length;
|
if(!fileTypeInfo.sig) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lenNeeded = fileTypeInfo.offset + fileTypeInfo.sig.length;
|
||||||
|
|
||||||
if(bytesRead < lenNeeded) {
|
if(bytesRead < lenNeeded) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const comp = buf.slice(archFormat.offset, archFormat.offset + archFormat.sig.length);
|
const comp = buf.slice(fileTypeInfo.offset, fileTypeInfo.offset + fileTypeInfo.sig.length);
|
||||||
return (archFormat.sig.equals(comp));
|
return (fileTypeInfo.sig.equals(comp));
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(archFormat ? null : Errors.General('Unknown type'), archFormat);
|
return cb(archFormat ? null : Errors.General('Unknown type'), archFormat);
|
||||||
|
@ -157,19 +149,13 @@ module.exports = class ArchiveUtil {
|
||||||
// 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.: No such file or directory')) {
|
if(_.isString(d) && d.startsWith('execvp(3) failed.')) {
|
||||||
err = new Error(`${action} failed: ${d.trim()}`);
|
err = Errors.ExternalProcess(`${action} failed: ${d.trim()}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
proc.once('exit', exitCode => {
|
proc.once('exit', exitCode => {
|
||||||
if(exitCode) {
|
return cb(exitCode ? Errors.ExternalProcess(`${action} failed with exit code: ${exitCode}`) : err);
|
||||||
return cb(new Error(`${action} failed with exit code: ${exitCode}`));
|
|
||||||
}
|
|
||||||
if(err) {
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
return cb(null);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +163,7 @@ module.exports = class ArchiveUtil {
|
||||||
const archiver = this.getArchiver(archType);
|
const archiver = this.getArchiver(archType);
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(new Error(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
|
@ -205,7 +191,7 @@ module.exports = class ArchiveUtil {
|
||||||
const archiver = this.getArchiver(archType);
|
const archiver = this.getArchiver(archType);
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(new Error(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
|
@ -235,7 +221,7 @@ module.exports = class ArchiveUtil {
|
||||||
const archiver = this.getArchiver(archType);
|
const archiver = this.getArchiver(archType);
|
||||||
|
|
||||||
if(!archiver) {
|
if(!archiver) {
|
||||||
return cb(new Error(`Unknown archive type: ${archType}`));
|
return cb(Errors.Invalid(`Unknown archive type: ${archType}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const fmtObj = {
|
const fmtObj = {
|
||||||
|
@ -254,7 +240,7 @@ module.exports = class ArchiveUtil {
|
||||||
|
|
||||||
proc.once('exit', exitCode => {
|
proc.once('exit', exitCode => {
|
||||||
if(exitCode) {
|
if(exitCode) {
|
||||||
return cb(new Error(`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 };
|
||||||
|
|
|
@ -21,7 +21,7 @@ const fs = require('fs');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
|
|
||||||
// our main entry point
|
// our main entry point
|
||||||
exports.bbsMain = bbsMain;
|
exports.main = main;
|
||||||
|
|
||||||
// object with various services we want to de-init/shutdown cleanly if possible
|
// object with various services we want to de-init/shutdown cleanly if possible
|
||||||
const initServices = {};
|
const initServices = {};
|
||||||
|
@ -42,7 +42,7 @@ function printHelpAndExit() {
|
||||||
process.exit();
|
process.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
function bbsMain() {
|
function main() {
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
function processArgs(callback) {
|
function processArgs(callback) {
|
||||||
|
|
185
core/config.js
185
core/config.js
|
@ -223,6 +223,14 @@ function getDefaultConfig() {
|
||||||
privateKeyPem : paths.join(__dirname, './../misc/ssh_private_key.pem'),
|
privateKeyPem : paths.join(__dirname, './../misc/ssh_private_key.pem'),
|
||||||
firstMenu : 'sshConnected',
|
firstMenu : 'sshConnected',
|
||||||
firstMenuNewUser : 'sshConnectedNewUser',
|
firstMenuNewUser : 'sshConnectedNewUser',
|
||||||
|
},
|
||||||
|
webSocket : {
|
||||||
|
port : 8810,
|
||||||
|
enabled : true, // :TODO: default to false
|
||||||
|
},
|
||||||
|
secureWebSocket : {
|
||||||
|
port : 8811,
|
||||||
|
enabled : false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -263,6 +271,124 @@ function getDefaultConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
infoExtractUtils : {
|
||||||
|
Exiftool2Desc : {
|
||||||
|
cmd : `${__dirname}/../util/exiftool2desc.js`, // ensure chmod +x
|
||||||
|
},
|
||||||
|
Exiftool : {
|
||||||
|
cmd : 'exiftool',
|
||||||
|
args : [
|
||||||
|
'-charset', 'utf8', '{filePath}',
|
||||||
|
'--directory', '--filepermissions', '--exiftoolversion', '--filename', '--filesize', '--filemodifydate', '--fileaccessdate', '--fileinodechangedate'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fileTypes : {
|
||||||
|
//
|
||||||
|
// File types explicitly known to the system. Here we can configure
|
||||||
|
// information extraction, archive treatment, etc.
|
||||||
|
//
|
||||||
|
// MIME types can be found in mime-db: https://github.com/jshttp/mime-db
|
||||||
|
//
|
||||||
|
// Resources for signature/magic bytes:
|
||||||
|
// * http://www.garykessler.net/library/file_sigs.html
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// :TODO: text/x-ansi -> SAUCE extraction for .ans uploads
|
||||||
|
// :TODO: textual : bool -- if text, we can view.
|
||||||
|
// :TODO: asText : { cmd, args[] } -> viewable text
|
||||||
|
|
||||||
|
//
|
||||||
|
// Audio
|
||||||
|
//
|
||||||
|
'audio/mpeg' : {
|
||||||
|
desc : 'MP3 Audio',
|
||||||
|
shortDescUtil : 'Exiftool2Desc',
|
||||||
|
longDescUtil : 'Exiftool',
|
||||||
|
},
|
||||||
|
'application/pdf' : {
|
||||||
|
desc : 'Adobe PDF',
|
||||||
|
shortDescUtil : 'Exiftool2Desc',
|
||||||
|
longDescUtil : 'Exiftool',
|
||||||
|
},
|
||||||
|
//
|
||||||
|
// Images
|
||||||
|
//
|
||||||
|
'image/jpeg' : {
|
||||||
|
desc : 'JPEG Image',
|
||||||
|
shortDescUtil : 'Exiftool2Desc',
|
||||||
|
longDescUtil : 'Exiftool',
|
||||||
|
},
|
||||||
|
'image/png' : {
|
||||||
|
desc : 'Portable Network Graphic Image',
|
||||||
|
shortDescUtil : 'Exiftool2Desc',
|
||||||
|
longDescUtil : 'Exiftool',
|
||||||
|
},
|
||||||
|
'image/gif' : {
|
||||||
|
desc : 'Graphics Interchange Format Image',
|
||||||
|
shortDescUtil : 'Exiftool2Desc',
|
||||||
|
longDescUtil : 'Exiftool',
|
||||||
|
},
|
||||||
|
'image/webp' : {
|
||||||
|
desc : 'WebP Image',
|
||||||
|
shortDescUtil : 'Exiftool2Desc',
|
||||||
|
longDescUtil : 'Exiftool',
|
||||||
|
},
|
||||||
|
//
|
||||||
|
// Archives
|
||||||
|
//
|
||||||
|
'application/zip' : {
|
||||||
|
desc : 'ZIP Archive',
|
||||||
|
sig : '504b0304',
|
||||||
|
offset : 0,
|
||||||
|
archiveHandler : '7Zip',
|
||||||
|
},
|
||||||
|
'application/x-arj' : {
|
||||||
|
desc : 'ARJ Archive',
|
||||||
|
sig : '60ea',
|
||||||
|
offset : 0,
|
||||||
|
archiveHandler : 'Arj',
|
||||||
|
},
|
||||||
|
'application/x-rar-compressed' : {
|
||||||
|
desc : 'RAR Archive',
|
||||||
|
sig : '526172211a0700',
|
||||||
|
offset : 0,
|
||||||
|
archiveHandler : 'Rar',
|
||||||
|
},
|
||||||
|
'application/gzip' : {
|
||||||
|
desc : 'Gzip Archive',
|
||||||
|
sig : '1f8b',
|
||||||
|
offset : 0,
|
||||||
|
archiveHandler : '7Zip',
|
||||||
|
},
|
||||||
|
// :TODO: application/x-bzip
|
||||||
|
'application/x-bzip2' : {
|
||||||
|
desc : 'BZip2 Archive',
|
||||||
|
sig : '425a68',
|
||||||
|
offset : 0,
|
||||||
|
archiveHandler : '7Zip',
|
||||||
|
},
|
||||||
|
'application/x-lzh-compressed' : {
|
||||||
|
desc : 'LHArc Archive',
|
||||||
|
sig : '2d6c68',
|
||||||
|
offset : 2,
|
||||||
|
archiveHandler : 'Lha',
|
||||||
|
},
|
||||||
|
'application/x-7z-compressed' : {
|
||||||
|
desc : '7-Zip Archive',
|
||||||
|
sig : '377abcaf271c',
|
||||||
|
offset : 0,
|
||||||
|
archiveHandler : '7Zip',
|
||||||
|
}
|
||||||
|
|
||||||
|
// :TODO: update archives::formats to fall here
|
||||||
|
// * archive handler -> archiveHandler (consider archive if archiveHandler present)
|
||||||
|
// * sig, offset, ...
|
||||||
|
// * mime-db -> exts lookup
|
||||||
|
// *
|
||||||
|
},
|
||||||
|
|
||||||
archives : {
|
archives : {
|
||||||
archivers : {
|
archivers : {
|
||||||
|
@ -348,62 +474,6 @@ function getDefaultConfig() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
formats : {
|
|
||||||
//
|
|
||||||
// Resources
|
|
||||||
// * http://www.garykessler.net/library/file_sigs.html
|
|
||||||
//
|
|
||||||
zip : {
|
|
||||||
sig : '504b0304',
|
|
||||||
offset : 0,
|
|
||||||
exts : [ 'zip' ],
|
|
||||||
handler : '7Zip',
|
|
||||||
desc : 'ZIP Archive',
|
|
||||||
},
|
|
||||||
'7z' : {
|
|
||||||
sig : '377abcaf271c',
|
|
||||||
offset : 0,
|
|
||||||
exts : [ '7z' ],
|
|
||||||
handler : '7Zip',
|
|
||||||
desc : '7-Zip Archive',
|
|
||||||
},
|
|
||||||
arj : {
|
|
||||||
sig : '60ea',
|
|
||||||
offset : 0,
|
|
||||||
exts : [ 'arj' ],
|
|
||||||
handler : 'Arj',
|
|
||||||
desc : 'ARJ Archive',
|
|
||||||
},
|
|
||||||
rar : {
|
|
||||||
sig : '526172211a0700',
|
|
||||||
offset : 0,
|
|
||||||
exts : [ 'rar' ],
|
|
||||||
handler : 'Rar',
|
|
||||||
desc : 'RAR Archive',
|
|
||||||
},
|
|
||||||
gzip : {
|
|
||||||
sig : '1f8b',
|
|
||||||
offset : 0,
|
|
||||||
exts : [ 'gz' ],
|
|
||||||
handler : '7Zip',
|
|
||||||
desc : 'Gzip Archive',
|
|
||||||
},
|
|
||||||
bzip : {
|
|
||||||
sig : '425a68',
|
|
||||||
offset : 0,
|
|
||||||
exts : [ 'bz2' ],
|
|
||||||
handler : '7Zip',
|
|
||||||
desc : 'BZip2 Archive',
|
|
||||||
},
|
|
||||||
lzh : {
|
|
||||||
sig : '2d6c68',
|
|
||||||
offset : 2,
|
|
||||||
exts : [ 'lzh', 'ice' ],
|
|
||||||
handler : 'Lha',
|
|
||||||
desc : 'LHArc Archive',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fileTransferProtocols : {
|
fileTransferProtocols : {
|
||||||
|
@ -550,8 +620,9 @@ function getDefaultConfig() {
|
||||||
"\\b('[1789][0-9])\\b", // eslint-disable-line quotes
|
"\\b('[1789][0-9])\\b", // eslint-disable-line quotes
|
||||||
'\\b[0-3]?[0-9][\\-\\/\\.](?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december)[\\-\\/\\.]((?:[0-9]{2})?[0-9]{2})\\b',
|
'\\b[0-3]?[0-9][\\-\\/\\.](?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december)[\\-\\/\\.]((?:[0-9]{2})?[0-9]{2})\\b',
|
||||||
'\\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december),?\\s[0-9]+(?:st|nd|rd|th)?,?\\s((?:[0-9]{2})?[0-9]{2})\\b', // November 29th, 1997
|
'\\b(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec|january|february|march|april|may|june|july|august|september|october|november|december),?\\s[0-9]+(?:st|nd|rd|th)?,?\\s((?:[0-9]{2})?[0-9]{2})\\b', // November 29th, 1997
|
||||||
|
'\\(((?:19|20)[0-9]{2})\\)', // (19xx) or (20xx) -- do this before 19xx 20xx such that this has priority
|
||||||
|
'\\b((?:19|20)[0-9]{2})\\b', // simple 19xx or 20xx with word boundaries
|
||||||
// :TODO: DD/MMM/YY, DD/MMMM/YY, DD/MMM/YYYY, etc.
|
// :TODO: DD/MMM/YY, DD/MMMM/YY, DD/MMM/YYYY, etc.
|
||||||
// :TODO: "Copyright YYYY someone"
|
|
||||||
],
|
],
|
||||||
|
|
||||||
web : {
|
web : {
|
||||||
|
|
|
@ -10,6 +10,9 @@ const FileDb = require('./database.js').dbs.file;
|
||||||
const ArchiveUtil = require('./archive_util.js');
|
const ArchiveUtil = require('./archive_util.js');
|
||||||
const CRC32 = require('./crc.js').CRC32;
|
const CRC32 = require('./crc.js').CRC32;
|
||||||
const Log = require('./logger.js').log;
|
const Log = require('./logger.js').log;
|
||||||
|
const resolveMimeType = require('./mime_util.js').resolveMimeType;
|
||||||
|
const stringFormat = require('./string_format.js');
|
||||||
|
const wordWrapText = require('./word_wrap.js').wordWrapText;
|
||||||
|
|
||||||
// deps
|
// deps
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
@ -19,6 +22,8 @@ const crypto = require('crypto');
|
||||||
const paths = require('path');
|
const paths = require('path');
|
||||||
const temptmp = require('temptmp').createTrackedSession('file_area');
|
const temptmp = require('temptmp').createTrackedSession('file_area');
|
||||||
const iconv = require('iconv-lite');
|
const iconv = require('iconv-lite');
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
exports.isInternalArea = isInternalArea;
|
exports.isInternalArea = isInternalArea;
|
||||||
exports.getAvailableFileAreas = getAvailableFileAreas;
|
exports.getAvailableFileAreas = getAvailableFileAreas;
|
||||||
|
@ -210,7 +215,7 @@ function attemptSetEstimatedReleaseDate(fileEntry) {
|
||||||
const patterns = Config.fileBase.yearEstPatterns.map( p => new RegExp(p, 'gmi'));
|
const patterns = Config.fileBase.yearEstPatterns.map( p => new RegExp(p, 'gmi'));
|
||||||
|
|
||||||
function getMatch(input) {
|
function getMatch(input) {
|
||||||
if(input) {
|
if(input) {
|
||||||
let m;
|
let m;
|
||||||
for(let i = 0; i < patterns.length; ++i) {
|
for(let i = 0; i < patterns.length; ++i) {
|
||||||
m = patterns[i].exec(input);
|
m = patterns[i].exec(input);
|
||||||
|
@ -222,8 +227,12 @@ function attemptSetEstimatedReleaseDate(fileEntry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// We attempt deteciton in short -> long order
|
// We attempt detection in short -> long order
|
||||||
//
|
//
|
||||||
|
// Throw out anything that is current_year + 2 (we give some leway)
|
||||||
|
// with the assumption that must be wrong.
|
||||||
|
//
|
||||||
|
const maxYear = moment().add(2, 'year').year();
|
||||||
const match = getMatch(fileEntry.desc) || getMatch(fileEntry.descLong);
|
const match = getMatch(fileEntry.desc) || getMatch(fileEntry.descLong);
|
||||||
if(match && match[1]) {
|
if(match && match[1]) {
|
||||||
let year;
|
let year;
|
||||||
|
@ -240,7 +249,7 @@ function attemptSetEstimatedReleaseDate(fileEntry) {
|
||||||
year = parseInt(match[1]);
|
year = parseInt(match[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(year) {
|
if(year && year <= maxYear) {
|
||||||
fileEntry.meta.est_release_year = year;
|
fileEntry.meta.est_release_year = year;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -390,9 +399,82 @@ function populateFileEntryWithArchive(fileEntry, filePath, stepInfo, iterator, c
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getInfoExtractUtilForDesc(mimeType, descType) {
|
||||||
|
let util = _.get(Config, [ 'fileTypes', mimeType, `${descType}DescUtil` ]);
|
||||||
|
if(!_.isString(util)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
util = _.get(Config, [ 'infoExtractUtils', util ]);
|
||||||
|
if(!util || !_.isString(util.cmd)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return util;
|
||||||
|
}
|
||||||
|
|
||||||
function populateFileEntryNonArchive(fileEntry, filePath, stepInfo, iterator, cb) {
|
function populateFileEntryNonArchive(fileEntry, filePath, stepInfo, iterator, cb) {
|
||||||
// :TODO: implement me!
|
|
||||||
return cb(null);
|
async.series(
|
||||||
|
[
|
||||||
|
function processDescFilesStart(callback) {
|
||||||
|
stepInfo.step = 'desc_files_start';
|
||||||
|
return iterator(callback);
|
||||||
|
},
|
||||||
|
function getDescriptions(callback) {
|
||||||
|
const mimeType = resolveMimeType(filePath);
|
||||||
|
if(!mimeType) {
|
||||||
|
return callback(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.eachSeries( [ 'short', 'long' ], (descType, nextDesc) => {
|
||||||
|
const util = getInfoExtractUtilForDesc(mimeType, descType);
|
||||||
|
if(!util) {
|
||||||
|
return nextDesc(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = (util.args || [ '{filePath} '] ).map( arg => stringFormat(arg, { filePath : filePath } ) );
|
||||||
|
|
||||||
|
exec(`${util.cmd} ${args.join(' ')}`, (err, stdout) => {
|
||||||
|
if(err) {
|
||||||
|
logDebug(
|
||||||
|
{ error : err.message, cmd : util.cmd, args : args },
|
||||||
|
`${_.upperFirst(descType)} description command failed`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
stdout = (stdout || '').trim();
|
||||||
|
if(stdout.length > 0) {
|
||||||
|
const key = 'short' === descType ? 'desc' : 'descLong';
|
||||||
|
if('desc' === key) {
|
||||||
|
//
|
||||||
|
// Word wrap short descriptions to FILE_ID.DIZ spec
|
||||||
|
//
|
||||||
|
// "...no more than 45 characters long"
|
||||||
|
//
|
||||||
|
// See http://www.textfiles.com/computers/fileid.txt
|
||||||
|
//
|
||||||
|
stdout = (wordWrapText( stdout, { width : 45 } ).wrapped || []).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
fileEntry[key] = stdout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextDesc(null);
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
return callback(null);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function processDescFilesFinish(callback) {
|
||||||
|
stepInfo.step = 'desc_files_finish';
|
||||||
|
return iterator(callback);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
err => {
|
||||||
|
return cb(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewFileEntry(fileEntry, filePath, cb) {
|
function addNewFileEntry(fileEntry, filePath, cb) {
|
||||||
|
|
|
@ -396,13 +396,13 @@ module.exports = class FileEntry {
|
||||||
FROM file_user_rating
|
FROM file_user_rating
|
||||||
WHERE file_id = f.file_id)
|
WHERE file_id = f.file_id)
|
||||||
AS avg_rating
|
AS avg_rating
|
||||||
FROM file f, file_meta m`;
|
FROM file f`;
|
||||||
|
|
||||||
sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`;
|
sqlOrderBy = `ORDER BY avg_rating ${sqlOrderDir}`;
|
||||||
} else {
|
} else {
|
||||||
sql =
|
sql =
|
||||||
`SELECT DISTINCT f.file_id, f.${filter.sort}
|
`SELECT DISTINCT f.file_id, f.${filter.sort}
|
||||||
FROM file f, file_meta m`;
|
FROM file f`;
|
||||||
|
|
||||||
sqlOrderBy = getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
|
sqlOrderBy = getOrderByWithCast(`f.${filter.sort}`) + ' ' + sqlOrderDir;
|
||||||
}
|
}
|
||||||
|
@ -410,7 +410,7 @@ module.exports = class FileEntry {
|
||||||
} else {
|
} else {
|
||||||
sql =
|
sql =
|
||||||
`SELECT DISTINCT f.file_id
|
`SELECT DISTINCT f.file_id
|
||||||
FROM file f, file_meta m`;
|
FROM file f`;
|
||||||
|
|
||||||
sqlOrderBy = `${getOrderByWithCast('f.file_id')} ${sqlOrderDir}`;
|
sqlOrderBy = `${getOrderByWithCast('f.file_id')} ${sqlOrderDir}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const mimeTypes = require('mime-types');
|
||||||
|
|
||||||
|
exports.resolveMimeType = resolveMimeType;
|
||||||
|
|
||||||
|
function resolveMimeType(query) {
|
||||||
|
if(mimeTypes.extensions[query]) {
|
||||||
|
return query; // alreaed a mime-type
|
||||||
|
}
|
||||||
|
|
||||||
|
return mimeTypes.lookup(query) || undefined; // lookup() returns false; we want undefined
|
||||||
|
}
|
|
@ -48,174 +48,182 @@ function userStatAsString(client, statName, defaultValue) {
|
||||||
return (StatLog.getUserStat(client.user, statName) || defaultValue).toString();
|
return (StatLog.getUserStat(client.user, statName) || defaultValue).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PREDEFINED_MCI_GENERATORS = {
|
||||||
|
//
|
||||||
|
// Board
|
||||||
|
//
|
||||||
|
BN : function boardName() { return Config.general.boardName; },
|
||||||
|
|
||||||
|
// ENiGMA
|
||||||
|
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
|
||||||
|
VN : function version() { return packageJson.version; },
|
||||||
|
|
||||||
|
// +op info
|
||||||
|
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
|
||||||
|
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
|
||||||
|
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
|
||||||
|
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
|
||||||
|
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
|
||||||
|
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
|
||||||
|
// :TODO: op age, web, ?????
|
||||||
|
|
||||||
|
//
|
||||||
|
// Current user / session
|
||||||
|
//
|
||||||
|
UN : function userName(client) { return client.user.username; },
|
||||||
|
UI : function userId(client) { return client.user.userId.toString(); },
|
||||||
|
UG : function groups(client) { return _.values(client.user.groups).join(', '); },
|
||||||
|
UR : function realName(client) { return userStatAsString(client, 'real_name', ''); },
|
||||||
|
LO : function location(client) { return userStatAsString(client, 'location', ''); },
|
||||||
|
UA : function age(client) { return client.user.getAge().toString(); },
|
||||||
|
BD : function birthdate(client) { return moment(client.user.properties.birthdate).format(client.currentTheme.helpers.getDateFormat()); }, // iNiQUiTY
|
||||||
|
US : function sex(client) { return userStatAsString(client, 'sex', ''); },
|
||||||
|
UE : function emailAddres(client) { return userStatAsString(client, 'email_address', ''); },
|
||||||
|
UW : function webAddress(client) { return userStatAsString(client, 'web_address', ''); },
|
||||||
|
UF : function affils(client) { return userStatAsString(client, 'affiliation', ''); },
|
||||||
|
UT : function themeId(client) { return userStatAsString(client, 'theme_id', ''); },
|
||||||
|
UC : function loginCount(client) { return userStatAsString(client, 'login_count', 0); },
|
||||||
|
ND : function connectedNode(client) { return client.node.toString(); },
|
||||||
|
IP : function clientIpAddress(client) { return client.remoteAddress; },
|
||||||
|
ST : function serverName(client) { return client.session.serverName; },
|
||||||
|
FN : function activeFileBaseFilterName(client) {
|
||||||
|
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
||||||
|
return activeFilter ? activeFilter.name : '';
|
||||||
|
},
|
||||||
|
DN : function userNumDownloads(client) { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
||||||
|
DK : function userByteDownload(client) { // Obv/2 uses DK=downloaded Kbytes
|
||||||
|
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
||||||
|
return formatByteSize(byteSize, true); // true=withAbbr
|
||||||
|
},
|
||||||
|
UP : function userNumUploadsclient(client) { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
||||||
|
UK : function userByteUpload(client) { // Obv/2 uses UK=uploaded Kbytes
|
||||||
|
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
||||||
|
return formatByteSize(byteSize, true); // true=withAbbr
|
||||||
|
},
|
||||||
|
NR : function userUpDownRatio(client) { // Obv/2
|
||||||
|
return getRatio(client, 'ul_total_count', 'dl_total_count');
|
||||||
|
},
|
||||||
|
KR : function userUpDownByteRatio(client) { // Obv/2 uses KR=upload/download Kbyte ratio
|
||||||
|
return getRatio(client, 'ul_total_bytes', 'dl_total_bytes');
|
||||||
|
},
|
||||||
|
|
||||||
|
MS : function accountCreatedclient(client) { return moment(client.user.properties.account_created).format(client.currentTheme.helpers.getDateFormat()); },
|
||||||
|
PS : function userPostCount(client) { return userStatAsString(client, 'post_count', 0); },
|
||||||
|
PC : function userPostCallRatio(client) { return getRatio(client, 'post_count', 'login_count'); },
|
||||||
|
|
||||||
|
MD : function currentMenuDescription(client) {
|
||||||
|
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
MA : function messageAreaName(client) {
|
||||||
|
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||||
|
return area ? area.name : '';
|
||||||
|
},
|
||||||
|
MC : function messageConfName(client) {
|
||||||
|
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||||
|
return conf ? conf.name : '';
|
||||||
|
},
|
||||||
|
ML : function messageAreaDescription(client) {
|
||||||
|
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
||||||
|
return area ? area.desc : '';
|
||||||
|
},
|
||||||
|
CM : function messageConfDescription(client) {
|
||||||
|
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
||||||
|
return conf ? conf.desc : '';
|
||||||
|
},
|
||||||
|
|
||||||
|
SH : function termHeight(client) { return client.term.termHeight.toString(); },
|
||||||
|
SW : function termWidth(client) { return client.term.termWidth.toString(); },
|
||||||
|
|
||||||
|
//
|
||||||
|
// Date/Time
|
||||||
|
//
|
||||||
|
// :TODO: change to CD for 'Current Date'
|
||||||
|
DT : function date(client) { return moment().format(client.currentTheme.helpers.getDateFormat()); },
|
||||||
|
CT : function time(client) { return moment().format(client.currentTheme.helpers.getTimeFormat()) ;},
|
||||||
|
|
||||||
|
//
|
||||||
|
// OS/System Info
|
||||||
|
//
|
||||||
|
OS : function operatingSystem() {
|
||||||
|
return {
|
||||||
|
linux : 'Linux',
|
||||||
|
darwin : 'Mac OS X',
|
||||||
|
win32 : 'Windows',
|
||||||
|
sunos : 'SunOS',
|
||||||
|
freebsd : 'FreeBSD',
|
||||||
|
}[os.platform()] || os.type();
|
||||||
|
},
|
||||||
|
|
||||||
|
OA : function systemArchitecture() { return os.arch(); },
|
||||||
|
|
||||||
|
SC : function systemCpuModel() {
|
||||||
|
//
|
||||||
|
// Clean up CPU strings a bit for better display
|
||||||
|
//
|
||||||
|
return os.cpus()[0].model
|
||||||
|
.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
||||||
|
.replace(/\s+(?= )/g, '');
|
||||||
|
},
|
||||||
|
|
||||||
|
// :TODO: MCI for core count, e.g. os.cpus().length
|
||||||
|
|
||||||
|
// :TODO: cpu load average (over N seconds): http://stackoverflow.com/questions/9565912/convert-the-output-of-os-cpus-in-node-js-to-percentage
|
||||||
|
NV : function nodeVersion() { return process.version; },
|
||||||
|
|
||||||
|
AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); },
|
||||||
|
|
||||||
|
TC : function totalCalls() { return StatLog.getSystemStat('login_count').toString(); },
|
||||||
|
|
||||||
|
RR : function randomRumor() {
|
||||||
|
// start the process of picking another random one
|
||||||
|
setNextRandomRumor();
|
||||||
|
|
||||||
|
return StatLog.getSystemStat('random_rumor');
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
// System File Base, Up/Download Info
|
||||||
|
//
|
||||||
|
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
||||||
|
//
|
||||||
|
// :TODO: System stat log for total ul/dl, total ul/dl bytes
|
||||||
|
|
||||||
|
// :TODO: PT - Messages posted *today* (Obv/2)
|
||||||
|
// -> Include FTN/etc.
|
||||||
|
// :TODO: NT - New users today (Obv/2)
|
||||||
|
// :TODO: CT - Calls *today* (Obv/2)
|
||||||
|
// :TODO: TF - Total files on the system (Obv/2)
|
||||||
|
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
||||||
|
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
||||||
|
// :TODO: TP - total message/posts on the system (Obv/2)
|
||||||
|
// -> Include FTN/etc.
|
||||||
|
// :TODO: LC - name of last caller to system (Obv/2)
|
||||||
|
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Special handling for XY
|
||||||
|
//
|
||||||
|
XY : function xyHack() { return; /* nothing */ },
|
||||||
|
};
|
||||||
|
|
||||||
function getPredefinedMCIValue(client, code) {
|
function getPredefinedMCIValue(client, code) {
|
||||||
|
|
||||||
if(!client || !code) {
|
if(!client || !code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const generator = PREDEFINED_MCI_GENERATORS[code];
|
||||||
return {
|
|
||||||
//
|
|
||||||
// Board
|
|
||||||
//
|
|
||||||
BN : function boardName() { return Config.general.boardName; },
|
|
||||||
|
|
||||||
// ENiGMA
|
if(generator) {
|
||||||
VL : function versionLabel() { return 'ENiGMA½ v' + packageJson.version; },
|
let value;
|
||||||
VN : function version() { return packageJson.version; },
|
try {
|
||||||
|
value = generator(client);
|
||||||
|
} catch(e) {
|
||||||
|
Log.error( { code : code, exception : e.message }, 'Exception caught generating predefined MCI value' );
|
||||||
|
}
|
||||||
|
|
||||||
// +op info
|
return value;
|
||||||
SN : function opUserName() { return StatLog.getSystemStat('sysop_username'); },
|
|
||||||
SR : function opRealName() { return StatLog.getSystemStat('sysop_real_name'); },
|
|
||||||
SL : function opLocation() { return StatLog.getSystemStat('sysop_location'); },
|
|
||||||
SA : function opAffils() { return StatLog.getSystemStat('sysop_affiliation'); },
|
|
||||||
SS : function opSex() { return StatLog.getSystemStat('sysop_sex'); },
|
|
||||||
SE : function opEmail() { return StatLog.getSystemStat('sysop_email_address'); },
|
|
||||||
// :TODO: op age, web, ?????
|
|
||||||
|
|
||||||
//
|
|
||||||
// Current user / session
|
|
||||||
//
|
|
||||||
UN : function userName() { return client.user.username; },
|
|
||||||
UI : function userId() { return client.user.userId.toString(); },
|
|
||||||
UG : function groups() { return _.values(client.user.groups).join(', '); },
|
|
||||||
UR : function realName() { return userStatAsString(client, 'real_name', ''); },
|
|
||||||
LO : function location() { return userStatAsString(client, 'location', ''); },
|
|
||||||
UA : function age() { return client.user.getAge().toString(); },
|
|
||||||
BD : function birthdate() { return moment(client.user.properties.birthdate).format(client.currentTheme.helpers.getDateFormat()); }, // iNiQUiTY
|
|
||||||
US : function sex() { return userStatAsString(client, 'sex', ''); },
|
|
||||||
UE : function emailAddres() { return userStatAsString(client, 'email_address', ''); },
|
|
||||||
UW : function webAddress() { return userStatAsString(client, 'web_address', ''); },
|
|
||||||
UF : function affils() { return userStatAsString(client, 'affiliation', ''); },
|
|
||||||
UT : function themeId() { return userStatAsString(client, 'theme_id', ''); },
|
|
||||||
UC : function loginCount() { return userStatAsString(client, 'login_count', 0); },
|
|
||||||
ND : function connectedNode() { return client.node.toString(); },
|
|
||||||
IP : function clientIpAddress() { return client.remoteAddress; },
|
|
||||||
ST : function serverName() { return client.session.serverName; },
|
|
||||||
FN : function activeFileBaseFilterName() {
|
|
||||||
const activeFilter = FileBaseFilters.getActiveFilter(client);
|
|
||||||
return activeFilter ? activeFilter.name : '';
|
|
||||||
},
|
|
||||||
DN : function userNumDownloads() { return userStatAsString(client, 'dl_total_count', 0); }, // Obv/2
|
|
||||||
DK : function userByteDownload() { // Obv/2 uses DK=downloaded Kbytes
|
|
||||||
const byteSize = StatLog.getUserStatNum(client.user, 'dl_total_bytes');
|
|
||||||
return formatByteSize(byteSize, true); // true=withAbbr
|
|
||||||
},
|
|
||||||
UP : function userNumUploads() { return userStatAsString(client, 'ul_total_count', 0); }, // Obv/2
|
|
||||||
UK : function userByteUpload() { // Obv/2 uses UK=uploaded Kbytes
|
|
||||||
const byteSize = StatLog.getUserStatNum(client.user, 'ul_total_bytes');
|
|
||||||
return formatByteSize(byteSize, true); // true=withAbbr
|
|
||||||
},
|
|
||||||
NR : function userUpDownRatio() { // Obv/2
|
|
||||||
return getRatio(client, 'ul_total_count', 'dl_total_count');
|
|
||||||
},
|
|
||||||
KR : function userUpDownByteRatio() { // Obv/2 uses KR=upload/download Kbyte ratio
|
|
||||||
return getRatio(client, 'ul_total_bytes', 'dl_total_bytes');
|
|
||||||
},
|
|
||||||
|
|
||||||
MS : function accountCreated() { return moment(client.user.properties.account_created).format(client.currentTheme.helpers.getDateFormat()); },
|
|
||||||
PS : function userPostCount() { return userStatAsString(client, 'post_count', 0); },
|
|
||||||
PC : function userPostCallRatio() { return getRatio(client, 'post_count', 'login_count'); },
|
|
||||||
|
|
||||||
MD : function currentMenuDescription() {
|
|
||||||
return _.has(client, 'currentMenuModule.menuConfig.desc') ? client.currentMenuModule.menuConfig.desc : '';
|
|
||||||
},
|
|
||||||
|
|
||||||
MA : function messageAreaName() {
|
|
||||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
|
||||||
return area ? area.name : '';
|
|
||||||
},
|
|
||||||
MC : function messageConfName() {
|
|
||||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
|
||||||
return conf ? conf.name : '';
|
|
||||||
},
|
|
||||||
ML : function messageAreaDescription() {
|
|
||||||
const area = getMessageAreaByTag(client.user.properties.message_area_tag);
|
|
||||||
return area ? area.desc : '';
|
|
||||||
},
|
|
||||||
CM : function messageConfDescription() {
|
|
||||||
const conf = getMessageConferenceByTag(client.user.properties.message_conf_tag);
|
|
||||||
return conf ? conf.desc : '';
|
|
||||||
},
|
|
||||||
|
|
||||||
SH : function termHeight() { return client.term.termHeight.toString(); },
|
|
||||||
SW : function termWidth() { return client.term.termWidth.toString(); },
|
|
||||||
|
|
||||||
//
|
|
||||||
// Date/Time
|
|
||||||
//
|
|
||||||
// :TODO: change to CD for 'Current Date'
|
|
||||||
DT : function date() { return moment().format(client.currentTheme.helpers.getDateFormat()); },
|
|
||||||
CT : function time() { return moment().format(client.currentTheme.helpers.getTimeFormat()) ;},
|
|
||||||
|
|
||||||
//
|
|
||||||
// OS/System Info
|
|
||||||
//
|
|
||||||
OS : function operatingSystem() {
|
|
||||||
return {
|
|
||||||
linux : 'Linux',
|
|
||||||
darwin : 'Mac OS X',
|
|
||||||
win32 : 'Windows',
|
|
||||||
sunos : 'SunOS',
|
|
||||||
freebsd : 'FreeBSD',
|
|
||||||
}[os.platform()] || os.type();
|
|
||||||
},
|
|
||||||
|
|
||||||
OA : function systemArchitecture() { return os.arch(); },
|
|
||||||
SC : function systemCpuModel() {
|
|
||||||
//
|
|
||||||
// Clean up CPU strings a bit for better display
|
|
||||||
//
|
|
||||||
return os.cpus()[0].model.replace(/\(R\)|\(TM\)|processor|CPU/g, '')
|
|
||||||
.replace(/\s+(?= )/g, '');
|
|
||||||
},
|
|
||||||
|
|
||||||
// :TODO: MCI for core count, e.g. os.cpus().length
|
|
||||||
|
|
||||||
// :TODO: cpu load average (over N seconds): http://stackoverflow.com/questions/9565912/convert-the-output-of-os-cpus-in-node-js-to-percentage
|
|
||||||
NV : function nodeVersion() { return process.version; },
|
|
||||||
|
|
||||||
AN : function activeNodes() { return clientConnections.getActiveConnections().length.toString(); },
|
|
||||||
|
|
||||||
TC : function totalCalls() { return StatLog.getSystemStat('login_count').toString(); },
|
|
||||||
|
|
||||||
RR : function randomRumor() {
|
|
||||||
// start the process of picking another random one
|
|
||||||
setNextRandomRumor();
|
|
||||||
|
|
||||||
return StatLog.getSystemStat('random_rumor');
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
// System File Base, Up/Download Info
|
|
||||||
//
|
|
||||||
// :TODO: DD - Today's # of downloads (iNiQUiTY)
|
|
||||||
//
|
|
||||||
// :TODO: System stat log for total ul/dl, total ul/dl bytes
|
|
||||||
|
|
||||||
// :TODO: PT - Messages posted *today* (Obv/2)
|
|
||||||
// -> Include FTN/etc.
|
|
||||||
// :TODO: NT - New users today (Obv/2)
|
|
||||||
// :TODO: CT - Calls *today* (Obv/2)
|
|
||||||
// :TODO: TF - Total files on the system (Obv/2)
|
|
||||||
// :TODO: FT - Files uploaded/added *today* (Obv/2)
|
|
||||||
// :TODO: DD - Files downloaded *today* (iNiQUiTY)
|
|
||||||
// :TODO: TP - total message/posts on the system (Obv/2)
|
|
||||||
// -> Include FTN/etc.
|
|
||||||
// :TODO: LC - name of last caller to system (Obv/2)
|
|
||||||
// :TODO: TZ - Average *system* post/call ratio (iNiQUiTY)
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Special handling for XY
|
|
||||||
//
|
|
||||||
XY : function xyHack() { return; /* nothing */ },
|
|
||||||
|
|
||||||
}[code](); // :TODO: Just call toString() here and remove above - DRY
|
|
||||||
|
|
||||||
} catch(e) {
|
|
||||||
// Don't use client.log here as we may not have a client logger established yet!!
|
|
||||||
Log.warn( { code : code, exception : e.message }, 'Exception caught attempting to construct predefined MCI value');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -378,10 +378,11 @@ function getThemeArt(options, cb) {
|
||||||
options.random = _.isBoolean(options.random) ? options.random : true; // FILENAME<n>.EXT support
|
options.random = _.isBoolean(options.random) ? options.random : true; // FILENAME<n>.EXT support
|
||||||
|
|
||||||
//
|
//
|
||||||
// We look for themed art in the following manor:
|
// We look for themed art in the following order:
|
||||||
// * Supplied theme via |themeId|
|
// 1) Direct/relative path
|
||||||
// * Fallback 1: Default theme (if different than |themeId|)
|
// 2) Via theme supplied by |themeId|
|
||||||
// * General art directory
|
// 3) Via default theme
|
||||||
|
// 4) General art directory
|
||||||
//
|
//
|
||||||
async.waterfall(
|
async.waterfall(
|
||||||
[
|
[
|
||||||
|
@ -389,7 +390,7 @@ function getThemeArt(options, cb) {
|
||||||
//
|
//
|
||||||
// We allow relative (to enigma-bbs) or full paths
|
// We allow relative (to enigma-bbs) or full paths
|
||||||
//
|
//
|
||||||
if('/' === options.name[0]) {
|
if('/' === options.name.charAt(0)) {
|
||||||
// just take the path as-is
|
// just take the path as-is
|
||||||
options.basePath = paths.dirname(options.name);
|
options.basePath = paths.dirname(options.name);
|
||||||
} else if(options.name.indexOf('/') > -1) {
|
} else if(options.name.indexOf('/') > -1) {
|
||||||
|
@ -409,41 +410,35 @@ function getThemeArt(options, cb) {
|
||||||
}
|
}
|
||||||
|
|
||||||
options.basePath = paths.join(Config.paths.themes, options.themeId);
|
options.basePath = paths.join(Config.paths.themes, options.themeId);
|
||||||
|
art.getArt(options.name, options, (err, artInfo) => {
|
||||||
art.getArt(options.name, options, function artLoaded(err, artInfo) {
|
return callback(null, artInfo);
|
||||||
callback(null, artInfo);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function fromDefaultTheme(artInfo, callback) {
|
function fromDefaultTheme(artInfo, callback) {
|
||||||
if(artInfo || Config.defaults.theme === options.themeId) {
|
if(artInfo || Config.defaults.theme === options.themeId) {
|
||||||
callback(null, artInfo);
|
return callback(null, artInfo);
|
||||||
} else {
|
|
||||||
options.basePath = paths.join(Config.paths.themes, Config.defaults.theme);
|
|
||||||
|
|
||||||
art.getArt(options.name, options, function artLoaded(err, artInfo) {
|
|
||||||
callback(null, artInfo);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.basePath = paths.join(Config.paths.themes, Config.defaults.theme);
|
||||||
|
art.getArt(options.name, options, (err, artInfo) => {
|
||||||
|
return callback(null, artInfo);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
function fromGeneralArtDir(artInfo, callback) {
|
function fromGeneralArtDir(artInfo, callback) {
|
||||||
if(artInfo) {
|
if(artInfo) {
|
||||||
callback(null, artInfo);
|
return callback(null, artInfo);
|
||||||
} else {
|
|
||||||
options.basePath = Config.paths.art;
|
|
||||||
|
|
||||||
art.getArt(options.name, options, function artLoaded(err, artInfo) {
|
|
||||||
callback(err, artInfo);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options.basePath = Config.paths.art;
|
||||||
|
art.getArt(options.name, options, (err, artInfo) => {
|
||||||
|
return callback(err, artInfo);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
function complete(err, artInfo) {
|
function complete(err, artInfo) {
|
||||||
if(err) {
|
if(err) {
|
||||||
if(options.client) {
|
const logger = _.get(options, 'client.log') || Log;
|
||||||
options.client.log.debug( { error : err.message }, 'Cannot find theme art' );
|
logger.debug( { reason : err.message }, 'Cannot find theme art');
|
||||||
} else {
|
|
||||||
Log.debug( { error : err.message }, 'Cannot find theme art' );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return cb(err, artInfo);
|
return cb(err, artInfo);
|
||||||
}
|
}
|
||||||
|
|
2
main.js
2
main.js
|
@ -9,4 +9,4 @@
|
||||||
If this file does not run directly, ensure it's executable:
|
If this file does not run directly, ensure it's executable:
|
||||||
> chmod u+x main.js
|
> chmod u+x main.js
|
||||||
*/
|
*/
|
||||||
require('./core/bbs.js').bbsMain();
|
require('./core/bbs.js').main();
|
|
@ -17,6 +17,7 @@ const Config = require('../core/config.js').config;
|
||||||
const DownloadQueue = require('../core/download_queue.js');
|
const DownloadQueue = require('../core/download_queue.js');
|
||||||
const FileAreaWeb = require('../core/file_area_web.js');
|
const FileAreaWeb = require('../core/file_area_web.js');
|
||||||
const FileBaseFilters = require('../core/file_base_filter.js');
|
const FileBaseFilters = require('../core/file_base_filter.js');
|
||||||
|
const resolveMimeType = require('../core/mime_util.js').resolveMimeType;
|
||||||
|
|
||||||
const cleanControlCodes = require('../core/string_util.js').cleanControlCodes;
|
const cleanControlCodes = require('../core/string_util.js').cleanControlCodes;
|
||||||
|
|
||||||
|
@ -236,9 +237,8 @@ exports.getModule = class FileAreaList extends MenuModule {
|
||||||
});
|
});
|
||||||
|
|
||||||
if(entryInfo.archiveType) {
|
if(entryInfo.archiveType) {
|
||||||
entryInfo.archiveTypeDesc = _.has(Config, [ 'archives', 'formats', entryInfo.archiveType, 'desc' ]) ?
|
const mimeType = resolveMimeType(entryInfo.archiveType);
|
||||||
Config.archives.formats[entryInfo.archiveType].desc :
|
entryInfo.archiveTypeDesc = mimeType ? _.get(Config, [ 'fileTypes', mimeType, 'desc' ] ) || mimeType : entryInfo.archiveType;
|
||||||
entryInfo.archiveType;
|
|
||||||
} else {
|
} else {
|
||||||
entryInfo.archiveTypeDesc = 'N/A';
|
entryInfo.archiveTypeDesc = 'N/A';
|
||||||
}
|
}
|
||||||
|
|
|
@ -2372,6 +2372,17 @@
|
||||||
]
|
]
|
||||||
focusItemIndex: 1
|
focusItemIndex: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// :TODO: these can be removed once the hack is not required:
|
||||||
|
TL10: {}
|
||||||
|
TL11: {}
|
||||||
|
TL12: {}
|
||||||
|
TL13: {}
|
||||||
|
TL14: {}
|
||||||
|
TL15: {}
|
||||||
|
TL16: {}
|
||||||
|
TL17: {}
|
||||||
|
TL18: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
submit: {
|
submit: {
|
||||||
|
@ -2453,6 +2464,17 @@
|
||||||
"general", "nfo/readme", "file listing"
|
"general", "nfo/readme", "file listing"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// :TODO: these can be removed once the hack is not required:
|
||||||
|
TL10: {}
|
||||||
|
TL11: {}
|
||||||
|
TL12: {}
|
||||||
|
TL13: {}
|
||||||
|
TL14: {}
|
||||||
|
TL15: {}
|
||||||
|
TL16: {}
|
||||||
|
TL17: {}
|
||||||
|
TL18: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
actionKeys: [
|
actionKeys: [
|
||||||
|
|
33
package.json
33
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "enigma-bbs",
|
"name": "enigma-bbs",
|
||||||
"version": "0.0.5-alpha",
|
"version": "0.0.6-alpha",
|
||||||
"description": "ENiGMA½ Bulletin Board System",
|
"description": "ENiGMA½ Bulletin Board System",
|
||||||
"author": "Bryan Ashby <bryan@l33t.codes>",
|
"author": "Bryan Ashby <bryan@l33t.codes>",
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
|
@ -15,36 +15,37 @@
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bbs",
|
"bbs",
|
||||||
"telnet",
|
"telnet",
|
||||||
|
"ssh",
|
||||||
"retro"
|
"retro"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "^2.1.4",
|
"async": "^2.4.0",
|
||||||
"binary": "0.3.x",
|
"binary": "0.3.x",
|
||||||
"buffers": "NuSkooler/node-buffers",
|
"buffers": "NuSkooler/node-buffers",
|
||||||
"bunyan": "^1.7.1",
|
"bunyan": "^1.8.10",
|
||||||
"farmhash": "^1.2.1",
|
"farmhash": "^1.2.1",
|
||||||
"fs-extra": "^2.0.0",
|
"fs-extra": "^3.0.1",
|
||||||
"gaze": "^1.1.2",
|
"gaze": "^1.1.2",
|
||||||
"hashids": "^1.1.1",
|
"hashids": "^1.1.1",
|
||||||
"hjson": "^2.4.1",
|
"hjson": "^2.4.2",
|
||||||
"iconv-lite": "^0.4.13",
|
"iconv-lite": "^0.4.17",
|
||||||
"inquirer": "^3.0.1",
|
"inquirer": "^3.0.6",
|
||||||
"later": "1.2.0",
|
"later": "1.2.0",
|
||||||
"lodash": "^4.17.4",
|
"lodash": "^4.17.4",
|
||||||
"mime-types": "^2.1.12",
|
"mime-types": "^2.1.15",
|
||||||
"minimist": "1.2.x",
|
"minimist": "1.2.x",
|
||||||
"moment": "^2.11.0",
|
"moment": "^2.18.1",
|
||||||
"uuid": "^3.0.1",
|
"nodemailer": "^4.0.1",
|
||||||
"uuid-parse" : "^1.0.0",
|
|
||||||
"ptyw.js": "NuSkooler/ptyw.js",
|
"ptyw.js": "NuSkooler/ptyw.js",
|
||||||
|
"sanitize-filename": "^1.6.1",
|
||||||
"sqlite3": "^3.1.1",
|
"sqlite3": "^3.1.1",
|
||||||
"ssh2": "^0.5.1",
|
"ssh2": "^0.5.1",
|
||||||
"temptmp" : "^1.0.0",
|
"temptmp": "^1.0.0",
|
||||||
"sanitize-filename" : "^1.6.1",
|
"uuid": "^3.0.1",
|
||||||
"nodemailer" : "^3.1.3"
|
"uuid-parse": "^1.0.0",
|
||||||
},
|
"ws" : "^2.3.1"
|
||||||
"devDependencies": {
|
|
||||||
},
|
},
|
||||||
|
"devDependencies": {},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.2"
|
"node": ">=6.9.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/* jslint node: true */
|
||||||
|
/* eslint-disable no-console */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// :TODO: Make this it's own sep tool/repo
|
||||||
|
|
||||||
|
const exiftool = require('exiftool');
|
||||||
|
const fs = require('fs');
|
||||||
|
const moment = require('moment');
|
||||||
|
|
||||||
|
const TOOL_VERSION = '1.0.0.0';
|
||||||
|
|
||||||
|
// map fileTypes -> handlers
|
||||||
|
const FILETYPE_HANDLERS = {};
|
||||||
|
[ 'AIFF', 'APE', 'FLAC', 'OGG', 'MP3' ].forEach(ext => FILETYPE_HANDLERS[ext] = audioFile);
|
||||||
|
[ 'PDF', 'DOC', 'DOCX', 'DOCM', 'ODB', 'ODC', 'ODF', 'ODG', 'ODI', 'ODP', 'ODS', 'ODT' ].forEach(ext => FILETYPE_HANDLERS[ext] = documentFile);
|
||||||
|
[ 'PNG', 'JPEG', 'GIF', 'WEBP', 'XCF' ].forEach(ext => FILETYPE_HANDLERS[ext] = imageFile);
|
||||||
|
|
||||||
|
function audioFile(metadata) {
|
||||||
|
let desc = `${metadata.artist||'Unknown Artist'} - ${metadata.title||'Unknown'} (`;
|
||||||
|
if(metadata.year) {
|
||||||
|
desc += `${metadata.year}, `;
|
||||||
|
}
|
||||||
|
desc += `${metadata.audioBitrate})`;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentFile(metadata) {
|
||||||
|
let desc = `${metadata.author||'Unknown Author'} - ${metadata.title||'Unknown'}`;
|
||||||
|
const created = moment(metadata.createdate);
|
||||||
|
if(created.isValid()) {
|
||||||
|
desc += ` (${created.format('YYYY')})`;
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function imageFile(metadata) {
|
||||||
|
let desc = `${metadata.fileType} image (`;
|
||||||
|
if(metadata.animationIterations) {
|
||||||
|
desc += 'Animated, ';
|
||||||
|
}
|
||||||
|
desc += `${metadata.imageSize}px`;
|
||||||
|
const created = moment(metadata.createdate);
|
||||||
|
if(created.isValid()) {
|
||||||
|
desc += `, ${created.format('YYYY')})`;
|
||||||
|
} else {
|
||||||
|
desc += ')';
|
||||||
|
}
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const argv = exports.argv = require('minimist')(process.argv.slice(2), {
|
||||||
|
alias : {
|
||||||
|
h : 'help',
|
||||||
|
v : 'version',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(argv.version) {
|
||||||
|
console.info(TOOL_VERSION);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(0 === argv._.length || argv.help) {
|
||||||
|
console.info('usage: exiftool2desc.js [--version] [--help] PATH');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = argv._[0];
|
||||||
|
|
||||||
|
fs.readFile(path, (err, data) => {
|
||||||
|
if(err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
exiftool.metadata(data, (err, metadata) => {
|
||||||
|
if(err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handler = FILETYPE_HANDLERS[metadata.fileType];
|
||||||
|
if(!handler) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(handler(metadata));
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return main();
|
Loading…
Reference in New Issue