diff --git a/UPGRADE.md b/UPGRADE.md index 3726fb78..802a8092 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -28,6 +28,7 @@ Upgrading from GitHub is easy: ```bash cd /path/to/enigma-bbs git pull +rm -rf npm_modules # do this any time you update Node.js itself npm install ``` diff --git a/core/file_base_area.js b/core/file_base_area.js index 0e05b497..bce81fac 100644 --- a/core/file_base_area.js +++ b/core/file_base_area.js @@ -24,6 +24,7 @@ exports.isInternalArea = isInternalArea; exports.getAvailableFileAreas = getAvailableFileAreas; exports.getSortedAvailableFileAreas = getSortedAvailableFileAreas; exports.getAreaDefaultStorageDirectory = getAreaDefaultStorageDirectory; +exports.getAreaStorageLocations = getAreaStorageLocations; exports.getDefaultFileAreaTag = getDefaultFileAreaTag; exports.getFileAreaByTag = getFileAreaByTag; exports.getFileEntryPath = getFileEntryPath; diff --git a/core/oputil/oputil_common.js b/core/oputil/oputil_common.js new file mode 100644 index 00000000..3b99bf11 --- /dev/null +++ b/core/oputil/oputil_common.js @@ -0,0 +1,76 @@ +/* jslint node: true */ +/* eslint-disable no-console */ +'use strict'; + +const resolvePath = require('../misc_util.js').resolvePath; + +const config = require('../../core/config.js'); +const db = require('../../core/database.js'); + +const _ = require('lodash'); +const async = require('async'); + +exports.printUsageAndSetExitCode = printUsageAndSetExitCode; +exports.getDefaultConfigPath = getDefaultConfigPath; +exports.initConfigAndDatabases = initConfigAndDatabases; +exports.getAreaAndStorage = getAreaAndStorage; + +const exitCodes = exports.ExitCodes = { + SUCCESS : 0, + ERROR : -1, + BAD_COMMAND : -2, + BAD_ARGS : -3, +}; + +const argv = exports.argv = require('minimist')(process.argv.slice(2)); + +function printUsageAndSetExitCode(errMsg, exitCode) { + if(_.isUndefined(exitCode)) { + exitCode = exitCodes.ERROR; + } + + process.exitCode = exitCode; + + if(errMsg) { + console.error(errMsg); + } +} + +function getDefaultConfigPath() { + return resolvePath('~/.config/enigma-bbs/config.hjson'); +} + +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 getAreaAndStorage(tags) { + return tags.map(tag => { + const parts = tag.split('@'); + const entry = { + areaTag : parts[0], + }; + if(parts[1]) { + entry.storageTag = parts[1]; + } + return entry; + }); +} \ No newline at end of file diff --git a/core/oputil/oputil_config.js b/core/oputil/oputil_config.js new file mode 100644 index 00000000..7071f459 --- /dev/null +++ b/core/oputil/oputil_config.js @@ -0,0 +1,258 @@ +/* jslint node: true */ +/* eslint-disable no-console */ +'use strict'; + +// ENiGMA½ +const resolvePath = require('../../core/misc_util.js').resolvePath; +const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; +const ExitCodes = require('./oputil_common.js').ExitCodes; +const argv = require('./oputil_common.js').argv; +const getDefaultConfigPath = require('./oputil_common.js').getDefaultConfigPath; +const getHelpFor = require('./oputil_help.js').getHelpFor; + +// deps +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'); + +exports.handleConfigCommand = handleConfigCommand; + + +function getAnswers(questions, cb) { + inq.prompt(questions).then( answers => { + return cb(answers); + }); +} + +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(getHelpFor('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(getHelpFor('Config'), ExitCodes.ERROR); + } +} diff --git a/core/oputil/oputil_file_base.js b/core/oputil/oputil_file_base.js new file mode 100644 index 00000000..fe9ebb54 --- /dev/null +++ b/core/oputil/oputil_file_base.js @@ -0,0 +1,175 @@ +/* jslint node: true */ +/* eslint-disable no-console */ +'use strict'; + +const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; +const ExitCodes = require('./oputil_common.js').ExitCodes; +const argv = require('./oputil_common.js').argv; +const initConfigAndDatabases = require('./oputil_common.js').initConfigAndDatabases; +const getHelpFor = require('./oputil_help.js').getHelpFor; +const getAreaAndStorage = require('./oputil_common.js').getAreaAndStorage; + + +const async = require('async'); +const fs = require('fs'); +const paths = require('path'); + +exports.handleFileBaseCommand = handleFileBaseCommand; + +/* + :TODO: + + Global options: + --yes: assume yes + --no-prompt: try to avoid user input + + Prompt for import and description before scan + * Only after finding duplicate-by-path + * Default to filename -> desc if auto import + +*/ + +let fileArea; // required during init + +function scanFileAreaForChanges(areaInfo, options, cb) { + + const storageLocations = fileArea.getAreaStorageLocations(areaInfo).filter(sl => { + return options.areaAndStorageInfo.find(asi => { + return !asi.storageTag || sl.storageTag === asi.storageTag; + }); + }); + + async.eachSeries(storageLocations, (storageLoc, nextLocation) => { + async.series( + [ + function scanPhysFiles(callback) { + const physDir = storageLoc.dir; + + fs.readdir(physDir, (err, files) => { + if(err) { + return callback(err); + } + + async.eachSeries(files, (fileName, nextFile) => { + const fullPath = paths.join(physDir, fileName); + + fs.stat(fullPath, (err, stats) => { + if(err) { + // :TODO: Log me! + return nextFile(null); // always try next file + } + + if(!stats.isFile()) { + return nextFile(null); + } + + process.stdout.write(`* Scanning ${fullPath}... `); + + fileArea.scanFile( + fullPath, + { + areaTag : areaInfo.areaTag, + storageTag : storageLoc.storageTag + }, + (err, fileEntry, dupeEntries) => { + if(err) { + // :TODO: Log me!!! + console.info(`Error: ${err.message}`); + return nextFile(null); // try next anyway + } + + + + if(dupeEntries.length > 0) { + // :TODO: Handle duplidates -- what to do here??? + console.info('Dupe'); + return nextFile(null); + } else { + console.info('Done!'); + if(Array.isArray(options.tags)) { + options.tags.forEach(tag => { + fileEntry.hashTags.add(tag); + }); + } + + fileEntry.persist( err => { + return nextFile(err); + }); + } + } + ); + }); + }, err => { + return callback(err); + }); + }); + }, + function scanDbEntries(callback) { + // :TODO: Look @ db entries for area that were *not* processed above + return callback(null); + } + ], + err => { + return nextLocation(err); + } + ); + }, + err => { + return cb(err); + }); +} + +function scanFileAreas() { + const options = {}; + + const tags = argv.tags; + if(tags) { + options.tags = tags.split(','); + } + + options.areaAndStorageInfo = getAreaAndStorage(argv._.slice(2)); + + async.series( + [ + function init(callback) { + return initConfigAndDatabases(callback); + }, + function scanAreas(callback) { + fileArea = require('../../core/file_base_area.js'); + + async.eachSeries(options.areaAndStorageInfo, (areaAndStorage, nextAreaTag) => { + const areaInfo = fileArea.getFileAreaByTag(areaAndStorage.areaTag); + if(!areaInfo) { + return nextAreaTag(new Error(`Invalid file base area tag: ${areaAndStorage.areaTag}`)); + } + + console.info(`Processing area "${areaInfo.name}":`); + + scanFileAreaForChanges(areaInfo, options, err => { + return callback(err); + }); + }, err => { + return callback(err); + }); + } + ], + err => { + if(err) { + process.exitCode = ExitCodes.ERROR; + console.error(err.message); + } + } + ); +} + +function handleFileBaseCommand() { + if(true === argv.help) { + return printUsageAndSetExitCode(getHelpFor('FileBase'), ExitCodes.ERROR); + } + + const action = argv._[1]; + + switch(action) { + case 'scan' : return scanFileAreas(); + } +} \ No newline at end of file diff --git a/core/oputil/oputil_help.js b/core/oputil/oputil_help.js new file mode 100644 index 00000000..25237680 --- /dev/null +++ b/core/oputil/oputil_help.js @@ -0,0 +1,55 @@ +/* jslint node: true */ +/* eslint-disable no-console */ +'use strict'; + +const getDefaultConfigPath = require('./oputil_common.js').getDefaultConfigPath; + +exports.getHelpFor = getHelpFor; + +const usageHelp = exports.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 ... + +valid scan : + --tags TAG1,TAG2,... : specify tag(s) to assign to discovered entries +` +}; + +function getHelpFor(command) { + return usageHelp[command]; +} \ No newline at end of file diff --git a/core/oputil/oputil_main.js b/core/oputil/oputil_main.js new file mode 100644 index 00000000..78dae8d2 --- /dev/null +++ b/core/oputil/oputil_main.js @@ -0,0 +1,45 @@ +/* jslint node: true */ +/* eslint-disable no-console */ +'use strict'; + +const ExitCodes = require('./oputil_common.js').ExitCodes; +const argv = require('./oputil_common.js').argv; +const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; +const handleUserCommand = require('./oputil_user.js').handleUserCommand; +const handleFileBaseCommand = require('./oputil_file_base.js').handleFileBaseCommand; +const handleConfigCommand = require('./oputil_config.js').handleConfigCommand; +const getHelpFor = require('./oputil_help.js').getHelpFor; + + +module.exports = function() { + + process.exitCode = ExitCodes.SUCCESS; + + if(true === argv.version) { + return console.info(require('../package.json').version); + } + + if(0 === argv._.length || + 'help' === argv._[0]) + { + printUsageAndSetExitCode(getHelpFor('General'), ExitCodes.SUCCESS); + } + + switch(argv._[0]) { + case 'user' : + handleUserCommand(); + break; + + case 'config' : + handleConfigCommand(); + break; + + case 'file-base' : + case 'fb' : + handleFileBaseCommand(); + break; + + default: + return printUsageAndSetExitCode('', ExitCodes.BAD_COMMAND); + } +}; diff --git a/core/oputil/oputil_user.js b/core/oputil/oputil_user.js new file mode 100644 index 00000000..feb712f6 --- /dev/null +++ b/core/oputil/oputil_user.js @@ -0,0 +1,112 @@ +/* jslint node: true */ +/* eslint-disable no-console */ +'use strict'; + +const printUsageAndSetExitCode = require('./oputil_common.js').printUsageAndSetExitCode; +const ExitCodes = require('./oputil_common.js').ExitCodes; +const argv = require('./oputil_common.js').argv; +const initConfigAndDatabases = require('./oputil_common.js').initConfigAndDatabases; + + +const async = require('async'); + +exports.handleUserCommand = handleUserCommand; + +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 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')); + } + } + ); +} \ No newline at end of file diff --git a/docs/archive.md b/docs/archive.md index 4b5a005e..8303858c 100644 --- a/docs/archive.md +++ b/docs/archive.md @@ -15,7 +15,7 @@ The following archivers are pre-configured in ENiGMA½ as of this writing. Remem #### Lha * Formats: LHA files such as .lzh. * Key: `Lha` -* Homepage/package: `lhasa` on most *nix environments. See also https://fragglet.github.io/lhasa/ +* Homepage/package: `lhasa` on most *nix environments. See also https://fragglet.github.io/lhasa/ and http://www2m.biglobe.ne.jp/~dolphin/lha/lha-unix.htm #### Arj * Formats: .arj diff --git a/oputil.js b/oputil.js index c14f04fb..365b0d06 100755 --- a/oputil.js +++ b/oputil.js @@ -4,555 +4,4 @@ /* 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(); \ No newline at end of file +require('./core/oputil/oputil_main.js')();