#!/usr/bin/env node /* jslint node: true */ /* eslint-disable no-console */ 'use strict'; // ENiGMA½ const config = require('./core/config.js'); const db = require('./core/database.js'); const resolvePath = require('./core/misc_util.js').resolvePath; // deps const _ = require('lodash'); const async = require('async'); const inq = require('inquirer'); const mkdirsSync = require('fs-extra').mkdirsSync; const fs = require('fs'); const hjson = require('hjson'); const paths = require('path'); const argv = require('minimist')(process.argv.slice(2)); const ExitCodes = { SUCCESS : 0, ERROR : -1, BAD_COMMAND : -2, BAD_ARGS : -3, }; const USAGE_HELP = { General : `usage: optutil.js [--version] [--help] [] global args: --config PATH : specify config path (${getDefaultConfigPath()}) where is one of: user : user utilities config : config file management file-base fb : file base management `, User : `usage: optutil.js user --user USERNAME valid args: --user USERNAME : specify username for further actions --password PASS : set new password --delete : delete user --activate : activate user --deactivate : deactivate user `, Config : `usage: optutil.js config valid args: --new : generate a new/initial configuration `, FileBase : `usage: oputil.js file-base [] [] where is one of: scan AREA_TAG : (re)scan area specified by AREA_TAG for new files multiple area tags can be specified in form of AREA_TAG1 AREA_TAG2 ... scan args: --tags TAG1,TAG2,... : specify tag(s) to assign to discovered entries ` }; function printUsageAndSetExitCode(command, exitCode) { if(_.isUndefined(exitCode)) { exitCode = ExitCodes.ERROR; } process.exitCode = exitCode; const errMsg = USAGE_HELP[command]; if(errMsg) { console.error(errMsg); } } function initConfig(cb) { const configPath = argv.config ? argv.config : config.getDefaultPath(); config.init(configPath, cb); } function initConfigAndDatabases(cb) { async.series( [ function init(callback) { initConfig(callback); }, function initDb(callback) { db.initializeDatabases(callback); }, ], err => { return cb(err); } ); } function getUser(userName, cb) { const user = require('./core/user.js'); user.getUserIdAndName(argv.user, function userNameAndId(err, userId) { if(err) { process.exitCode = ExitCodes.BAD_ARGS; return cb(new Error('Failed to retrieve user')); } else { let u = new user.User(); u.userId = userId; return cb(null, u); } }); } function initAndGetUser(userName, cb) { async.waterfall( [ function init(callback) { initConfigAndDatabases(callback); }, function getUserObject(callback) { getUser(argv.user, (err, user) => { if(err) { process.exitCode = ExitCodes.BAD_ARGS; return callback(err); } return callback(null, user); }); } ], (err, user) => { return cb(err, user); } ); } function setAccountStatus(userName, active) { async.waterfall( [ function init(callback) { initAndGetUser(argv.user, callback); }, function activateUser(user, callback) { const AccountStatus = require('./core/user.js').User.AccountStatus; user.persistProperty('account_status', active ? AccountStatus.active : AccountStatus.inactive, callback); } ], err => { if(err) { console.error(err.message); } else { console.info('User ' + ((true === active) ? 'activated' : 'deactivated')); } } ); } function handleUserCommand() { if(true === argv.help || !_.isString(argv.user) || 0 === argv.user.length) { return printUsageAndSetExitCode('User', ExitCodes.ERROR); } if(_.isString(argv.password)) { if(0 === argv.password.length) { process.exitCode = ExitCodes.BAD_ARGS; return console.error('Invalid password'); } async.waterfall( [ function init(callback) { initAndGetUser(argv.user, callback); }, function setNewPass(user, callback) { user.setNewAuthCredentials(argv.password, function credsSet(err) { if(err) { process.exitCode = ExitCodes.ERROR; callback(new Error('Failed setting password')); } else { callback(null); } }); } ], function complete(err) { if(err) { console.error(err.message); } else { console.info('Password set'); } } ); } else if(argv.activate) { setAccountStatus(argv.user, true); } else if(argv.deactivate) { setAccountStatus(argv.user, false); } } function getAnswers(questions, cb) { inq.prompt(questions).then( answers => { return cb(answers); }); } function getDefaultConfigPath() { return resolvePath('~/.config/enigma-bbs/config.hjson'); } const QUESTIONS = { Intro : [ { name : 'createNewConfig', message : 'Create a new configuration?', type : 'confirm', default : false, }, { name : 'configPath', message : 'Configuration path:', default : argv.config ? argv.config : getDefaultConfigPath(), when : answers => answers.createNewConfig }, ], OverwriteConfig : [ { name : 'overwriteConfig', message : 'Config file exists. Overwrite?', type : 'confirm', default : false, } ], Basic : [ { name : 'boardName', message : 'BBS name:', default : 'New ENiGMA½ BBS', }, ], Misc : [ { name : 'loggingLevel', message : 'Logging level:', type : 'list', choices : [ 'Error', 'Warn', 'Info', 'Debug', 'Trace' ], default : 2, filter : s => s.toLowerCase(), }, { name : 'sevenZipExe', message : '7-Zip executable:', type : 'list', choices : [ '7z', '7za', 'None' ] } ], MessageConfAndArea : [ { name : 'msgConfName', message : 'First message conference:', default : 'Local', }, { name : 'msgConfDesc', message : 'Conference description:', default : 'Local Areas', }, { name : 'msgAreaName', message : 'First area in message conference:', default : 'General', }, { name : 'msgAreaDesc', message : 'Area description:', default : 'General chit-chat', } ] }; function makeMsgConfAreaName(s) { return s.toLowerCase().replace(/\s+/g, '_'); } function askNewConfigQuestions(cb) { const ui = new inq.ui.BottomBar(); let configPath; let config; async.waterfall( [ function intro(callback) { getAnswers(QUESTIONS.Intro, answers => { if(!answers.createNewConfig) { return callback('exit'); } // adjust for ~ and the like configPath = resolvePath(answers.configPath); const configDir = paths.dirname(configPath); mkdirsSync(configDir); // // Check if the file exists and can be written to // fs.access(configPath, fs.F_OK | fs.W_OK, err => { if(err) { if('EACCES' === err.code) { ui.log.write(`${configPath} cannot be written to`); callback('exit'); } else if('ENOENT' === err.code) { callback(null, false); } } else { callback(null, true); // exists + writable } }); }); }, function promptOverwrite(needPrompt, callback) { if(needPrompt) { getAnswers(QUESTIONS.OverwriteConfig, answers => { callback(answers.overwriteConfig ? null : 'exit'); }); } else { callback(null); } }, function basic(callback) { getAnswers(QUESTIONS.Basic, answers => { config = { general : { boardName : answers.boardName, }, }; callback(null); }); }, function msgConfAndArea(callback) { getAnswers(QUESTIONS.MessageConfAndArea, answers => { config.messageConferences = {}; const confName = makeMsgConfAreaName(answers.msgConfName); const areaName = makeMsgConfAreaName(answers.msgAreaName); config.messageConferences[confName] = { name : answers.msgConfName, desc : answers.msgConfDesc, sort : 1, default : true, }; config.messageConferences.another_sample_conf = { name : 'Another Sample Conference', desc : 'Another conference example. Change me!', sort : 2, }; config.messageConferences[confName].areas = {}; config.messageConferences[confName].areas[areaName] = { name : answers.msgAreaName, desc : answers.msgAreaDesc, sort : 1, default : true, }; config.messageConferences.another_sample_conf = { name : 'Another Sample Conference', desc : 'Another conf sample. Change me!', areas : { another_sample_area : { name : 'Another Sample Area', desc : 'Another area example. Change me!', sort : 2 } } }; callback(null); }); }, function misc(callback) { getAnswers(QUESTIONS.Misc, answers => { if('None' !== answers.sevenZipExe) { config.archivers = { zip : { compressCmd : answers.sevenZipExe, decompressCmd : answers.sevenZipExe, } }; } config.logging = { level : answers.loggingLevel, }; callback(null); }); } ], err => { cb(err, configPath, config); } ); } function handleConfigCommand() { if(true === argv.help) { return printUsageAndSetExitCode('Config', ExitCodes.ERROR); } if(argv.new) { askNewConfigQuestions( (err, configPath, config) => { if(err) { return; } config = hjson.stringify(config, { bracesSameLine : true, spaces : '\t' } ); try { fs.writeFileSync(configPath, config, 'utf8'); console.info('Configuration generated'); } catch(e) { console.error('Exception attempting to create config: ' + e.toString()); } }); } else { return printUsageAndSetExitCode('Config', ExitCodes.ERROR); } } function scanFileBaseArea(areaTag, options, iterator, cb) { async.waterfall( [ function getFileArea(callback) { const fileAreaMod = require('./core/file_base_area.js'); const areaInfo = fileAreaMod.getFileAreaByTag(areaTag); if(!areaInfo) { return callback(new Error(`Invalid file base area tag: ${areaTag}`)); } return callback(null, fileAreaMod, areaInfo); }, function performScan(fileAreaMod, areaInfo, callback) { fileAreaMod.scanFileAreaForChanges(areaInfo, options, iterator, err => { return callback(err); }); } ], err => { return cb(err); } ); } function fileAreaScan() { const options = {}; const tags = argv.tags; if(tags) { options.tags = tags.split(','); } const areaTags = argv._.slice(2); function scanFileIterator(stepInfo, nextScanStep) { if('start' === stepInfo.step) { console.info(`Scanning ${stepInfo.filePath}...`); } return nextScanStep(null); // :TODO: add 'finished' step when avail } async.series( [ function init(callback) { return initConfigAndDatabases(callback); }, function scanAreas(callback) { async.eachSeries(areaTags, (areaTag, nextAreaTag) => { scanFileBaseArea(areaTag, options, scanFileIterator, err => { return nextAreaTag(err); }); }, err => { return callback(err); }); } ], err => { if(err) { process.exitCode = ExitCodes.ERROR; console.error(err.message); } } ); } function handleFileBaseCommand() { if(true === argv.help) { return printUsageAndSetExitCode('FileBase', ExitCodes.ERROR); } const action = argv._[1]; switch(action) { case 'scan' : return fileAreaScan(); } } function main() { process.exitCode = ExitCodes.SUCCESS; if(true === argv.version) { return console.info(require('./package.json').version); } if(0 === argv._.length || 'help' === argv._[0]) { printUsageAndSetExitCode('General', ExitCodes.SUCCESS); } switch(argv._[0]) { case 'user' : handleUserCommand(); break; case 'config' : handleConfigCommand(); break; case 'file-base' : case 'fb' : handleFileBaseCommand(); break; default: printUsageAndSetExitCode('', ExitCodes.BAD_COMMAND); } } main();