<commit_before>#!/usr/bin/env node
var amqp = require('amqplib');
var Readable = require('stream').Readable
  || require('readable-stream/readable');
var Writable = require('stream').Writable
  || require('readable-stream/writable');
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var spawn = require('child_process').spawn;

var options = require('yargs')
  .example('$0 < foobar.txt', 'Send a file')
  .example('$0 -lk', 'Listen for connections and output data to stdout')

  .options('url', {
    'default': 'amqp://localhost',
    describe:'Connect to the RabbitMQ at <url>'})
  .options('service', {
    'default': 'rmqcat',
    describe: 'Use the service at <queue>'})
  .options('help', {
    describe: 'Print help and exit'})

  .options('send', {
    describe: 'Send directly to <queue>'})
  .options('recv', {
    describe: 'Receive directly from <queue>'})

  .options('exec', {
    describe: 'Spawn a process and use stdin and stdout from that process',
    alias: 'e'
  })

  .describe('l', 'Listen for connections')
  .describe('k', 'Keep listening after client disconnections')
  .describe('D', 'Output debug information to stderr')
  .boolean(['l', 'k', 'D']);

var argv = options.argv;

if (argv.help) {
  options.showHelp();
  process.exit(0);
}

var debug = (argv.D) ? console.warn : function() {};

var url = argv.url;

// I use three different (kinds of) queues: there is the handshake
// queue, which is the common knowledge between the client and the
// server (the "connection point", like a port); then there is an in
// queue and an out queue, both named from the point of view of the
// process.

// The stdin queue is that over which the remote sends data to the
// this process, and the out queue is that over which the process
// sends data to the remote. This process creates the in queue and
// annouces it to the remote (as the 'replyTo' of an empty message);
// the remote creates the out queue and announces it to this process.

// The difference between a client and a server (listener) is that the
// client sends an open message to the handshake queue, and the
// listener responds with an open message to the client's in queue
// (which becomes the server's out queue).

var handshakeQ = argv.service;

function closeLatch(done) {
  function either(which) {
    switch (which) {
    case 'in':
      return only('out');
    case 'out':
      return only('in');
    default:
      throw new Error('Unknown stream ' + which);
    }
  }
  function only(s) {
    return function(which) {
      switch (which) {
      case s:
        done();
        return neither;
      default:
        throw new Error('Close on stream other than expected ' + s);
      }
    }
  }
  function neither() { throw new Error('Both closed'); }
  return either;
}

var ok = amqp.connect(url);
ok.then(function(connection) {

  return connection.createChannel().then(function(ch) {

    // It's convenient, since most of the work goes through the
    // channel, to use its closure as a signal to clean up and leave.
    ch.on('close', function() {
      connection.close().then(function() {
        process.exit(0);
      });
    });

    // send and recv don't use a service (handshake) queue, they just
    // send to or receive from the queue mentioned.
    if (argv.send || argv.recv) {

      if (argv.send) {
        var dest = argv.send;
        ch.assertQueue(dest);
        var out = writableQueue(ch, dest);
        process.stdin.pipe(out);
        out.on('finish', function() {
          ch.close();
        });
      }

      else if (argv.recv) {
        var source = argv.recv;
        ch.assertQueue(source);
        var reader = readableQueue(ch, source);
        reader.on('end', function() {
          ch.close();
        });
        reader.pipe(process.stdout);
      }

      return; // no more options matter
    }

    // Make sure the handshake queue exists, since we don't know who
    // will turn up first
    debug('Asserting handshake queue: %s', handshakeQ);
    ch.assertQueue(handshakeQ);

    var stdin;
    var stdout;

    function setup() {
      if (argv.e) {
        debug('Starting process %s', argv.e);
        var args = argv.e.split(' ');
        var child = spawn(args[0], args.slice(1));
        stdin = child.stdout;
        stdout = child.stdin;
      }
      else {
        stdin = process.stdin;
        stdout = process.stdout;
      }
    }

    if (argv.l) { // act as server
      return ch.assertQueue('', {exclusive: true}).then(function(ok) {
        var inQ = ok.queue;
        debug('Created in queue: %s', inQ);

        // I need a channel on which to accept connections. Why
        // another? Because this one only deals with one connection at
        // a time (so it can rewire stdin and stdout appropriately),
        // and to do that, I must use prefetch=1 so I don't get sent
        // all connection requests at once.
        connection.createChannel().then(function(acceptCh) {
          var accepted = null;
          var current = null;

          function next() {
            stdin.unpipe();
            acceptCh.ack(accepted);
          }

          var latch;
          if (argv.k) {
            function freshLatch() {
              return closeLatch(function() {
                latch = freshLatch();
                next();
              });
            }
            latch = freshLatch();
          }
          else {
            latch = closeLatch(function() {
              next();
              ch.close();
            });
          }

          acceptCh.prefetch(1);
          // Any returned messages are a result of the 'open' not
          // being routed; this is interpreted as the connection
          // having disappeared (possibly before we even turned up)
          acceptCh.on('return', function(returned) {
            debug('open message returned from %s',
                  returned.fields.routingKey,
                  ' assuming dead connection');
            next();
          });

          acceptCh.consume(handshakeQ, function(msg) {
            switch (msg.properties.type) {
            case 'open':
              accepted = msg;
              var outQ = msg.properties.replyTo;
              debug('Recv open: out queue is %s', outQ);
              acceptCh.sendToQueue(outQ, new Buffer(0),
                                   {type: 'open',
                                    mandatory: true,
                                    replyTo: inQ});
              debug('Sent open to out queue %s: in queue is %s', outQ, inQ);
              current = writableQueue(ch, outQ);
              current.on('finish', function() {
                latch = latch('out');
              });
              setup();
              stdin.pipe(current, {end: true});
              break;
            default:
              console.warn('Something other than open, %s ',
                           msg.properties.type,
                           'received on handshake queue');
            }
          }, {noAck: false});

          var streams = new QueueStreamServer(ch, inQ);
          streams.on('connection', function(stream) {
            stream.pipe(stdout, {end: !argv.k});
            stream.on('end', function() {
              latch = latch('in');
            });
          });
        });
      });
    }

    else { // act as client
      var latch = closeLatch(function() {
        ch.close();
      });

      ch.assertQueue('', {exclusive: true}).then(function(ok) {
        var outQ = ok.queue;
        debug('Created out queue %s', outQ);

        setup();

        var readable = readableQueue(ch, outQ, function(inQ) {
          var writable = writableQueue(ch, inQ);
          stdin.pipe(writable, {end: true});
          writable.on('finish', function() {
            latch = latch('out');
          });

          // The special case for closing client streams: if we're
          // accepting input on stdin, treat the server closing as us
          // closing.
          if (stdin === process.stdin) {
            readable.on('end', function() {
              writable.end();
            });
          }
        });
        readable.on('end', function() {
          latch = latch('in');
        });

        readable.pipe(stdout);
        ch.sendToQueue(handshakeQ, new Buffer(0),
                       {type: 'open', replyTo: outQ});
        debug('Sent open to handshake queue %s', handshakeQ);
      });
    }
  });
}, console.warn);

// Create a writable stream that sends chunks to a queue.
function writableQueue(channel, queue) {
  var writable = new Writable();
  writable._write = function(chunk, _enc, cb) {
    debug('Sent %d bytes to %s', chunk.length, queue);
    if (channel.sendToQueue(queue, chunk, {type: 'data'})) {
      cb();
    }
    else channel.once('drain', cb);
  };
  writable.on('finish', function() {
    channel.sendToQueue(queue, new Buffer(0), {type: 'eof'});
    debug('Sent eof to %s', queue);
  });
  return writable;
}

// Create a readable stream that gets chunks from a stream.
function readableQueue(channel, queue, openCb) {
  var readable = new Readable();
  readable._read = function() {};

  // Logically, we want to receive everything up to EOF and no
  // more. However, in practice there's no way to switch the tap off
  // at an exact message; instead, we will overrun slightly, so we
  // need to put those extra messages back in the queue by nacking
  // them.
  var running = true;

  var ok = channel.consume(queue, function(msg) {
    if (!running) {
      channel.nack(msg);
      return;
    }

    switch (msg && msg.properties.type) {
    case null: // cancelled by server
      readable.emit('error', new Error('Consume cancelled by server'));
      break;
    case 'eof':
      running = false;
      // Don't trigger anything (e.g., closing the channel) until
      // we've cancelled. We may get messages in the meantime, which
      // is why the nack and early return above.
      ok.then(function(consumeOk) {
        channel.cancel(consumeOk.consumerTag);
        readable.push(null);
      });
      break;
    case 'data':
      debug('Recv %d bytes', msg.content.length);
      readable.push(msg.content);
      break;
    case 'open':
      var inQ = msg.properties.replyTo;
      debug('Recv open: in queue is %s', inQ);
      openCb(inQ);
      break;
    default:
      console.warn('Unknown message type %s', msg.properties.type);
    }
    channel.ack(msg);
  }, {exclusive: true, noAck: false});

  return readable;
}

function QueueStreamServer(channel, queue) {
  EventEmitter.call(this);

  var self = this;
  var current = null;

  channel.consume(queue, function(msg) {
    if (current === null) {
      current = new Readable();
      current._read = function() {};
      self.emit('connection', current);
    }

    switch (msg && msg.properties.type) {
    case null: // consume has been cancelled
      debug('Consume cancelled (%s)', queue);
      setImmediate(function() {
        self.emit('error', new Error('In queue deleted'))});
      // fall-through
    case 'eof':
      debug('Recv eof on in queue %s', queue);
      current.push(null);
      current = null;
      break;
    case 'data':
      debug('Recv %d bytes on %s', msg.content.length, queue);
      current.push(msg.content);
      break;
    default:
      console.warn('Unknown message type %s', msg.properties.type);
    }
  }, {exclusive: true, noAck: true});
}
inherits(QueueStreamServer, EventEmitter);
<commit_msg>Move towards listen/connect symmetry<commit_after>#!/usr/bin/env node
var amqp = require('amqplib');
var Readable = require('stream').Readable
  || require('readable-stream/readable');
