enigma-bbs/core/module_util.js

208 lines
6.3 KiB
JavaScript

/* jslint node: true */
'use strict';
// ENiGMA½
const Config = require('./config.js').get;
const Log = require('./logger.js').log;
const { Errors, ErrorReasons } = require('./enig_error.js');
// deps
const fs = require('graceful-fs');
const paths = require('path');
const _ = require('lodash');
const assert = require('assert');
const async = require('async');
const glob = require('glob');
// exports
exports.loadModuleEx = loadModuleEx;
exports.loadModule = loadModule;
exports.loadModulesForCategory = loadModulesForCategory;
exports.getModulePaths = getModulePaths;
exports.initializeModules = initializeModules;
function loadModuleEx(options, cb) {
assert(_.isObject(options));
assert(_.isString(options.name));
assert(_.isString(options.path));
const modConfig = _.isObject(Config[options.category])
? Config[options.category][options.name]
: null;
if (_.isObject(modConfig) && false === modConfig.enabled) {
return cb(
Errors.AccessDenied(
`Module "${options.name}" is disabled`,
ErrorReasons.Disabled
)
);
}
//
// Modules are allowed to live in /path/to/<moduleName>/<moduleName>.js or
// simply in /path/to/<moduleName>.js. This allows for more advanced modules
// to have their own containing folder, package.json & dependencies, etc.
//
let mod;
let modPath = paths.join(options.path, `${options.name}.js`); // general case first
try {
mod = require(modPath);
} catch (e) {
if ('MODULE_NOT_FOUND' === e.code) {
modPath = paths.join(options.path, options.name, `${options.name}.js`);
try {
mod = require(modPath);
} catch (e) {
return cb(e);
}
} else {
return cb(e);
}
}
if (!_.isObject(mod.moduleInfo)) {
return cb(
Errors.Invalid(`No exported "moduleInfo" block for module ${modPath}!`)
);
}
if (!_.isFunction(mod.getModule)) {
return cb(
Errors.Invalid(`No exported "getModule" method for module ${modPath}!`)
);
}
return cb(null, mod);
}
function loadModule(name, category, cb) {
const path = Config().paths[category];
if (!_.isString(path)) {
return cb(
Errors.DoesNotExist(
`Not sure where to look for module "${name}" of category "${category}"`
)
);
}
loadModuleEx(
{ name: name, path: path, category: category },
function loaded(err, mod) {
return cb(err, mod);
}
);
}
function loadModulesForCategory(category, iterator, complete) {
fs.readdir(Config().paths[category], (err, files) => {
if (err) {
return iterator(err);
}
const jsModules = files.filter(file => {
return '.js' === paths.extname(file);
});
async.each(
jsModules,
(file, next) => {
loadModule(paths.basename(file, '.js'), category, (err, mod) => {
if (err) {
if (ErrorReasons.Disabled === err.reasonCode) {
Log.debug(err.message);
} else {
Log.info({ err: err }, 'Failed loading module');
}
return next(null); // continue no matter what
}
return iterator(mod, next);
});
},
err => {
if (complete) {
return complete(err);
}
}
);
});
}
function getModulePaths() {
const config = Config();
return [
config.paths.mods,
config.paths.loginServers,
config.paths.contentServers,
config.paths.chatServers,
config.paths.scannerTossers,
];
}
function initializeModules(cb) {
const Events = require('./events.js');
const modulePaths = getModulePaths().concat(__dirname);
async.each(
modulePaths,
(modulePath, nextPath) => {
glob('*{.js,/*.js}', { cwd: modulePath }, (err, files) => {
if (err) {
return nextPath(err);
}
const ourPath = paths.join(__dirname, __filename);
async.each(
files,
(moduleName, nextModule) => {
const fullModulePath = paths.join(modulePath, moduleName);
if (ourPath === fullModulePath) {
return nextModule(null);
}
try {
const mod = require(fullModulePath);
if (_.isFunction(mod.moduleInitialize)) {
const initInfo = {
events: Events,
};
mod.moduleInitialize(initInfo, err => {
if (err) {
Log.warn(
{
error: err.message,
modulePath: fullModulePath,
},
'Error during "moduleInitialize"'
);
}
return nextModule(null);
});
} else {
return nextModule(null);
}
} catch (e) {
Log.warn(
{ error: e.message, fullModulePath },
'Exception during "moduleInitialize"'
);
return nextModule(null);
}
},
err => {
return nextPath(err);
}
);
});
},
err => {
return cb(err);
}
);
}