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
}];

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
              });
            }
          }
        }

        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);
        }

        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(new Error("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;