var Writable = require('stream').Writable
  || require('readable-stream/writable');
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var spawn = require('child_process').spawn;

var options = require('yargs')
  .example('$0 < foobar.txt', 'Send a file')
  .example('$0 -lk', 'Listen for connections and output data to stdout')

  .options('url', {
    'default': 'amqp://localhost',
    describe:'Connect to the RabbitMQ at <url>'})
  .options('service', {
    'default': 'rmqcat',
    describe: 'Use the service at <queue>'})
  .options('help', {
    describe: 'Print help and exit'})

  .options('send', {
    describe: 'Send directly to <queue>'})
  .options('recv', {
    describe: 'Receive directly from <queue>'})

  .options('exec', {
    describe: 'Spawn a process and use stdin and stdout from that process',
    alias: 'e'
  })

  .describe('l', 'Listen for connections')
  .describe('k', 'Keep listening after client disconnections')
  .describe('D', 'Output debug information to stderr')
  .boolean(['l', 'k', 'D']);

var argv = options.argv;

if (argv.help) {
  options.showHelp();
  process.exit(0);
}

var debug = (argv.D) ? console.warn : function() {};

var url = argv.url;

// I use three different (kinds of) queues: there is the handshake
// queue, which is the common knowledge between the client and the
// server (the "connection point", like a port); then there is an in
// queue and an out queue, both named from the point of view of the
// process.

// The stdin queue is that over which the remote sends data to the
// this process, and the out queue is that over which the process
// sends data to the remote. This process creates the in queue and
// annouces it to the remote (as the 'replyTo' of an empty message);
// the remote creates the out queue and announces it to this process.

// The difference between a client and a server (listener) is that the
// client sends an open message to the handshake queue, and the
// listener responds with an open message to the client's in queue
// (which becomes the server's out queue).

var handshakeQ = argv.service;

function closeLatch(done) {
  function either(which) {
    switch (which) {
    case 'in':
      return only('out');
    case 'out':
      return only('in');
    default:
      throw new Error('Unknown stream ' + which);
    }
  }
  function only(s) {
    return function(which) {
      switch (which) {
      case s:
        done();
        return neither;
      default:
        throw new Error('Close on stream other than expected ' + s);
      }
    }
  }
  function neither() { throw new Error('Both closed'); }
  return either;
}

var ok = amqp.connect(url);
ok.then(function(connection) {

  return connection.createChannel().then(function(ch) {

    // It's convenient, since most of the work goes through the
    // channel, to use its closure as a signal to clean up and leave.
    ch.on('close', function() {
      connection.close().then(function() {
        process.exit(0);
      });
    });

    // send and recv don't use a service (handshake) queue, they just
    // send to or receive from the queue mentioned.
    if (argv.send || argv.recv) {

      if (argv.send) {
        var dest = argv.send;
        ch.assertQueue(dest);
        var out = writableQueue(ch, dest);
        process.stdin.pipe(out);
        out.on('finish', function() {
          ch.close();
        });
      }

      else if (argv.recv) {
        var source = argv.recv;
        ch.assertQueue(source);
        var reader = readableQueue(ch, source);
        reader.on('end', function() {
          ch.close();
        });
        reader.pipe(process.stdout);
      }

      return; // no more options matter
    }

    // Make sure the handshake queue exists, since we don't know who
    // will turn up first
    debug('Asserting handshake queue: %s', handshakeQ);
    ch.assertQueue(handshakeQ);

    var stdin;
    var stdout;

    function setup() {
      if (argv.e) {
        debug('Starting process %s', argv.e);
        var args = argv.e.split(' ');
        var child = spawn(args[0], args.slice(1));
        stdin = child.stdout;
        stdout = child.stdin;
      }
      else {
        stdin = process.stdin;
        stdout = process.stdout;
      }
    }

    if (argv.l) { // act as server
      return ch.assertQueue('', {exclusive: true}).then(function(ok) {
        var inQ = ok.queue;
        debug('Created in queue: %s', inQ);

        // I need a channel on which to accept connections. Why
        // another? Because this one only deals with one connection at
        // a time (so it can rewire stdin and stdout appropriately),
        // and to do that, I must use prefetch=1 so I don't get sent
        // all connection requests at once.
        connection.createChannel().then(function(acceptCh) {
          var accepted = null;
          var writable = null;

          function next() {
            stdin.unpipe();
            acceptCh.ack(accepted);
          }

          var latch;
          if (argv.k) {
            function freshLatch() {
              return closeLatch(function() {
                latch = freshLatch();
                next();
              });
            }
            latch = freshLatch();
          }
          else {
            latch = closeLatch(function() {
              next();
              ch.close();
            });
          }

          acceptCh.prefetch(1);
          // Any returned messages are a result of the 'open' not
          // being routed; this is interpreted as the connection
          // having disappeared (possibly before we even turned up)
          acceptCh.on('return', function(returned) {
            debug('open message returned from %s',
                  returned.fields.routingKey,
                  ' assuming dead connection');
            next();
          });

          acceptCh.consume(handshakeQ, function(msg) {
            switch (msg.properties.type) {
            case 'open':
              accepted = msg;
              var outQ = msg.properties.replyTo;
              debug('Recv open: out queue is %s', outQ);
              acceptCh.sendToQueue(outQ, new Buffer(0),
                                   {type: 'open',
                                    mandatory: true,
                                    replyTo: inQ});
              debug('Sent open to out queue %s: in queue is %s', outQ, inQ);
              writable = writableQueue(ch, outQ);
              writable.on('finish', function() {
                latch = latch('out');
              });
              setup();
              stdin.pipe(writable, {end: true});
              break;
            default:
              console.warn('Something other than open, %s ',
                           msg.properties.type,
                           'received on handshake queue');
            }
          }, {noAck: false});

          var streams = new QueueStreamServer(ch, inQ);
          streams.on('connection', function(readable) {
            readable.pipe(stdout, {end: !argv.k});
            readable.on('end', function() {
              latch = latch('in');
            });

            // The special case for closing client streams: if we're
            // accepting input on stdin, treat the server closing as us
            // closing.
            if (stdin === process.stdin) {
              readable.on('end', function() {
                writable.end();
              });
            }
          });
        });
      });
    }

    else { // act as client
      var latch = closeLatch(function() {
        ch.close();
      });

      ch.assertQueue('', {exclusive: true}).then(function(ok) {
        var outQ = ok.queue;
        debug('Created out queue %s', outQ);

        setup();

        var readable = readableQueue(ch, outQ, function(inQ) {
          var writable = writableQueue(ch, inQ);
          stdin.pipe(writable, {end: true});
          writable.on('finish', function() {
            latch = latch('out');
          });

          // The special case for closing client streams: if we're
          // accepting input on stdin, treat the server closing as us
          // closing.
          if (stdin === process.stdin) {
            readable.on('end', function() {
              writable.end();
            });
          }
        });
        readable.on('end', function() {
          latch = latch('in');
        });

        readable.pipe(stdout);
        ch.sendToQueue(handshakeQ, new Buffer(0),
                       {type: 'open', replyTo: outQ});
        debug('Sent open to handshake queue %s', handshakeQ);
      });
    }
  });
}, console.warn);

// Create a writable stream that sends chunks to a queue.
function writableQueue(channel, queue) {
  var writable = new Writable();
  writable._write = function(chunk, _enc, cb) {
    debug('Sent %d bytes to %s', chunk.length, queue);
    if (channel.sendToQueue(queue, chunk, {type: 'data'})) {
      cb();
    }
    else channel.once('drain', cb);
  };
  writable.on('finish', function() {
    channel.sendToQueue(queue, new Buffer(0), {type: 'eof'});
    debug('Sent eof to %s', queue);
  });
  return writable;
}

// Create a readable stream that gets chunks from a stream.
function readableQueue(channel, queue, openCb) {
  var readable = new Readable();
  readable._read = function() {};

  // Logically, we want to receive everything up to EOF and no
  // more. However, in practice there's no way to switch the tap off
  // at an exact message; instead, we will overrun slightly, so we
  // need to put those extra messages back in the queue by nacking
  // them.
  var running = true;

  var ok = channel.consume(queue, function(msg) {
    if (!running) {
      channel.nack(msg);
      return;
    }

    switch (msg && msg.properties.type) {
    case null: // cancelled by server
      readable.emit('error', new Error('Consume cancelled by server'));
      break;
    case 'eof':
      running = false;
      // Don't trigger anything (e.g., closing the channel) until
      // we've cancelled. We may get messages in the meantime, which
      // is why the nack and early return above.
      ok.then(function(consumeOk) {
        channel.cancel(consumeOk.consumerTag);
        readable.push(null);
      });
      break;
    case 'data':
      debug('Recv %d bytes', msg.content.length);
      readable.push(msg.content);
      break;
    case 'open':
      var inQ = msg.properties.replyTo;
      debug('Recv open: in queue is %s', inQ);
      openCb(inQ);
      break;
    default:
      console.warn('Unknown message type %s', msg.properties.type);
    }
    channel.ack(msg);
  }, {exclusive: true, noAck: false});

  return readable;
}

function QueueStreamServer(channel, queue) {
  EventEmitter.call(this);

  var self = this;
  var current = null;

  channel.consume(queue, function(msg) {
    if (current === null) {
      current = new Readable();
      current._read = function() {};
      self.emit('connection', current);
    }

    switch (msg && msg.properties.type) {
    case null: // consume has been cancelled
      debug('Consume cancelled (%s)', queue);
      setImmediate(function() {
        self.emit('error', new Error('In queue deleted'))});
      // fall-through
    case 'eof':
      debug('Recv eof on in queue %s', queue);
      current.push(null);
      current = null;
      break;
    case 'data':
      debug('Recv %d bytes on %s', msg.content.length, queue);
      current.push(msg.content);
      break;
    default:
      console.warn('Unknown message type %s', msg.properties.type);
    }
  }, {exclusive: true, noAck: true});
}
inherits(QueueStreamServer, EventEmitter);
<|endoftext|>


