'use strict';
/**
* MainService
* @namespace MainService
* @description Manages main init function and commands dispatching.
*/
const hooks = require('./hooks');
const telegram = require('./telegram');
const express = require('./express');
const monitor = require('./monitor');
const local = require('./local');
const logger = require('./logger');
const rpc = require('./rpc');
const commandline = require('./commandline');
const path = require('path');
const Promise = require('promise');
const commandLineCommands = require('command-line-commands');
const getUsage = require('command-line-usage');
const ansi = require('ansi-escape-sequences');
const package_desc = require('../package.json');
const _ = require('underscore');
const s = require('underscore.string');
_.mixin(s.exports());
let tcid = null;
let pid_path = path.resolve(__dirname, '..', '.pid');
const cli_common_conf = [{
name: 'verbose',
alias: 'V',
type: Boolean
}, {
name: 'telegramid',
alias: 'T',
type: String
}, {
name: 'token',
alias: 'K',
type: String
}, {
name: 'config-dir',
type: String
}, {
name: 'config-file',
type: String
}, {
name: 'hooks-dir',
type: String
}, {
name: 'log-file',
type: String
}];
let monitor_control_def = _.union([], cli_common_conf, [{
name: 'hook',
alias: 'H',
type: String,
defaultOption: true
}]);
let help_def = _.union([], cli_common_conf, [{
name: 'hook',
alias: 'H',
type: String,
defaultOption: true
}]);
/**
* @class
* @classdesc Manages main init function and commands dispatching.
*/
const MainService = {
/**
* @function help
* @description Returns command line help interface.<br/>Will include commandline hooks' help.
* @static
* @param {Config} config Link to config manager
* @param {String} hook_name You can pass a commandline hook name or command to have a detailed help here.
* @memberof MainService
* @public
* @returns {Promise}
*/
help: function (config, hook_name) {
return new Promise(function (resolve, reject) {
hooks.load().then(function () {
let cm_hooks = hooks.get_hooks('has_command_line_hook');
let help = '';
let footer = '-----------------------------';
hook_name = (hook_name || '');
let header = [
'',
ansi.format(ansi.format(require('../assets/ansi-header'), 'cyan')),
'',
ansi.format(`${package_desc.name} v${package_desc.version}`, 'bold'),
`${package_desc.description}`,
''
];
if (hook_name) {
switch (hook_name) {
case 'help':
help = getUsage(help_def, {
header: header,
description: 'This Help',
title: 'Command: help',
synopsis: 'The output of this help will change according to installed hooks'
});
break;
case 'start':
help = getUsage(cli_common_conf, {
header: header,
description: 'Start the server',
title: 'Command: start',
synopsis: 'Will start the main receiving server'
});
break;
case 'reload':
help = getUsage(cli_common_conf, {
header: header,
description: 'Reload hooks',
title: 'Command: reload',
synopsis: 'Will reload hook in the main server'
});
break;
case 'stop':
help = getUsage(cli_common_conf, {
header: header,
description: 'Stop the server',
title: 'Command: stop',
synopsis: 'Will stop the main receiving server'
});
break;
case 'monitor:start':
help = getUsage(monitor_control_def, {
header: header,
description: 'Start a monitor',
title: 'Command: monitor:start',
synopsis: 'Will start a monitoring hook'
});
break;
case 'monitor:stop':
help = getUsage(monitor_control_def, {
header: header,
description: 'Stop a monitor',
title: 'Command: monitor:stop',
synopsis: 'Will stop a monitoring hook'
});
break;
case 'monitor:restart':
help = getUsage(monitor_control_def, {
header: header,
description: 'Restart a monitor',
title: 'Command: monitor:restart',
synopsis: 'Will restart a monitoring hook'
});
break;
default:
if (config.get('commandline:active') !== false) {
for (let i = 0; i < cm_hooks.length; i++) {
let cml = cm_hooks[i];
if (_.isArray(cml.params)) {
help = getUsage(_.union([], cli_common_conf, cml.params), {
header: header,
description: cml.description,
synopsis: cml.help,
title: `Command: ${cml.cmd_name}`
});
break;
} else {
help = getUsage(cli_common_conf, {
header: header,
description: cml.description,
synopsis: cml.help,
title: `Command: ${cml.cmd_name}`
});
break;
}
}
}
break;
}
} else {
let commands = ['help', 'start', 'stop', 'reload', 'monitor:start', 'monitor:stop', 'monitor:restart'];
if (config.get('commandline:active') !== false) {
for (let i = 0; i < cm_hooks.length; i++) {
let cml = cm_hooks[i];
commands.push(cml.cmd_name);
}
}
help = getUsage([], {
header: header,
synopsis: [
ansi.format(`${commands.length} commands defined:`, 'bold'),
'',
commands.join(', '),
'',
footer,
'',
'use command ' + ansi.format('help', 'cyan') + ' followed by ' + ansi.format('command name', 'cyan') + ' to print command specific help'
]
});
}
return resolve(help);
}).catch(reject);
});
},
/**
* @function parse_commands
* @description Parse command line arguments and commands
* @static
* @param {Config} config Link to config manager
* @param {Object} cmd_arguments Command arguments override
* @memberof MainService
* @public
* @returns {Promise}
*/
parse_commands: function (config, cmd_arguments) {
return new Promise(function (resolve, reject) {
hooks.load().then(function () {
let cm_hooks = hooks.get_hooks('has_command_line_hook');
let cla = [{
name: 'help',
definitions: help_def
}, {
name: 'start',
definitions: cli_common_conf
}, {
name: 'stop',
definitions: cli_common_conf
}, {
name: 'reload',
definitions: cli_common_conf
}, {
name: 'monitor:start',
definitions: monitor_control_def
}, {
name: 'monitor:stop',
definitions: monitor_control_def
}, {
name: 'monitor:restart',
definitions: monitor_control_def
}];
if (config.get('commandline:active') !== false) {
for (let i = 0; i < cm_hooks.length; i++) {
let cml = cm_hooks[i];
if (_.isArray(cml.params)) {
cla.push({
name: cml.cmd_name,
definitions: _.union([], cli_common_conf, cml.params)
});
} else {
cla.push({
name: cml.cmd_name,
definitions: cli_common_conf
});
}
logger.notify(`Registered '${cml.full_name}' command line hook with command ${cml.cmd_name}`);
}
}
const cli = commandLineCommands(cla);
let command_line_res = null;
try {
if (cmd_arguments) {
command_line_res = cli.parse(cmd_arguments);
} else {
command_line_res = cli.parse();
}
} catch (e) {
logger.error(e);
}
const command = _.extend({}, command_line_res);
tcid = command.telegramid;
if (command.token) {
config.set('telegram:token', command.token);
}
if (command['config-dir'] || command['config-file']) {
config.load_from(command['config-file'] || command['config-dir']);
}
if (command['hooks-dir'] && config.get('hooks:folder') != command['hooks-dir']) {
config.set('hooks:folder', command['hooks-dir']);
return hooks.reload(command['hooks-dir']).then(function () {
return parse_command(config, cmd_arguments).then(resolve).catch(reject);
}).catch(reject);
}
command.name = (command.name || '');
command.options = (command.options || {});
resolve(command);
}).catch(reject);
});
},
/**
* @function start_server
* @description Starts the service
* @static
* @memberof MainService
* @public
* @returns {Promise}
*/
start_server: function () {
return new Promise(function (resolve, reject) {
hooks.load().then(function () {
let cm_hooks = hooks.get_hooks('has_command_line_hook');
const npid = require('npid');
const pid = npid.create(pid_path, true);
pid.removeOnExit();
process.on('uncaughtException', function (err) {
pid.remove();
return reject(err);
});
process.on('SIGINT', function () {
pid.remove();
resolve(`${package_desc.name} v${package_desc.version} stopped.`);
});
logger.log(`${package_desc.name} v${package_desc.version} starting...`);
return telegram.init(tcid).then(local.init).then(monitor.init).then(express.init).then(rpc.init).then(function () {
logger.log(`${package_desc.name} v${package_desc.version} started.`);
}).catch(function (error) {
reject(error);
});
}).catch(reject);
});
},
/**
* @function stop_server
* @description Stops the service
* @static
* @memberof MainService
* @public
* @returns {Promise}
*/
stop_server: function () {
return new Promise(function (resolve, reject) {
let fs = require('fs');
let terminate = Promise.denodeify(require('terminate'));
const read = Promise.denodeify(fs.readFile);
let mainpid = '';
return read(pid_path, 'utf8').then(function (running_pid) {
mainpid = running_pid;
return Promise.resolve(mainpid);
}).then(terminate).then(function (done) {
return new Promise(function (resolve, reject) {
if (done) {
const exec = require('child_process').exec;
exec(`kill -2 ${mainpid}`, function (error, stdout, stderr) {
if (error) {
return reject(error);
}
if (stderr.length > 0) {
return reject(stderr.toString('utf8'));
} else {
logger.log(`${package_desc.name} v${package_desc.version} has been stopped.`);
}
resolve(true);
});
} else {
resolve(false);
}
});
}).catch(function (error) {
if (error.code != 'ENOENT') {
logger.error(error);
} else {
logger.log(`${package_desc.name} v${package_desc.version} is not running.`);
}
}).finally(function () {
resolve();
});
});
},
/**
* @function main
* @description Main app
* @static
* @param {Config} config Link to config manager
* @param {Object} cmd_arguments Command arguments override
* @memberof MainService
* @public
* @returns {Promise}
*/
main: function (config, cmd_arguments) {
return new Promise(function (resolve, reject) {
return MainService.parse_commands(config, cmd_arguments).then(function (command) {
if (command.options.verbose) {
config.set('verbose', true);
}
if (command.options['log-file']) {
logger.set_log_file(command.options['log-file']);
}
switch (command.name) {
case 'help':
MainService.help(config, command.options.hook).then(resolve).catch(reject);
break;
case '':
return reject('No command specified');
case 'monitor:start':
rpc.send('start_monitor', command.options.hook).then(resolve).catch(reject);
break;
case 'monitor:stop':
rpc.send('stop_monitor', command.options.hook).then(resolve).catch(reject);
break;
case 'monitor:restart':
rpc.send('restart_monitor', command.options.hook).then(resolve).catch(reject);
break;
case 'stop':
return MainService.stop_server().then(resolve).catch(reject);
break;
case 'start':
return MainService.start_server().then(resolve).catch(reject);
break;
case 'reload':
rpc.send('reload_hooks').then(resolve).catch(reject);
break;
default:
if (config.get('commandline:active') !== false) {
return telegram.init(tcid, true).then(commandline.init).then(function () {
commandline.execute(command.name, command.options).then(resolve).catch(reject);
}).catch(function (error) {
logger.error(error);
});
} else {
reject('Command line not active');
}
break;
}
}).catch(reject);
});
}
};
module.exports = MainService;