code/main.js

"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: 'hooks-dir',
    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"]) {
                    config.load_from(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);
                }
                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) {
                        telegram.init(tcid).then(commandline.init).then(function () {
                            commandline.execute(command.name, command.options).then(resolve).catch(reject)
                        }).catch(function (error) {
                            logger.error(error);
                        });
                    }
                    break
                }
            }).catch(reject);
        });
    }
}

module.exports = MainService;