<commit_before>var fs     = require('fs');
var path   = require('path');
var gm     = require('gm');
var imageMagick = gm.subClass({imageMagick: true});
var colors = require('colors');
var _      = require('underscore');
var Q      = require('q');
var mkdirp = require('mkdirp');

/**
 * @var {Object} settings - default values of configuration (will be used if no commandline parameters are given)
 */
var settings = {};
settings.LOAD_CONFIGS_FROM_FILES = true;
settings.CONFIG_DATA = null; // used only if LOAD_CONFIGS_FROM_FILES is false
settings.CONFIG_FILE = 'config.json';
settings.CONFIG_LOCAL_FILE = 'config-local.json';
settings.TAGS = ['all'];

/**
 * @var {Object} console utils
 */
var display = {};
display.success = function (str) {
    str = 'OK  '.green + str;
    console.log('  ' + str);
};
display.warning = function (str) {
    str = 'WARNING  '.yellow + str;
    console.log('  ' + str);
};
display.error = function (str) {
    str = 'ERROR  '.red + str;
    console.log('  ' + str);
};
display.header = function (str) {
    console.log('');
    console.log(' ' + str.cyan.underline);
    console.log('');
};

/**
 * Sets the configuration
 *
 * @param {string} configPath
 * @param {string} configLocalPath
 * @param {string} tags
 */
var configure = function( configPath, configLocalPath, tags )
{
    settings.CONFIG_FILE = (typeof configPath != "undefined" && configPath != null ) ? configPath : settings.CONFIG_LOCAL_FILE;
    settings.CONFIG_LOCAL_FILE = (typeof configLocalPath != "undefined" && configLocalPath != null ) ? configLocalPath : settings.CONFIG_LOCAL_FILE;
    settings.TAGS = (typeof tags != "undefined" && tags != null ) ? tags.split(",") : settings.CONFIG_LOCAL_FILE;
    settings.LOAD_CONFIGS_FROM_FILES = true;
}

/**
 * Sets the configuration data directly.
 *
 * @param {Object} configPath
 * @param {Object} configLocalPath
 * @param {string} tags
 */
var configureWithData = function( configData, configLocalData, tags )
{
    settings.CONFIG_DATA = _.extend( {}, configData, configLocalData || {} );
    settings.TAGS = (typeof tags != "undefined" && tags != null ) ? tags.split(",") : settings.CONFIG_LOCAL_FILE;
    settings.LOAD_CONFIGS_FROM_FILES = false;

}

/**
 * Read commandline parameters into settings.
 *
 * @param  {Array} argv
 * @param  {Object} settings
 * @return {Promise} resolves to an object{} - settings
 */
var readParameters = function (argv, settings)
{
    argv.forEach(function (val, index, array)
    {
        if( index > 1 && index%2 != 0) // index 3,5,7, ...
        {
            // --config
            if( argv[index-1] == "--config" || argv[index-1] == "-config" || argv[index-1] == "config" )
            {
                settings.CONFIG_FILE = argv[index];
            }

            // --config-local
            if( argv[index-1] == "--config-local" || argv[index-1] == "-config-local" || argv[index-1] == "config-local" )
            {
                settings.CONFIG_LOCAL_FILE = argv[index];
            }

            // --tags
            if( argv[index-1] == "--tags" || argv[index-1] == "-tags" || argv[index-1] == "tags" )
            {
                settings.TAGS = argv[index].split(",");
            }
        }
    });

    // log used settings
    console.log("  Settings: ");
    Object.keys(settings).forEach(function(key) {
        console.log( "   - " + key + ": " + settings[key] );
    });

    var deferred = Q.defer();
    deferred.resolve(settings);

    return deferred.promise;
};

/**
 * Checks if a config.json file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var configFileExists = function ()
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        console.log("\n  Loading configs: ");

        fs.exists(settings.CONFIG_FILE, function (exists) {
            if (exists) {
                display.success(settings.CONFIG_FILE + ' exists');
                deferred.resolve();
            } else {
                display.error(settings.CONFIG_FILE + ' does not exist in "'+process.cwd()+'".');
                deferred.reject();
            }
        });
    }
    else
    {
        deferred.resolve();
    }

    return deferred.promise;
};

/**
 * Checks if a config-local.json file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var configLocalFileExists = function ()
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        fs.exists(settings.CONFIG_LOCAL_FILE, function (exists) {
            if (exists) {
                display.success(settings.CONFIG_LOCAL_FILE + ' exists');
                deferred.resolve();
            } else {
                display.error(settings.CONFIG_LOCAL_FILE + ' does not exist in "'+process.cwd()+'".');
                deferred.reject();
            }
        });
    }
    else
    {
        deferred.resolve();
    }

    return deferred.promise;
};

/**
 * Read the config file.
 *
 * @return {Promise} resolves to an object{} - the content of config.json
 */
var readConfig = function ()
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        data = fs.readFile(settings.CONFIG_FILE, function (err, data) {
            if (err) {
                deferred.reject(err);
            }
            var config = JSON.parse(data);
            if(config === false || config == null)
            {
                deferred.reject("Parsing "+settings.CONFIG_FILE+" failed.");
            }
            else
            {
                display.success("Parsing "+settings.CONFIG_FILE+" succeeded.");
                deferred.resolve(config);
            }
        });
    }
    else
    {
        deferred.resolve(settings.CONFIG_DATA);
    }

    return deferred.promise;
};

/**
 * Read the config-local file.
 *
 * @param  {Object} config
 * @return {Promise} resolves to an object{} - the content of config-local.json
 */
var readConfigLocal = function (config)
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        data = fs.readFile(settings.CONFIG_LOCAL_FILE, function (err, data) {
            if (err) {
                deferred.reject(err);
            }
            var configLocal = JSON.parse(data);
            if(configLocal === false || configLocal == null)
            {
                deferred.reject("Parsing "+settings.CONFIG_LOCAL_FILE+" failed.");
            }
            else
            {
                display.success("Parsing "+settings.CONFIG_LOCAL_FILE+" succeeded.");
                deferred.resolve( _.extend( {}, config, configLocal ) );
            }
        });
    }
    else
    {
        deferred.resolve(settings.CONFIG_DATA);
    }

    return deferred.promise;
};

/**
 * Runs through all the images and resizes them.
 *
 * @param  {Object} config
 * @return {Promise}
 */
var generateImages = function (config)
{
    console.log("\n  Resizing images: ")
    var deferred = Q.defer();
    var sequence = Q();
    var all = [];
    var imagesCounter = 0;
    _(config.images).filter(function(image){ return _(image.tags.split(",")).intersection(settings.TAGS).length > 0 ? image : false; } ).forEach(function (image) {
        sequence = sequence.then(function () {
            imagesCounter++;
            return generateImage(image, config);
        });
        all.push(sequence);
    });
    Q.all(all).then(function () {
        if( imagesCounter == 0 )
        {
            console.log("  \n    No images matching the tags '"+settings.TAGS.join(",")+"' found.");
        }
        deferred.resolve();
    });
    return deferred.promise;
};

/**
 * Resizes and creates a new icon in the platform's folder.
 *
 * @param  {Object} image entry from config
 * @param  {Object} the whole config (used to extract aliases)
 * @return {Promise}
 */
var generateImage = function (image, config)
{
    var deferred = Q.defer();

    // size
    var _wh = image.resolution.split("x");
    var width = _wh[0];
    var height = _wh[1];

    var basePath = config.basePath == null ? "" : config.basePath;
    // ensure ending slash
    basePath = basePath != "" && basePath.charAt(basePath.length-1) != "/" && basePath.charAt(basePath.length-1) != "\\" ? basePath + "/" : basePath;
    basePath = path.normalize(basePath);

    // source path
    var sourcePath = basePath + image.sourcePath;
    // replace aliases
    _(config.aliases).forEach(function (alias) {
        sourcePath = sourcePath.split(alias.name).join(alias.path);
    });
    sourcePath = path.normalize(sourcePath);

    // target path
    var targetPath = basePath + image.targetPath;
    // replace aliases
    _(config.aliases).forEach(function (alias) {
        targetPath = targetPath.split(alias.name).join(alias.path);
    });
    targetPath = path.normalize(targetPath);

    makeDir(targetPath)
        .then(function(){
            fileExists(sourcePath)
                .then( function(){
                    // Create empty image file (ImageMagic sometimes writes broken png data if the file does not
                    // yet exist - I honestly don´t know why).
                    fs.closeSync(fs.openSync(targetPath, 'w'));
                    // create image
                    var newImage = imageMagick(sourcePath);
                    // apply quality
                    if( image.quality !== null )
                    {
                        newImage.quality( image.quality );
                    }
                    // apply options
                    if( typeof image.options != "undefined" && image.options !== null )
                    {
                        Object.keys(image.options).forEach(function(key) {
                            try
                            {
                                console.log( "    -option '" + key + "': '" + (image.options[key]||[]).join(",") + "'" );
                                if( typeof newImage[key] != "undefined" )
                                {
                                    newImage[key].apply(newImage, image.options[key] || []);
                                }
                            }
                            catch( e )
                            {
                                display.warning("Option '" + key + "': " + e.message);
                            }
                        });
                    }
                    // resize (proportionally or not)
                    if( image.proportional != null && (image.proportional == "true" || image.proportional === true) )
                    {
                        newImage.resize(width, height);
                    }
                    else
                    {
                        newImage.resizeExact(width, height);
                    }
                    newImage.write(targetPath, function(err) {
                        if (err)
                        {
                            display.error(err);
                            deferred.resolve();
                        } else {
                            deferred.resolve();
                            display.success(targetPath + ' ('+image.resolution+') created');
                        }
                    });
                })
                .catch(function (error) {
                    // warn the user but don´t abort execution
                    display.warning('Source image "' + sourcePath + '" does not exist.');
                    deferred.resolve();
                })
                .done();
        });

    return deferred.promise;
};

/**
 * Checks if a file exists.
 *
 * @param  {Object} filePath
 * @return {Promise}
 */
var fileExists = function (filePath)
{
    var deferred = Q.defer();

    fs.exists(filePath, function (exists)
    {
        if (exists) {
            deferred.resolve();
        } else {
            deferred.reject();
        }
    });
    return deferred.promise;
};

/**
 * Removes the filename from path and creates the directories if they don´t exist.
 * Depends on mkdirp (https://github.com/substack/node-mkdirp).
 *
 * @param  {Object} filePath
 * @return {Promise}
 */
var makeDir = function (filePath)
{
    var deferred = Q.defer();

    // get base path of target file
    filePath = path.dirname(filePath);

    mkdirp(filePath, function (error, made)
    {
        if( error == null ){
            if( made != null )
            {
                console.log("    Created new directory '"+made+"'.");
            }
            deferred.resolve();
        } else {
            display.error("    Could not create target directory '"+filePath+"'.");
            deferred.reject(error.message);
        }
    });

    return deferred.promise;
};

var run = function()
{

    display.header('Creating Images');

    return readParameters(process.argv, settings)
        .then(configFileExists)
        .then(configLocalFileExists)
        .then(readConfig)
        .then(readConfigLocal)
        .then(generateImages)
        .catch(function (err) {
            if (err) {
                console.log(err);
            }
        }).then(function () {
            console.log('');
        });
};

module.exports = {
    run: run,
    configure: configure,
    configureWithData: configureWithData
};
<commit_msg>Current working directory is now reported at start.<commit_after>var fs     = require('fs');
var path   = require('path');
var gm     = require('gm');
var imageMagick = gm.subClass({imageMagick: true});
var colors = require('colors');
var _      = require('underscore');
var Q      = require('q');
var mkdirp = require('mkdirp');

/**
 * @var {Object} settings - default values of configuration (will be used if no commandline parameters are given)
 */
var settings = {};
settings.LOAD_CONFIGS_FROM_FILES = true;
settings.CONFIG_DATA = null; // used only if LOAD_CONFIGS_FROM_FILES is false
settings.CONFIG_FILE = 'config.json';
settings.CONFIG_LOCAL_FILE = 'config-local.json';
settings.TAGS = ['all'];

/**
 * @var {Object} console utils
 */
var display = {};
display.success = function (str) {
    str = 'OK  '.green + str;
    console.log('  ' + str);
};
display.warning = function (str) {
    str = 'WARNING  '.yellow + str;
    console.log('  ' + str);
};
display.error = function (str) {
    str = 'ERROR  '.red + str;
    console.log('  ' + str);
};
display.header = function (str) {
    console.log('');
    console.log(' ' + str.cyan.underline);
    console.log('');
};

/**
 * Sets the configuration
 *
 * @param {string} configPath
 * @param {string} configLocalPath
 * @param {string} tags
 */
var configure = function( configPath, configLocalPath, tags )
{
    settings.CONFIG_FILE = (typeof configPath != "undefined" && configPath != null ) ? configPath : settings.CONFIG_LOCAL_FILE;
    settings.CONFIG_LOCAL_FILE = (typeof configLocalPath != "undefined" && configLocalPath != null ) ? configLocalPath : settings.CONFIG_LOCAL_FILE;
    settings.TAGS = (typeof tags != "undefined" && tags != null ) ? tags.split(",") : settings.CONFIG_LOCAL_FILE;
    settings.LOAD_CONFIGS_FROM_FILES = true;
}

/**
 * Sets the configuration data directly.
 *
 * @param {Object} configPath
 * @param {Object} configLocalPath
 * @param {string} tags
 */
var configureWithData = function( configData, configLocalData, tags )
{
    settings.CONFIG_DATA = _.extend( {}, configData, configLocalData || {} );
    settings.TAGS = (typeof tags != "undefined" && tags != null ) ? tags.split(",") : settings.CONFIG_LOCAL_FILE;
    settings.LOAD_CONFIGS_FROM_FILES = false;

}

/**
 * Read commandline parameters into settings.
 *
 * @param  {Array} argv
 * @param  {Object} settings
 * @return {Promise} resolves to an object{} - settings
 */
var readParameters = function (argv, settings)
{
    argv.forEach(function (val, index, array)
    {
        if( index > 1 && index%2 != 0) // index 3,5,7, ...
        {
            // --config
            if( argv[index-1] == "--config" || argv[index-1] == "-config" || argv[index-1] == "config" )
            {
                settings.CONFIG_FILE = argv[index];
            }

            // --config-local
            if( argv[index-1] == "--config-local" || argv[index-1] == "-config-local" || argv[index-1] == "config-local" )
            {
                settings.CONFIG_LOCAL_FILE = argv[index];
            }

            // --tags
            if( argv[index-1] == "--tags" || argv[index-1] == "-tags" || argv[index-1] == "tags" )
            {
                settings.TAGS = argv[index].split(",");
            }
        }
    });

    // log used settings
    console.log("  Settings: ");
    Object.keys(settings).forEach(function(key) {
        console.log( "   - " + key + ": " + settings[key] );
    });
    console.log("  Current working dir is:\n   - " + process.cwd());

    var deferred = Q.defer();
    deferred.resolve(settings);

    return deferred.promise;
};

/**
 * Checks if a config.json file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var configFileExists = function ()
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        console.log("\n  Loading configs: ");

        fs.exists(settings.CONFIG_FILE, function (exists) {
            if (exists) {
                display.success(settings.CONFIG_FILE + ' exists');
                deferred.resolve();
            } else {
                display.error(settings.CONFIG_FILE + ' does not exist in "'+process.cwd()+'".');
                deferred.reject();
            }
        });
    }
    else
    {
        deferred.resolve();
    }

    return deferred.promise;
};

/**
 * Checks if a config-local.json file exists
 *
 * @return {Promise} resolves if exists, rejects otherwise
 */
var configLocalFileExists = function ()
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        fs.exists(settings.CONFIG_LOCAL_FILE, function (exists) {
            if (exists) {
                display.success(settings.CONFIG_LOCAL_FILE + ' exists');
                deferred.resolve();
            } else {
                display.error(settings.CONFIG_LOCAL_FILE + ' does not exist in "'+process.cwd()+'".');
                deferred.reject();
            }
        });
    }
    else
    {
        deferred.resolve();
    }

    return deferred.promise;
};

/**
 * Read the config file.
 *
 * @return {Promise} resolves to an object{} - the content of config.json
 */
var readConfig = function ()
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        data = fs.readFile(settings.CONFIG_FILE, function (err, data) {
            if (err) {
                deferred.reject(err);
            }
            var config = JSON.parse(data);
            if(config === false || config == null)
            {
                deferred.reject("Parsing "+settings.CONFIG_FILE+" failed.");
            }
            else
            {
                display.success("Parsing "+settings.CONFIG_FILE+" succeeded.");
                deferred.resolve(config);
            }
        });
    }
    else
    {
        deferred.resolve(settings.CONFIG_DATA);
    }

    return deferred.promise;
};

/**
 * Read the config-local file.
 *
 * @param  {Object} config
 * @return {Promise} resolves to an object{} - the content of config-local.json
 */
var readConfigLocal = function (config)
{
    var deferred = Q.defer();
    if( settings.LOAD_CONFIGS_FROM_FILES == true )
    {
        data = fs.readFile(settings.CONFIG_LOCAL_FILE, function (err, data) {
            if (err) {
                deferred.reject(err);
            }
            var configLocal = JSON.parse(data);
            if(configLocal === false || configLocal == null)
            {
                deferred.reject("Parsing "+settings.CONFIG_LOCAL_FILE+" failed.");
            }
            else
            {
                display.success("Parsing "+settings.CONFIG_LOCAL_FILE+" succeeded.");
                deferred.resolve( _.extend( {}, config, configLocal ) );
            }
        });
    }
    else
    {
        deferred.resolve(settings.CONFIG_DATA);
    }

    return deferred.promise;
};

/**
 * Runs through all the images and resizes them.
 *
 * @param  {Object} config
 * @return {Promise}
 */
var generateImages = function (config)
{
    console.log("\n  Resizing images: ")
    var deferred = Q.defer();
    var sequence = Q();
    var all = [];
    var imagesCounter = 0;
    _(config.images).filter(function(image){ return _(image.tags.split(",")).intersection(settings.TAGS).length > 0 ? image : false; } ).forEach(function (image) {
        sequence = sequence.then(function () {
            imagesCounter++;
            return generateImage(image, config);
        });
        all.push(sequence);
    });
    Q.all(all).then(function () {
        if( imagesCounter == 0 )
        {
            console.log("  \n    No images matching the tags '"+settings.TAGS.join(",")+"' found.");
        }
        deferred.resolve();
    });
    return deferred.promise;
};

/**
 * Resizes and creates a new icon in the platform's folder.
 *
 * @param  {Object} image entry from config
 * @param  {Object} the whole config (used to extract aliases)
 * @return {Promise}
 */
var generateImage = function (image, config)
{
    var deferred = Q.defer();

    // size
    var _wh = image.resolution.split("x");
    var width = _wh[0];
    var height = _wh[1];

    var basePath = config.basePath == null ? "" : config.basePath;
    // ensure ending slash
    basePath = basePath != "" && basePath.charAt(basePath.length-1) != "/" && basePath.charAt(basePath.length-1) != "\\" ? basePath + "/" : basePath;
    basePath = path.normalize(basePath);

    // source path
    var sourcePath = basePath + image.sourcePath;
    // replace aliases
    _(config.aliases).forEach(function (alias) {
        sourcePath = sourcePath.split(alias.name).join(alias.path);
    });
    sourcePath = path.normalize(sourcePath);

    // target path
    var targetPath = basePath + image.targetPath;
    // replace aliases
    _(config.aliases).forEach(function (alias) {
        targetPath = targetPath.split(alias.name).join(alias.path);
    });
    targetPath = path.normalize(targetPath);

    makeDir(targetPath)
        .then(function(){
            fileExists(sourcePath)
                .then( function(){
                    // Create empty image file (ImageMagic sometimes writes broken png data if the file does not
                    // yet exist - I honestly don´t know why).
                    fs.closeSync(fs.openSync(targetPath, 'w'));
                    // create image
                    var newImage = imageMagick(sourcePath);
                    // apply quality
                    if( image.quality !== null )
                    {
                        newImage.quality( image.quality );
                    }
                    // apply options
                    if( typeof image.options != "undefined" && image.options !== null )
                    {
                        Object.keys(image.options).forEach(function(key) {
                            try
                            {
                                console.log( "    -option '" + key + "': '" + (image.options[key]||[]).join(",") + "'" );
                                if( typeof newImage[key] != "undefined" )
                                {
                                    newImage[key].apply(newImage, image.options[key] || []);
                                }
                            }
                            catch( e )
                            {
                                display.warning("Option '" + key + "': " + e.message);
                            }
                        });
                    }
                    // resize (proportionally or not)
                    if( image.proportional != null && (image.proportional == "true" || image.proportional === true) )
                    {
                        newImage.resize(width, height);
                    }
                    else
                    {
                        newImage.resizeExact(width, height);
                    }
                    newImage.write(targetPath, function(err) {
                        if (err)
                        {
                            display.error(err);
                            deferred.resolve();
                        } else {
                            deferred.resolve();
                            display.success(targetPath + ' ('+image.resolution+') created');
                        }
                    });
                })
                .catch(function (error) {
                    // warn the user but don´t abort execution
                    display.warning('Source image "' + sourcePath + '" does not exist.');
                    deferred.resolve();
                })
                .done();
        });

    return deferred.promise;
};

/**
 * Checks if a file exists.
 *
 * @param  {Object} filePath
 * @return {Promise}
 */
var fileExists = function (filePath)
{
    var deferred = Q.defer();

    fs.exists(filePath, function (exists)
    {
        if (exists) {
            deferred.resolve();
        } else {
            deferred.reject();
        }
    });
    return deferred.promise;
};

/**
 * Removes the filename from path and creates the directories if they don´t exist.
 * Depends on mkdirp (https://github.com/substack/node-mkdirp).
 *
 * @param  {Object} filePath
 * @return {Promise}
 */
var makeDir = function (filePath)
{
    var deferred = Q.defer();

    // get base path of target file
    filePath = path.dirname(filePath);

    mkdirp(filePath, function (error, made)
    {
        if( error == null ){
            if( made != null )
            {
                console.log("    Created new directory '"+made+"'.");
            }
            deferred.resolve();
        } else {
            display.error("    Could not create target directory '"+filePath+"'.");
            deferred.reject(error.message);
        }
    });

    return deferred.promise;
};

var run = function()
{

    display.header('Creating Images');

    return readParameters(process.argv, settings)
        .then(configFileExists)
        .then(configLocalFileExists)
        .then(readConfig)
        .then(readConfigLocal)
        .then(generateImages)
        .catch(function (err) {
            if (err) {
                console.log(err);
            }
        }).then(function () {
            console.log('');
        });
};

module.exports = {
    run: run,
    configure: configure,
    configureWithData: configureWithData
};
<|endoftext|>


<commit_before>var _ = require('underscore');
var winston = require('winston');
var mongoose = require('mongoose');
var Slack = require('slack-client');
var nconf = require('nconf');
var winston = require('winston');

function AddonSlack (app, server, io, passport){
    var self = this;
	this.name = 'Slack';
	this.description = 'Slack integration addon';
    this.listDependencies = ['slack-robot'];
    var Testcase = mongoose.model('Testcase')
    var Result = mongoose.model('Result');

    var allowAllUsers = true;
    var allowedUsers = [/jussi/, /wirkus/];

    var allowAllChannels = false;
    var allowedChannels = [/jussiva/, /wirkus/, /thread/, /6lowpan/, /clitest/];

    var cfg = nconf.get('slack');

	this.register = function(){

        if( !cfg || !cfg.token ) {
            winston.error('slack configuration missing!');
            return;
        }

        var slackOpts = {
          token: nconf.get('slack').token,
          autoReconnect: true,
          //autoMark: false
        }
        var slack = new Slack(slackOpts.token, slackOpts.autoReconnect, slackOpts.automark);

        slack
          .on('open', function(){
          channels = []
          groups = []
          unreads = slack.getUnreadCount()
          winston.info('Slack -connection open');
          // Get all the channels that bot is a member of
          /*channels = {}
          for( var id, channel in slack.channels ) {
             if( channel.is_member )
              channels[channel.id] = channel.name;
          }*/
          // Get all groups that are open and not archived
          //groups = (group.name for id, group of slack.groups when group.is_open and not group.is_archived)
        })
        .on('error', winston.error)
        .on('message', handleMessage);
        slack.login();
    }

    var allowedChannel = function(channel) {
        if( allowAllChannels ) return true;
        if( !channel || !channel.name ) return false;
        winston.log('SLACK: check if channel is valid: '+channel.name);
        return allowedChannels.some( function(ch) {
            if( channel.name.toLowerCase().match(ch) ){
                return true;
            }
        });
    }
    var allowedUser = function(user) {
        if( allowAllUsers ) return true;
        if( !user || !user.real_name ) return false;
        winston.log('SLACK: check if user is valid: '+user.real_name);
        return allowedUsers.some( function(usr) {
            if( user.real_name.toLowerCase().match(usr) ){
                return true;
            }
        });
    }

    var allowMessage = function(channel, user) {
        if( !allowedChannel(channel) ) return false;
        if( !allowedUser(user) ) return false;
        return true;
    }

    var help = function(message, channel, user, m) {
        channel.send("```Test Management Service\n:list testcases\tList all available cases\n:list results\tList 10 latest results\n:run [testcase]\t:run individual case\n:help\tThis help```");
    }
    var list = function(message, channel, user, m) {
        q = {f: 'tcid', t: 'distinct'};
        if( m=message.text.match(/^\:list\Wtestcases\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        Testcase.query(q, function(error, list){
          channel.send('```'+JSON.stringify(list, ' ', '\n')+'```');
        })
    }
    var results = function(message, channel, user, m) {
        q = {l: 10, s: '{"cre.time": -1}', q: {}};
        if( m=message.text.match(/thread/) || channel.name.match(/thread/) ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': 'thread'});
        }
        else if( m=message.text.match(/6lowpan/) || channel.name.match(/6lowpan/)  ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': '6LoWPAN'});
        }
        else if( m=message.text.match(/^\:list\Wresults\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        q.q = JSON.stringify(q.q);
        console.log('SLACK: result query: '+JSON.stringify(q));
        Result.query(q, function(error, list){
            if( error ) {
                channel.send('Error while query');
                return;
            }
            var str = 'TC\t\tResult\n';
            list.forEach( function (result){
                if( result.exec && result.exec.verdict ) {
                    str += result.tcid+'\t'+result.exec.verdict+'\n';
                }
            });
            channel.send('```'+str+'```');
        })
    }
    var run = function(message, channel, user, m) {
        channel.send('Starting execute TC: '+m[1]+'...');
    }

    var commands = [
        { m: /^\:help/, f: help },
        { m: /^\:list\Wtestcases/, f: list},
        { m: /^\:list\Wresults/, f: results },
        { m: /^\:run\W(.*)/, f: run }
    ]

    var execute = function(message, channel, user) {
        if( !message.text.match(/^\:/) ) return false;
        console.log('SLACK: check if cmd is valid: '+message.text);
        return commands.some( function(cmd) {
            m =  message.text.match(cmd.m);
            if(m) {
                cmd.f(message, channel, user, m);
                return true;
            }
        });
    }

    var handleMessage = function(message) {
        try {
            channel = slack.getChannelGroupOrDMByID(message.channel)
            user = slack.getUserByID(message.user)
            if( !allowMessage(channel, user) ) {
                winston.warn('SLACK: message not allowed');
                return;
            }
            if( !user.hasOwnProperty("real_name") ) {
                console.log(user);
                console.log(message.text);
                return;
            }
            winston.log('SLACK: '+channel.name+'|'+user.real_name+''+message.text);
            if( !execute(message, channel, user) ) {
                console('SLACK: Unknown command: '+message.text);
            }
        } catch( e ) {
            console.log(e);
            console.log(message.text);
        }
    }
    /*
    var robotOpts = {
      ignoreMessageInGeneral: true,
      //mentionToRespond: true,
      //skipDMMention: false
    }
    var robot = new Robot(slackOpts, robotOpts);

    robot.listen('hello') //:results([a-z\-]+)
      .handler(function(req, res) {
      // you can get named-param inside Request object
      console.log(req.param);
      console.log("hello received");
      res.sendText("Hi There");
      // {animal: 'sheep', year: '2010'}
      // note that req.param always returns Map<string, string>
    });

    app.get('/api/v0/slack', function(req, res){
		  res.json({ok: 1});
		});*/

  return this;
}

exports = module.exports = AddonSlack;<commit_msg>change default values and clean a little<commit_after>var _ = require('underscore');
var winston = require('winston');
var mongoose = require('mongoose');
var Slack = require('slack-client');
var nconf = require('nconf');
var winston = require('winston');

function AddonSlack (app, server, io, passport){
    var self = this;
	this.name = 'Slack';
	this.description = 'Slack integration addon';
    this.listDependencies = ['slack-robot'];
    var Testcase = mongoose.model('Testcase')
    var Result = mongoose.model('Result');

    var allowAllUsers = false;
    var allowedUsers = [/myAllowedUser/];

    var allowAllChannels = false;
    var allowedChannels = [/allowedChannel/];

    var cfg = nconf.get('slack');

	this.register = function(){

        if( !cfg || !cfg.token ) {
            winston.error('slack configuration missing!');
            return;
        }

        var slackOpts = {
          token: nconf.get('slack').token,
          autoReconnect: true,
          //autoMark: false
        }
        var slack = new Slack(slackOpts.token, slackOpts.autoReconnect, slackOpts.automark);

        slack
          .on('open', function(){
          channels = []
          groups = []
          unreads = slack.getUnreadCount()
          winston.info('Slack -connection open');
          // Get all the channels that bot is a member of
          /*channels = {}
          for( var id, channel in slack.channels ) {
             if( channel.is_member )
              channels[channel.id] = channel.name;
          }*/
          // Get all groups that are open and not archived
          //groups = (group.name for id, group of slack.groups when group.is_open and not group.is_archived)
        })
        .on('error', winston.error)
        .on('message', handleMessage);
        slack.login();
    }

    var allowedChannel = function(channel) {
        if( allowAllChannels ) return true;
        if( !channel || !channel.name ) return false;
        winston.log('SLACK: check if channel is valid: '+channel.name);
        return allowedChannels.some( function(ch) {
            if( channel.name.toLowerCase().match(ch) ){
                return true;
            }
        });
    }
    var allowedUser = function(user) {
        if( allowAllUsers ) return true;
        if( !user || !user.real_name ) return false;
        winston.log('SLACK: check if user is valid: '+user.real_name);
        return allowedUsers.some( function(usr) {
            if( user.real_name.toLowerCase().match(usr) ){
                return true;
            }
        });
    }

    var allowMessage = function(channel, user) {
        if( !allowedChannel(channel) ) return false;
        if( !allowedUser(user) ) return false;
        return true;
    }

    var help = function(message, channel, user, m) {
        channel.send("```Test Management Service\n:list testcases\tList all available cases\n:list results\tList 10 latest results\n:run [testcase]\t:run individual case\n:help\tThis help```");
    }
    var list = function(message, channel, user, m) {
        q = {f: 'tcid', t: 'distinct'};
        if( m=message.text.match(/^\:list\Wtestcases\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        Testcase.query(q, function(error, list){
          channel.send('```'+JSON.stringify(list, ' ', '\n')+'```');
        })
    }
    var results = function(message, channel, user, m) {
        q = {l: 10, s: '{"cre.time": -1}', q: {}};
        if( m=message.text.match(/thread/) || channel.name.match(/thread/) ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': 'thread'});
        }
        else if( m=message.text.match(/6lowpan/) || channel.name.match(/6lowpan/)  ) {
            if(!q.q['$or']) q.q['$or'] = [];
            q.q['$or'].push({'exec.sut.cut': '6LoWPAN'});
        }
        else if( m=message.text.match(/^\:list\Wresults\W(.*)/)  ) {
            try {
                _.extend(q, JSON.parse(m[1]));
            } catch(e) {
                console.log(e);
                channel.send('Invalid format');
                return;
            }
        }
        q.q = JSON.stringify(q.q);
        console.log('SLACK: result query: '+JSON.stringify(q));
        Result.query(q, function(error, list){
            if( error ) {
                channel.send('Error while query');
                return;
            }
            var str = 'TC\t\tResult\n';
            list.forEach( function (result){
                if( result.exec && result.exec.verdict ) {
                    str += result.tcid+'\t'+result.exec.verdict+'\n';
                }
            });
            channel.send('```'+str+'```');
        })
    }
    var run = function(message, channel, user, m) {
        channel.send('Starting execute TC: '+m[1]+'...');
    }

    var commands = [
        { m: /^\:help/, f: help },
        { m: /^\:list\Wtestcases/, f: list},
        { m: /^\:list\Wresults/, f: results },
        { m: /^\:run\W(.*)/, f: run }
    ]

    var execute = function(message, channel, user) {
        if( !message.text.match(/^\:/) ) return false;
        console.log('SLACK: check if cmd is valid: '+message.text);
        return commands.some( function(cmd) {
            m =  message.text.match(cmd.m);
            if(m) {
                cmd.f(message, channel, user, m);
                return true;
            }
        });
    }

    var handleMessage = function(message) {
        try {
            channel = slack.getChannelGroupOrDMByID(message.channel)
            user = slack.getUserByID(message.user)
            if( !allowMessage(channel, user) ) {
                winston.warn('SLACK: message not allowed');
                return;
            }
            if( !user.hasOwnProperty("real_name") ) {
                console.log(user);
                console.log(message.text);
                return;
            }
            winston.log('SLACK: '+channel.name+'|'+user.real_name+''+message.text);
            if( !execute(message, channel, user) ) {
                console('SLACK: Unknown command: '+message.text);
            }
        } catch( e ) {
            console.log(e);
            console.log(message.text);
        }
    }

    return this;
}

exports = module.exports = AddonSlack;<|endoftext|>


<commit_before>var http = require('http');
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({
    ws: true //Set exports to allow for websocket proxy
});
proxy.on('error', function(e) {
    console.log(e);
});
var url = require('url');
var https = require('https');

var Route = function (host, port, path) {
    path = (typeof(path) === 'undefined') ? '/' : path;
    this.host = host;
    this[path] = port;
    this.port = port;
}

exports = module.exports = {};
//Default Values
exports.httpPort = 80;
exports.httpsPort = 443;
exports.defaultPagePort = 12000;
exports.localPath = "http://localhost"
exports.httpsOptions = false;
exports._function = {};

//Variables that will be constructed.
exports.httpRoutes = {};
exports.httpsRoutes = {};

exports.newRoute = function (host, port, path, protocall){
    path = (typeof(path) === "undefined") ? "/" : path;
    switch (protocall) {
        case "http":
            if (typeof(exports.httpRoutes[host]) === 'undefined')
                exports.httpRoutes[host] = {};
            exports.httpRoutes[host][path] = (new Route(host, port, path));
            break;
        case "https":
            if (typeof(exports.httpsRoutes[host]) === 'undefined')
                exports.httpsRoutes[host] = {};
            exports.httpsRoutes[host][path] =(new Route(host, port, path));
            break;
        default:
            if (typeof(exports.httpRoutes[host]) === 'undefined')
                exports.httpRoutes[host] = {};
            if (typeof(exports.httpsRoutes[host]) === 'undefined')
                exports.httpsRoutes[host] = {};
            exports.httpRoutes[host][path] =(new Route(host, port, path));
            exports.httpsRoutes[host][path] =(new Route(host, port, path));
    }
}

exports.router = function(req, res, routes, protocall){
    protocall = (typeof(protocall) === "string") ? protocall : "web";
    var hostname = req.headers.host.split(":")[0];
    var pathname = url.parse(req.url).pathname;
    console.log(pathname);
    var end = true;
    var port = exports.defaultPagePort;
    console.log(routes[hostname]);
    if (typeof(routes[hostname]) !== "undefined") {
        if (typeof(routes[hostname][pathname]) !== "undefined")
            port = routes[hostname][pathname].port;
        else if (typeof(routes[hostname]["/"]) !== "undefined")
            port = routes[hostname]["/"].port;
    }
    proxy[protocall](req, res, {target: exports.localPath + ":"+port});
}

exports.startHttp = function (port, name, filter){
    port = (typeof(port) === 'undefined') ? exports.httsPort : port;
    name = (typeof(name) === 'undefined') ? "http_server" : name;
    if (typeof(filter) != "undefined")
        exports._function[name] = filter;

    exports[name] = http.createServer(function(req, res) {
        if (typeof(exports._function[name]) === "function")
            exports._function[name](req, res);
        exports.router(req, res, exports.httpRoutes)
    }).listen(port, function(){
        console.log("Http server started on port "+port);
    })
    exports[name].on("upgrade", function(req, res) {
        exports.router(req, res, exports.httpRoutes, "ws")
    })
}
exports.startHttps = function (option, port, name, filter) {
    options = (typeof(options) === 'undefined') ? exports.httpsOptions : options;
    port = (typeof(port) === 'undefined') ? exports.httpsPort : port;
    name = (typeof(name) === 'undefined') ? "https_server" : name;
    if (typeof(filter) != "undefined")
        exports._function[name] = filter;
    console.log("https started on: "+port);
    if (options === false) {
        console.log("You must declare a key & cert to start the https server");
        return false;
    }
    exports[name] = https.createServer(options, function(req, res) {
        if (typeof(exports._function[name]) === "function")
            exports._function[name]();
        exports.router(req, res, exports.httpsRoutes);
    }).listen(port, function(){
        console.log("Https server started on port "+port);
    })
    exports[name].on('upgrade', function(req, res){
        exports.router(req, res, exports.httpsRoutes);
    })
}

exports.startDefault = function () {
    var app2 = require('express')();
    var http2 = require('http').Server(app2);
    app2.get('/', function(req, res){
      var hostname = req.headers.host.split(":")[0];
      res.send('
Hello world from '+hostname+'
');
    });
    http2.listen(exports.defaultPagePort, function(){
      console.log('Started default page at: '+exports.defaultPagePort);
    });
}
exports.start = function (){
    exports.startHttp();
    exports.startHttps();
    exports.startDefault();
}<commit_msg>testing routes<commit_after>var http = require('http');
var httpProxy = require('http-proxy');
var proxy = httpProxy.createProxyServer({
    ws: true //Set exports to allow for websocket proxy
});
proxy.on('error', function(e) {
    console.log(e);
});
var url = require('url');
var https = require('https');

var Route = function (host, port, path) {
    path = (typeof(path) === 'undefined') ? '/' : path;
    this.host = host;
    this[path] = port;
    this.port = port;
}

exports = module.exports = {};
//Default Values
exports.httpPort = 80;
exports.httpsPort = 443;
exports.defaultPagePort = 12000;
exports.localPath = "http://localhost"
exports.httpsOptions = false;
exports._function = {};

//Variables that will be constructed.
exports.httpRoutes = {};
exports.httpsRoutes = {};

exports.newRoute = function (host, port, path, protocall){
    path = (typeof(path) === "undefined") ? "/" : path;
    switch (protocall) {
        case "http":
            if (typeof(exports.httpRoutes[host]) === 'undefined')
                exports.httpRoutes[host] = {};
            exports.httpRoutes[host][path] = (new Route(host, port, path));
            break;
        case "https":
            if (typeof(exports.httpsRoutes[host]) === 'undefined')
                exports.httpsRoutes[host] = {};
            exports.httpsRoutes[host][path] =(new Route(host, port, path));
            break;
        default:
            if (typeof(exports.httpRoutes[host]) === 'undefined')
                exports.httpRoutes[host] = {};
            if (typeof(exports.httpsRoutes[host]) === 'undefined')
                exports.httpsRoutes[host] = {};
            exports.httpRoutes[host][path] =(new Route(host, port, path));
            exports.httpsRoutes[host][path] =(new Route(host, port, path));
    }
}

exports.router = function(req, res, routes, protocall){
    protocall = (typeof(protocall) === "string") ? protocall : "web";
    var hostname = req.headers.host.split(":")[0];
    var pathname = url.parse(req.url).pathname;
    console.log(pathname);
    var end = true;
    var port = exports.defaultPagePort;
    console.log(routes[hostname]);
    if (typeof(routes[hostname]) !== "undefined") {
        if (typeof(routes[hostname][pathname]) !== "undefined")
            port = routes[hostname][pathname].port;
        else if (typeof(routes[hostname]["/"]) !== "undefined")
            port = routes[hostname]["/"].port;
    }
    console.log(port);
    proxy[protocall](req, res, {target: exports.localPath + ":"+port});
}

exports.startHttp = function (port, name, filter){
    port = (typeof(port) === 'undefined') ? exports.httsPort : port;
    name = (typeof(name) === 'undefined') ? "http_server" : name;
    if (typeof(filter) != "undefined")
        exports._function[name] = filter;

    exports[name] = http.createServer(function(req, res) {
        if (typeof(exports._function[name]) === "function")
            exports._function[name](req, res);
        exports.router(req, res, exports.httpRoutes)
    }).listen(port, function(){
        console.log("Http server started on port "+port);
    })
    exports[name].on("upgrade", function(req, res) {
        exports.router(req, res, exports.httpRoutes, "ws")
    })
}
exports.startHttps = function (option, port, name, filter) {
    options = (typeof(options) === 'undefined') ? exports.httpsOptions : options;
    port = (typeof(port) === 'undefined') ? exports.httpsPort : port;
    name = (typeof(name) === 'undefined') ? "https_server" : name;
    if (typeof(filter) != "undefined")
        exports._function[name] = filter;
    console.log("https started on: "+port);
    if (options === false) {
        console.log("You must declare a key & cert to start the https server");
        return false;
    }
    exports[name] = https.createServer(options, function(req, res) {
        if (typeof(exports._function[name]) === "function")
            exports._function[name]();
        exports.router(req, res, exports.httpsRoutes);
    }).listen(port, function(){
        console.log("Https server started on port "+port);
    })
    exports[name].on('upgrade', function(req, res){
        exports.router(req, res, exports.httpsRoutes);
    })
}

exports.startDefault = function () {
    var app2 = require('express')();
    var http2 = require('http').Server(app2);
    app2.get('/', function(req, res){
      var hostname = req.headers.host.split(":")[0];
      res.send('
Hello world from '+hostname+'
');
    });
    http2.listen(exports.defaultPagePort, function(){
      console.log('Started default page at: '+exports.defaultPagePort);
    });
}
exports.start = function (){
    exports.startHttp();
    exports.startHttps();
    exports.startDefault();
}<|endoftext|>


<commit_before>var d3 = require('d3')
var topology = require('./topology.js')

module.exports = draw

function draw (nodes, edges, opts) {

  var network = {}
  var color, size

  network.opts = opts
  network.graph = topology(nodes, edges)

  opts = opts || {}
  opts.width = opts.width || 900
  opts.height = opts.height || 500
  opts.element = opts.element || 'body'
  opts.charge = opts.charge || -200
  opts.linkDistance = opts.linkDistance || 100

  if (typeof opts.setup === 'function') {
    opts.setup(network.graph.nodes, network.graph.edges)
  }

  var tick = opts.tick || function (nodes, edges) {
    edges.attr('x1', function(d) { return d.source.x })
        .attr('y1', function(d) { return d.source.y })
        .attr('x2', function(d) { return d.target.x })
        .attr('y2', function(d) { return d.target.y })

    nodes.attr('cx', function(d) { return d.x })
        .attr('cy', function(d) { return d.y })
  }
  opts.tick = function() {
    tick(network.nodes, network.edges)
  }

  if (typeof opts.node_color !== 'function') {
    color = opts.node_color || 'red'
    opts.node_color = function (node) {
      return color
    }
  }

  if (typeof opts.node_size !== 'function') {
    size = opts.node_size || '10'
    opts.node_size = function (node) {
      return size
    }
  }

  network.event = network.graph.event

  network.canvas = d3.select(opts.element).append('svg')
    .attr('width', opts.width)
    .attr('height', opts.height)

  var force = d3.layout.force()
    .charge(opts.charge)
    .linkDistance(opts.linkDistance)
    .size([opts.width, opts.height])

  network.edges = network.canvas.selectAll('.edge')
    .data(network.graph.edges)
    .enter()
    .append('line')
    .attr('class', 'edge')
    .style('stroke-width', '2px')
    .call(force.drag)

  network.nodes = network.canvas.selectAll('.node')
    .data(network.graph.nodes)
    .enter()
    .append('circle')
    .attr('class', 'node')
    .attr('id', function (d) { return 'i' + d.name })
    .attr('r', opts.node_size)
    .style('fill', opts.node_color)
    .call(force.drag)

  force.nodes(network.graph.nodes)
    .links(network.graph.edges)
    .start()

  force.on('tick', opts.tick)

  network.update = function (ev) {
    network.graph.update(ev, network)

    if (typeof opts.update === 'function') {
      opts.update(network)
    }
  }

  return network
}

draw.topology = topology
<commit_msg>adds overaly and zooming<commit_after>var d3 = require('d3')
var topology = require('./topology.js')

module.exports = draw

function draw (nodes, edges, opts) {

  var network = {}
  var color, size

  network.opts = opts
  network.graph = topology(nodes, edges)

  opts = opts || {}
  opts.width = opts.width || 900
  opts.height = opts.height || 500
  opts.element = opts.element || 'body'
  opts.charge = opts.charge || -200
  opts.linkDistance = opts.linkDistance || 100

  if (typeof opts.setup === 'function') {
    opts.setup(network.graph.nodes, network.graph.edges)
  }

  var tick = opts.tick || function (nodes, edges) {
    edges.attr('x1', function(d) { return d.source.x })
        .attr('y1', function(d) { return d.source.y })
        .attr('x2', function(d) { return d.target.x })
        .attr('y2', function(d) { return d.target.y })

    nodes.attr('cx', function(d) { return d.x })
        .attr('cy', function(d) { return d.y })
  }
  opts.tick = function() {
    tick(network.nodes, network.edges)
  }

  if (typeof opts.node_color !== 'function') {
    color = opts.node_color || 'red'
    opts.node_color = function (node) {
      return color
    }
  }

  if (typeof opts.node_size !== 'function') {
    size = opts.node_size || '10'
    opts.node_size = function (node) {
      return size
    }
  }

  network.event = network.graph.event

  network.canvas = d3.select(opts.element).append('svg')
    .attr('width', opts.width)
    .attr('height', opts.height)
    .append('g')
    .call(d3.behavior.zoom().scaleExtent([1,8]).on("zoom", function() {
      network.canvas.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
    }))

  network.canvas.append('rect')
    .style('fill', 'none')
    .attr('class', 'overlay')
    .attr('width', opts.width)
    .attr('height', opts.height)

  var force = d3.layout.force()
    .charge(opts.charge)
    .linkDistance(opts.linkDistance)
    .size([opts.width, opts.height])

  network.edges = network.canvas.selectAll('.edge')
    .data(network.graph.edges)
    .enter()
    .append('line')
    .attr('class', 'edge')
    .style('stroke-width', '2px')
    .call(force.drag)

  network.nodes = network.canvas.selectAll('.node')
    .data(network.graph.nodes)
    .enter()
    .append('circle')
    .attr('class', 'node')
    .attr('id', function (d) { return 'i' + d.name })
    .attr('r', opts.node_size)
    .style('fill', opts.node_color)
    .call(force.drag)

  force.nodes(network.graph.nodes)
    .links(network.graph.edges)
    .start()

  force.on('tick', opts.tick)

  network.update = function (ev) {
    network.graph.update(ev, network)

    if (typeof opts.update === 'function') {
      opts.update(network)
    }
  }

  return network
}

draw.topology = topology
<|endoftext|>


<commit_before>'use strict';

var ChildProcess = require('child_process');

var last_unique_id = 0;

var generateUniqueId = function () {
    last_unique_id++;
    return '_' + last_unique_id;
};

/**
 * @returns {{
 *   setMaxProcesses: (function(int)),
 *   getMaxProcesses: (function(): int),
 *   getCurrentProcessCount: (function(): int),
 *   getCurrentProcesses: (function(): Array.<ChildProcess>),
 *   getCurrentQueueSize: (function(): int),
 *   removeFromQueue: (function(String): Boolean),
 *   fork: (function(...[*]): String),
 *   spawn: (function(...[*]): String),
 *   exec: (function(...[*]): String),
 *   exec1: (function(...[*]): String),
 *   execFile: (function(...[*]): String),
 *   newQueue: newQueue
 * }}
 */
var newQueue = function newQueue () {

    var MAX_PROCESSES = 5;

    var QUEUE = [];
    var MAP = {};
    var PROCESSES = [];

    var removeTerminatedProcess = function (process) {

        for (var i = 0; i < PROCESSES.length; i++) {
            if (PROCESSES[i] === process) {
                PROCESSES.splice(i, 1);
                break;
            }
        }

        setImmediate(tryToReleaseQueue);
    };

    var tryToReleaseQueue = function () {

        if (PROCESSES.length >= MAX_PROCESSES || !QUEUE.length) {
            return false;
        }

        var next = QUEUE.shift();
        delete MAP[next.id];

        var args = next.args;

        if (next.hasTerminateCallback) {
            var oldTerminateCallback;
            if (typeof args[args.length - 1] === 'function') {
                oldTerminateCallback = args[args.length - 1];
            }

            //noinspection UnnecessaryLocalVariableJS
            var terminateCallback = function () {

                removeTerminatedProcess(process);

                if (oldTerminateCallback) {
                    oldTerminateCallback.apply(this, arguments);
                }
            };

            args[oldTerminateCallback ? args.length - 1 : args.length] = terminateCallback;
        }

        var process = ChildProcess[next.func].apply(ChildProcess, args);
        PROCESSES.push(process);

        process.on('exit', function () {
            removeTerminatedProcess(process);
        });

        if (next.callback) {
            next.callback(process);
        }

        return true;
    };

    var cloneObject = function (o) {
        var clone = {};

        if (o === null || typeof o !== 'object') {
            return clone;
        }

        var keys = Object.keys(o);
        for (var i = keys.length - 1; i >= 0; i--)
        {
            clone[keys[i]] = o[keys[i]];
        }

        return clone;
    };

    var extractOnCreateFromArgs = function (args) {

        for (var i = 0, length = args.length; i < length; i++) {
            if (Array.isArray(args[i])) {
                continue;
            }
            if (typeof args[i] !== 'object') {
                continue;
            }

            var options = cloneObject(args[i]);
            var onCreate = options['onCreate'];
            delete options['onCreate'];
            args[i] = options;
            return onCreate;
        }

        return null;
    };

    //noinspection JSUnusedGlobalSymbols
    var improvedChildProcess = {

        setMaxProcesses: function setMaxProcesses (max) {
            MAX_PROCESSES = max || 5;
            tryToReleaseQueue();
            return this;
        },

        getMaxProcesses: function getMaxProcesses () {
            return MAX_PROCESSES;
        },

        getCurrentProcessCount: function getCurrentProcessCount () {
            return PROCESSES.length;
        },

        getCurrentProcesses: function getCurrentProcesses () {
            return PROCESSES.slice(0);
        },

        getCurrentQueueSize: function getCurrentQueueSize () {
            return QUEUE.length;
        },

        removeFromQueue: function (id) {

            if (MAP.hasOwnProperty(id)) {
                var item = MAP[id];
                delete MAP[id];

                var index = QUEUE.indexOf(item);
                if (index !== -1)
                    QUEUE.splice(index, 1);

                return true;
            }

            return false;
        },

        fork: function fork (modulePath /*, args, options*/) {

            var args = Array.prototype.slice.call(arguments, 0);
            var onCreate = extractOnCreateFromArgs(args);

            var task = {
                func: 'fork',
                args: args,
                callback: onCreate,
                hasTerminateCallback: false,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP[task.id] = task;

            tryToReleaseQueue();

            return task.id;
        },

        spawn: function spawn (command /*, args, options*/) {

            var args = Array.prototype.slice.call(arguments, 0);
            var onCreate = extractOnCreateFromArgs(args);

            var task = {
                func: 'spawn',
                args: args,
                callback: onCreate,
                hasTerminateCallback: false,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP[task.id] = task;

            tryToReleaseQueue();

            return task.id;
        },

        exec: function exec (command /*, options, callback*/) {

            var args = Array.prototype.slice.call(arguments, 0);
            var onCreate = extractOnCreateFromArgs(args);

            var task = {
                func: 'exec',
                args: args,
                callback: onCreate,
                hasTerminateCallback: true,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP[task.id] = task;

            tryToReleaseQueue();

            return task.id;
        },

        execFile: function execFile (file /*, args, options, callback*/) {

            var args = Array.prototype.slice.call(arguments, 0);
            var onCreate = extractOnCreateFromArgs(args);

            var task = {
                func: 'exec',
                args: args,
                callback: onCreate,
                hasTerminateCallback: true,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP[task.id] = task;

            tryToReleaseQueue();

            return task.id;
        }

    };

    improvedChildProcess.newQueue = newQueue;

    //noinspection JSValidateTypes
    return improvedChildProcess;

};

/**
 * @type {{
 *   setMaxProcesses: (function(int)),
 *   getMaxProcesses: (function(): int),
 *   getCurrentProcessCount: (function(): int),
 *   getCurrentProcesses: (function(): Array.<ChildProcess>),
 *   getCurrentQueueSize: (function(): int),
 *   removeFromQueue: (function(String): Boolean),
 *   fork: (function(...[*]): String),
 *   spawn: (function(...[*]): String),
 *   exec: (function(...[*]): String),
 *   execFile: (function(...[*]): String),
 *   newQueue: newQueue
 * }}
 */
module.exports = newQueue();<commit_msg>A bit of refactoring<commit_after>'use strict';

const ChildProcess = require('child_process');

let last_unique_id = 0;

const generateUniqueId = function () {
    last_unique_id++;
    return '_' + last_unique_id;
};

/**
 * @returns {{
 *   setMaxProcesses: (function(int)),
 *   getMaxProcesses: (function(): int),
 *   getCurrentProcessCount: (function(): int),
 *   getCurrentProcesses: (function(): Array.<ChildProcess>),
 *   getCurrentQueueSize: (function(): int),
 *   removeFromQueue: (function(String): Boolean),
 *   fork: (function(...[*]): String),
 *   spawn: (function(...[*]): String),
 *   exec: (function(...[*]): String),
 *   exec1: (function(...[*]): String),
 *   execFile: (function(...[*]): String),
 *   newQueue: newQueue
 * }}
 */
const newQueue = function newQueue() {

    let MAX_PROCESSES = 5;

    let QUEUE = [];
    let MAP = new Map();
    let PROCESSES = [];

    let tryToReleaseQueue = function () {

        if (PROCESSES.length >= MAX_PROCESSES || !QUEUE.length) {
            return false;
        }

        let next = QUEUE.shift();
        MAP.delete(next.id);

        let args = next.args;
        let process;

        if (next.hasTerminateCallback) {
            let oldTerminateCallback;
            if (typeof args[args.length - 1] === 'function') {
                oldTerminateCallback = args[args.length - 1];
            }

            //noinspection UnnecessaryLocalVariableJS
            let terminateCallback = function () {

                removeTerminatedProcess(process);

                if (oldTerminateCallback) {
                    oldTerminateCallback.apply(this, arguments);
                }
            };

            args[oldTerminateCallback ? args.length - 1 : args.length] = terminateCallback;
        }

        process = ChildProcess[next.func].apply(ChildProcess, args);
        PROCESSES.push(process);

        process.on('exit', function () {
            removeTerminatedProcess(process);
        });

        if (next.callback) {
            next.callback(process);
        }

        return true;
    };

    let removeTerminatedProcess = function (process) {

        for (let i = 0; i < PROCESSES.length; i++) {
            if (PROCESSES[i] === process) {
                PROCESSES.splice(i, 1);
                break;
            }
        }

        setImmediate(tryToReleaseQueue);
    };

    let cloneObject = function (o) {
        let clone = {};

        if (o === null || typeof o !== 'object') {
            return clone;
        }

        let keys = Object.keys(o);
        for (let i = keys.length - 1; i >= 0; i--) {
            clone[keys[i]] = o[keys[i]];
        }

        return clone;
    };

    let extractOnCreateFromArgs = function (args) {

        let i = 0;
        let length = args.length;
        for (; i < length; i++) {
            if (Array.isArray(args[i])) {
                continue;
            }
            if (typeof args[i] !== 'object') {
                continue;
            }

            let options = cloneObject(args[i]);
            let onCreate = options['onCreate'];
            delete options['onCreate'];
            args[i] = options;
            return onCreate;
        }

        return null;
    };

    //noinspection JSUnusedGlobalSymbols
    let improvedChildProcess = {

        setMaxProcesses: function setMaxProcesses(max) {
            MAX_PROCESSES = max || 5;
            tryToReleaseQueue();
            return this;
        },

        getMaxProcesses: function getMaxProcesses() {
            return MAX_PROCESSES;
        },

        getCurrentProcessCount: function getCurrentProcessCount() {
            return PROCESSES.length;
        },

        getCurrentProcesses: function getCurrentProcesses() {
            return PROCESSES.slice(0);
        },

        getCurrentQueueSize: function getCurrentQueueSize() {
            return QUEUE.length;
        },

        removeFromQueue: function (id) {

            if (MAP.has(id)) {
                let item = MAP.get(id);
                MAP.delete(id);

                let index = QUEUE.indexOf(item);
                if (index !== -1)
                    QUEUE.splice(index, 1);

                return true;
            }

            return false;
        },

        fork: function fork(modulePath /*, args, options*/) {

            let args = Array.prototype.slice.call(arguments, 0);
            let onCreate = extractOnCreateFromArgs(args);

            let task = {
                func: 'fork',
                args: args,
                callback: onCreate,
                hasTerminateCallback: false,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP.set(task.id, task);

            tryToReleaseQueue();

            return task.id;
        },

        spawn: function spawn(command /*, args, options*/) {

            let args = Array.prototype.slice.call(arguments, 0);
            let onCreate = extractOnCreateFromArgs(args);

            let task = {
                func: 'spawn',
                args: args,
                callback: onCreate,
                hasTerminateCallback: false,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP.set(task.id, task);

            tryToReleaseQueue();

            return task.id;
        },

        exec: function exec(command /*, options, callback*/) {

            let args = Array.prototype.slice.call(arguments, 0);
            let onCreate = extractOnCreateFromArgs(args);

            let task = {
                func: 'exec',
                args: args,
                callback: onCreate,
                hasTerminateCallback: true,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP.set(task.id, task);

            tryToReleaseQueue();

            return task.id;
        },

        execFile: function execFile(file /*, args, options, callback*/) {

            let args = Array.prototype.slice.call(arguments, 0);
            let onCreate = extractOnCreateFromArgs(args);

            let task = {
                func: 'exec',
                args: args,
                callback: onCreate,
                hasTerminateCallback: true,
                id: generateUniqueId()
            };
            QUEUE.push(task);
            MAP.set(task.id, task);

            tryToReleaseQueue();

            return task.id;
        }

    };

    improvedChildProcess.newQueue = newQueue;

    //noinspection JSValidateTypes
    return improvedChildProcess;

};

/**
 * @type {{
 *   setMaxProcesses: (function(int)),
 *   getMaxProcesses: (function(): int),
 *   getCurrentProcessCount: (function(): int),
 *   getCurrentProcesses: (function(): Array.<ChildProcess>),
 *   getCurrentQueueSize: (function(): int),
 *   removeFromQueue: (function(String): Boolean),
 *   fork: (function(...[*]): String),
 *   spawn: (function(...[*]): String),
 *   exec: (function(...[*]): String),
 *   execFile: (function(...[*]): String),
 *   newQueue: newQueue
 * }}
 */
module.exports = newQueue();<|endoftext|>