<commit_before>// Ignore warning about 'new String()' and use of the Buffer constructor
/* eslint no-new-wrappers: "off",
   no-buffer-constructor: "off" */

'use strict';

var os = require('os');
var fs = require('fs');
var glob = require('glob');
var shell = require('..');

var shellMethods = Object.create(shell);

exports.extend = Object.assign;

// Check if we're running under electron
var isElectron = Boolean(process.versions.electron);

// Module globals (assume no execPath by default)
var DEFAULT_CONFIG = {
  fatal: false,
  globOptions: {},
  maxdepth: 255,
  noglob: false,
  silent: false,
  verbose: false,
  execPath: null,
  bufLength: 64 * 1024, // 64KB
};

var config = {
  reset: function () {
    Object.assign(this, DEFAULT_CONFIG);
    if (!isElectron) {
      this.execPath = process.execPath;
    }
  },
  resetForTesting: function () {
    this.reset();
    this.silent = true;
  },
};

config.reset();
exports.config = config;

// Note: commands should generally consider these as read-only values.
var state = {
  error: null,
  errorCode: 0,
  currentCmd: 'shell.js',
};
exports.state = state;

delete process.env.OLDPWD; // initially, there's no previous directory

// Reliably test if something is any sort of javascript object
function isObject(a) {
  return typeof a === 'object' && a !== null;
}
exports.isObject = isObject;

function log() {
  /* istanbul ignore next */
  if (!config.silent) {
    console.error.apply(console, arguments);
  }
}
exports.log = log;

// Converts strings to be equivalent across all platforms. Primarily responsible
// for making sure we use '/' instead of '\' as path separators, but this may be
// expanded in the future if necessary
function convertErrorOutput(msg) {
  if (typeof msg !== 'string') {
    throw new TypeError('input must be a string');
  }
  return msg.replace(/\\/g, '/');
}
exports.convertErrorOutput = convertErrorOutput;

// An exception class to help propagate command errors (e.g., non-zero exit
// status) up to the top-level. {@param value} should be a ShellString.
function CommandError(value) {
  this.returnValue = value;
}
CommandError.prototype = Object.create(Error.prototype);
CommandError.prototype.constructor = CommandError;
exports.CommandError = CommandError; // visible for testing

// Shows error message. Throws if fatal is true (defaults to config.fatal, overridable with options.fatal)
function error(msg, _code, options) {
  // Validate input
  if (typeof msg !== 'string') throw new Error('msg must be a string');

  var DEFAULT_OPTIONS = {
    continue: false,
    code: 1,
    prefix: state.currentCmd + ': ',
    silent: false,
    fatal: config.fatal,
  };

  if (typeof _code === 'number' && isObject(options)) {
    options.code = _code;
  } else if (isObject(_code)) { // no 'code'
    options = _code;
  } else if (typeof _code === 'number') { // no 'options'
    options = { code: _code };
  } else if (typeof _code !== 'number') { // only 'msg'
    options = {};
  }
  options = Object.assign({}, DEFAULT_OPTIONS, options);

  if (!state.errorCode) state.errorCode = options.code;

  var logEntry = convertErrorOutput(options.prefix + msg);
  state.error = state.error ? state.error + '\n' : '';
  state.error += logEntry;

  // Throw an error, or log the entry
  if (options.fatal) throw new Error(logEntry);
  if (msg.length > 0 && !options.silent) log(logEntry);

  if (!options.continue) {
    throw new CommandError(new ShellString('', state.error, state.errorCode));
  }
}
exports.error = error;

//@
//@ ### ShellString(str)
//@
//@ Examples:
//@
//@ ```javascript
//@ var foo = new ShellString('hello world');
//@ ```
//@
//@ This is a dedicated type returned by most ShellJS methods, which wraps a
//@ string (or array) value. This has all the string (or array) methods, but
//@ also exposes extra methods: [`.to()`](#shellstringprototypetofile),
//@ [`.toEnd()`](#shellstringprototypetoendfile), and all the pipe-able methods
//@ (ex. `.cat()`, `.grep()`, etc.). This can be easily converted into a string
//@ by calling `.toString()`.
//@
//@ This type also exposes the corresponding command's stdout, stderr, and
//@ return status code via the `.stdout` (string), `.stderr` (string), and
//@ `.code` (number) properties respectively.
function ShellString(stdout, stderr, code) {
  var that;
  if (stdout instanceof Array) {
    that = stdout;
    that.stdout = stdout.join('\n');
    if (stdout.length > 0) that.stdout += '\n';
  } else {
    that = new String(stdout);
    that.stdout = stdout;
  }
  that.stderr = stderr;
  that.code = code;
  // A list of all commands that can appear on the right-hand side of a pipe
  // (populated by calls to common.wrap())
  pipeMethods.forEach(function (cmd) {
    that[cmd] = shellMethods[cmd].bind(that);
  });
  return that;
}

exports.ShellString = ShellString;

// Returns {'alice': true, 'bob': false} when passed a string and dictionary as follows:
//   parseOptions('-a', {'a':'alice', 'b':'bob'});
// Returns {'reference': 'string-value', 'bob': false} when passed two dictionaries of the form:
//   parseOptions({'-r': 'string-value'}, {'r':'reference', 'b':'bob'});
// Throws an error when passed a string that does not start with '-':
//   parseOptions('a', {'a':'alice'}); // throws
function parseOptions(opt, map, errorOptions) {
  errorOptions = errorOptions || {};
  // Validate input
  if (typeof opt !== 'string' && !isObject(opt)) {
    throw new TypeError('options must be strings or key-value pairs');
  } else if (!isObject(map)) {
    throw new TypeError('parseOptions() internal error: map must be an object');
  } else if (!isObject(errorOptions)) {
    throw new TypeError(
        'parseOptions() internal error: errorOptions must be object'
    );
  }

  if (opt === '--') {
    // This means there are no options.
    return {};
  }

  // All options are false by default
  var options = {};
  Object.keys(map).forEach(function (letter) {
    var optName = map[letter];
    if (optName[0] !== '!') {
      options[optName] = false;
    }
  });

  if (opt === '') return options; // defaults

  if (typeof opt === 'string') {
    if (opt[0] !== '-') {
      throw new Error("Options string must start with a '-'");
    }

    // e.g. chars = ['R', 'f']
    var chars = opt.slice(1).split('');

    chars.forEach(function (c) {
      if (c in map) {
        var optionName = map[c];
        if (optionName[0] === '!') {
          options[optionName.slice(1)] = false;
        } else {
          options[optionName] = true;
        }
      } else {
        error('option not recognized: ' + c, errorOptions);
      }
    });
  } else { // opt is an Object
    Object.keys(opt).forEach(function (key) {
      if (key[0] === '-') {
        // key is a string of the form '-r', '-d', etc.
        var c = key[1];
        if (c in map) {
          var optionName = map[c];
          options[optionName] = opt[key]; // assign the given value
        } else {
          error('option not recognized: ' + c, errorOptions);
        }
      } else if (key in options) {
        // key is a "long option", so it should be the same
        options[key] = opt[key];
      } else {
        error('option not recognized: {' + key + ':...}', errorOptions);
      }
    });
  }
  return options;
}
exports.parseOptions = parseOptions;

// Expands wildcards with matching (ie. existing) file names.
// For example:
//   expand(['file*.js']) = ['file1.js', 'file2.js', ...]
//   (if the files 'file1.js', 'file2.js', etc, exist in the current dir)
function expand(list) {
  if (!Array.isArray(list)) {
    throw new TypeError('must be an array');
  }
  var expanded = [];
  list.forEach(function (listEl) {
    // Don't expand non-strings
    if (typeof listEl !== 'string') {
      expanded.push(listEl);
    } else {
      var ret;
      try {
        ret = glob.sync(listEl, config.globOptions);
        // if nothing matched, interpret the string literally
        ret = ret.length > 0 ? ret : [listEl];
      } catch (e) {
        // if glob fails, interpret the string literally
        ret = [listEl];
      }
      expanded = expanded.concat(ret);
    }
  });
  return expanded;
}
exports.expand = expand;

// Normalizes Buffer creation, using Buffer.alloc if possible.
// Also provides a good default buffer length for most use cases.
var buffer = typeof Buffer.alloc === 'function' ?
  function (len) {
    return Buffer.alloc(len || config.bufLength);
  } :
  function (len) {
    return new Buffer(len || config.bufLength);
  };
exports.buffer = buffer;

// Normalizes _unlinkSync() across platforms to match Unix behavior, i.e.
// file can be unlinked even if it's read-only, see https://github.com/joyent/node/issues/3006
function unlinkSync(file) {
  try {
    fs.unlinkSync(file);
  } catch (e) {
    // Try to override file permission
    /* istanbul ignore next */
    if (e.code === 'EPERM') {
      fs.chmodSync(file, '0666');
      fs.unlinkSync(file);
    } else {
      throw e;
    }
  }
}
exports.unlinkSync = unlinkSync;

// wrappers around common.statFollowLinks and common.statNoFollowLinks that clarify intent
// and improve readability
function statFollowLinks() {
  return fs.statSync.apply(fs, arguments);
}
exports.statFollowLinks = statFollowLinks;

function statNoFollowLinks() {
  return fs.lstatSync.apply(fs, arguments);
}
exports.statNoFollowLinks = statNoFollowLinks;

// e.g. 'shelljs_a5f185d0443ca...'
function randomFileName() {
  function randomHash(count) {
    if (count === 1) {
      return parseInt(16 * Math.random(), 10).toString(16);
    }
    var hash = '';
    for (var i = 0; i < count; i++) {
      hash += randomHash(1);
    }
    return hash;
  }

  return 'shelljs_' + randomHash(20);
}
exports.randomFileName = randomFileName;

// Common wrapper for all Unix-like commands that performs glob expansion,
// command-logging, and other nice things
function wrap(cmd, fn, options) {
  options = options || {};
  return function () {
    var retValue = null;

    state.currentCmd = cmd;
    state.error = null;
    state.errorCode = 0;

    try {
      var args = [].slice.call(arguments, 0);

      // Log the command to stderr, if appropriate
      if (config.verbose) {
        console.error.apply(console, [cmd].concat(args));
      }

      // If this is coming from a pipe, let's set the pipedValue (otherwise, set
      // it to the empty string)
      state.pipedValue = (this && typeof this.stdout === 'string') ? this.stdout : '';

      if (options.unix === false) { // this branch is for exec()
        retValue = fn.apply(this, args);
      } else { // and this branch is for everything else
        if (isObject(args[0]) && args[0].constructor.name === 'Object') {
          // a no-op, allowing the syntax `touch({'-r': file}, ...)`
        } else if (args.length === 0 || typeof args[0] !== 'string' || args[0].length <= 1 || args[0][0] !== '-') {
          args.unshift(''); // only add dummy option if '-option' not already present
        }

        // flatten out arrays that are arguments, to make the syntax:
        //    `cp([file1, file2, file3], dest);`
        // equivalent to:
        //    `cp(file1, file2, file3, dest);`
        args = args.reduce(function (accum, cur) {
          if (Array.isArray(cur)) {
            return accum.concat(cur);
          }
          accum.push(cur);
          return accum;
        }, []);

        // Convert ShellStrings (basically just String objects) to regular strings
        args = args.map(function (arg) {
          if (isObject(arg) && arg.constructor.name === 'String') {
            return arg.toString();
          }
          return arg;
        });

        // Expand the '~' if appropriate
        var homeDir = os.homedir();
        args = args.map(function (arg) {
          if (typeof arg === 'string' && arg.slice(0, 2) === '~/' || arg === '~') {
            return arg.replace(/^~/, homeDir);
          }
          return arg;
        });

        // Perform glob-expansion on all arguments after globStart, but preserve
        // the arguments before it (like regexes for sed and grep)
        if (!config.noglob && options.allowGlobbing === true) {
          args = args.slice(0, options.globStart).concat(expand(args.slice(options.globStart)));
        }

        try {
          // parse options if options are provided
          if (isObject(options.cmdOptions)) {
            args[0] = parseOptions(args[0], options.cmdOptions);
          }

          retValue = fn.apply(this, args);
        } catch (e) {
          /* istanbul ignore else */
          if (e instanceof CommandError) {
            retValue = e.returnValue;
          } else {
            throw e; // this is probably a bug that should be thrown up the call stack
          }
        }
      }
    } catch (e) {
      /* istanbul ignore next */
      if (!state.error) {
        // If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
        e.name = 'ShellJSInternalError';
        throw e;
      }
      if (config.fatal || options.handlesFatalDynamically) throw e;
    }

    if (options.wrapOutput &&
        (typeof retValue === 'string' || Array.isArray(retValue))) {
      retValue = new ShellString(retValue, state.error, state.errorCode);
    }

    state.currentCmd = 'shell.js';
    return retValue;
  };
} // wrap
exports.wrap = wrap;

// This returns all the input that is piped into the current command (or the
// empty string, if this isn't on the right-hand side of a pipe
function _readFromPipe() {
  return state.pipedValue;
}
exports.readFromPipe = _readFromPipe;

var DEFAULT_WRAP_OPTIONS = {
  allowGlobbing: true,
  canReceivePipe: false,
  cmdOptions: null,
  globStart: 1,
  handlesFatalDynamically: false,
  pipeOnly: false,
  wrapOutput: true,
  unix: true,
};

// This is populated during plugin registration
var pipeMethods = [];

// Register a new ShellJS command
function _register(name, implementation, wrapOptions) {
  wrapOptions = wrapOptions || {};

  // Validate options
  Object.keys(wrapOptions).forEach(function (option) {
    if (!DEFAULT_WRAP_OPTIONS.hasOwnProperty(option)) {
      throw new Error("Unknown option '" + option + "'");
    }
    if (typeof wrapOptions[option] !== typeof DEFAULT_WRAP_OPTIONS[option]) {
      throw new TypeError("Unsupported type '" + typeof wrapOptions[option] +
        "' for option '" + option + "'");
    }
  });

  // If an option isn't specified, use the default
  wrapOptions = Object.assign({}, DEFAULT_WRAP_OPTIONS, wrapOptions);

  if (shell[name]) {
    throw new Error('Command `' + name + '` already exists');
  }

  if (wrapOptions.pipeOnly) {
    wrapOptions.canReceivePipe = true;
    shellMethods[name] = wrap(name, implementation, wrapOptions);
  } else {
    shell[name] = wrap(name, implementation, wrapOptions);
  }

  if (wrapOptions.canReceivePipe) {
    pipeMethods.push(name);
  }
}
exports.register = _register;
<commit_msg>Silence potentially upcoming circular dependency warning (#973)<commit_after>// Ignore warning about 'new String()' and use of the Buffer constructor
/* eslint no-new-wrappers: "off",
   no-buffer-constructor: "off" */

'use strict';

var os = require('os');
var fs = require('fs');
var glob = require('glob');
var shell = require('..');

var shellMethods = Object.create(shell);

exports.extend = Object.assign;

// Check if we're running under electron
var isElectron = Boolean(process.versions.electron);

// Module globals (assume no execPath by default)
var DEFAULT_CONFIG = {
  fatal: false,
  globOptions: {},
  maxdepth: 255,
  noglob: false,
  silent: false,
  verbose: false,
  execPath: null,
  bufLength: 64 * 1024, // 64KB
};

var config = {
  reset: function () {
    Object.assign(this, DEFAULT_CONFIG);
    if (!isElectron) {
      this.execPath = process.execPath;
    }
  },
  resetForTesting: function () {
    this.reset();
    this.silent = true;
  },
};

config.reset();
exports.config = config;

// Note: commands should generally consider these as read-only values.
var state = {
  error: null,
  errorCode: 0,
  currentCmd: 'shell.js',
};
exports.state = state;

delete process.env.OLDPWD; // initially, there's no previous directory

// Reliably test if something is any sort of javascript object
function isObject(a) {
  return typeof a === 'object' && a !== null;
}
exports.isObject = isObject;

function log() {
  /* istanbul ignore next */
  if (!config.silent) {
    console.error.apply(console, arguments);
  }
}
exports.log = log;

// Converts strings to be equivalent across all platforms. Primarily responsible
// for making sure we use '/' instead of '\' as path separators, but this may be
// expanded in the future if necessary
function convertErrorOutput(msg) {
  if (typeof msg !== 'string') {
    throw new TypeError('input must be a string');
  }
  return msg.replace(/\\/g, '/');
}
exports.convertErrorOutput = convertErrorOutput;

// An exception class to help propagate command errors (e.g., non-zero exit
// status) up to the top-level. {@param value} should be a ShellString.
function CommandError(value) {
  this.returnValue = value;
}
CommandError.prototype = Object.create(Error.prototype);
CommandError.prototype.constructor = CommandError;
exports.CommandError = CommandError; // visible for testing

// Shows error message. Throws if fatal is true (defaults to config.fatal, overridable with options.fatal)
function error(msg, _code, options) {
  // Validate input
  if (typeof msg !== 'string') throw new Error('msg must be a string');

  var DEFAULT_OPTIONS = {
    continue: false,
    code: 1,
    prefix: state.currentCmd + ': ',
    silent: false,
    fatal: config.fatal,
  };

  if (typeof _code === 'number' && isObject(options)) {
    options.code = _code;
  } else if (isObject(_code)) { // no 'code'
    options = _code;
  } else if (typeof _code === 'number') { // no 'options'
    options = { code: _code };
  } else if (typeof _code !== 'number') { // only 'msg'
    options = {};
  }
  options = Object.assign({}, DEFAULT_OPTIONS, options);

  if (!state.errorCode) state.errorCode = options.code;

  var logEntry = convertErrorOutput(options.prefix + msg);
  state.error = state.error ? state.error + '\n' : '';
  state.error += logEntry;

  // Throw an error, or log the entry
  if (options.fatal) throw new Error(logEntry);
  if (msg.length > 0 && !options.silent) log(logEntry);

  if (!options.continue) {
    throw new CommandError(new ShellString('', state.error, state.errorCode));
  }
}
exports.error = error;

//@
//@ ### ShellString(str)
//@
//@ Examples:
//@
//@ ```javascript
//@ var foo = new ShellString('hello world');
//@ ```
//@
//@ This is a dedicated type returned by most ShellJS methods, which wraps a
//@ string (or array) value. This has all the string (or array) methods, but
//@ also exposes extra methods: [`.to()`](#shellstringprototypetofile),
//@ [`.toEnd()`](#shellstringprototypetoendfile), and all the pipe-able methods
//@ (ex. `.cat()`, `.grep()`, etc.). This can be easily converted into a string
//@ by calling `.toString()`.
//@
//@ This type also exposes the corresponding command's stdout, stderr, and
//@ return status code via the `.stdout` (string), `.stderr` (string), and
//@ `.code` (number) properties respectively.
function ShellString(stdout, stderr, code) {
  var that;
  if (stdout instanceof Array) {
    that = stdout;
    that.stdout = stdout.join('\n');
    if (stdout.length > 0) that.stdout += '\n';
  } else {
    that = new String(stdout);
    that.stdout = stdout;
  }
  that.stderr = stderr;
  that.code = code;
  // A list of all commands that can appear on the right-hand side of a pipe
  // (populated by calls to common.wrap())
  pipeMethods.forEach(function (cmd) {
    that[cmd] = shellMethods[cmd].bind(that);
  });
  return that;
}

exports.ShellString = ShellString;

// Returns {'alice': true, 'bob': false} when passed a string and dictionary as follows:
//   parseOptions('-a', {'a':'alice', 'b':'bob'});
// Returns {'reference': 'string-value', 'bob': false} when passed two dictionaries of the form:
//   parseOptions({'-r': 'string-value'}, {'r':'reference', 'b':'bob'});
// Throws an error when passed a string that does not start with '-':
//   parseOptions('a', {'a':'alice'}); // throws
function parseOptions(opt, map, errorOptions) {
  errorOptions = errorOptions || {};
  // Validate input
  if (typeof opt !== 'string' && !isObject(opt)) {
    throw new TypeError('options must be strings or key-value pairs');
  } else if (!isObject(map)) {
    throw new TypeError('parseOptions() internal error: map must be an object');
  } else if (!isObject(errorOptions)) {
    throw new TypeError(
        'parseOptions() internal error: errorOptions must be object'
    );
  }

  if (opt === '--') {
    // This means there are no options.
    return {};
  }

  // All options are false by default
  var options = {};
  Object.keys(map).forEach(function (letter) {
    var optName = map[letter];
    if (optName[0] !== '!') {
      options[optName] = false;
    }
  });

  if (opt === '') return options; // defaults

  if (typeof opt === 'string') {
    if (opt[0] !== '-') {
      throw new Error("Options string must start with a '-'");
    }

    // e.g. chars = ['R', 'f']
    var chars = opt.slice(1).split('');

    chars.forEach(function (c) {
      if (c in map) {
        var optionName = map[c];
        if (optionName[0] === '!') {
          options[optionName.slice(1)] = false;
        } else {
          options[optionName] = true;
        }
      } else {
        error('option not recognized: ' + c, errorOptions);
      }
    });
  } else { // opt is an Object
    Object.keys(opt).forEach(function (key) {
      if (key[0] === '-') {
        // key is a string of the form '-r', '-d', etc.
        var c = key[1];
        if (c in map) {
          var optionName = map[c];
          options[optionName] = opt[key]; // assign the given value
        } else {
          error('option not recognized: ' + c, errorOptions);
        }
      } else if (key in options) {
        // key is a "long option", so it should be the same
        options[key] = opt[key];
      } else {
        error('option not recognized: {' + key + ':...}', errorOptions);
      }
    });
  }
  return options;
}
exports.parseOptions = parseOptions;

// Expands wildcards with matching (ie. existing) file names.
// For example:
//   expand(['file*.js']) = ['file1.js', 'file2.js', ...]
//   (if the files 'file1.js', 'file2.js', etc, exist in the current dir)
function expand(list) {
  if (!Array.isArray(list)) {
    throw new TypeError('must be an array');
  }
  var expanded = [];
  list.forEach(function (listEl) {
    // Don't expand non-strings
    if (typeof listEl !== 'string') {
      expanded.push(listEl);
    } else {
      var ret;
      try {
        ret = glob.sync(listEl, config.globOptions);
        // if nothing matched, interpret the string literally
        ret = ret.length > 0 ? ret : [listEl];
      } catch (e) {
        // if glob fails, interpret the string literally
        ret = [listEl];
      }
      expanded = expanded.concat(ret);
    }
  });
  return expanded;
}
exports.expand = expand;

// Normalizes Buffer creation, using Buffer.alloc if possible.
// Also provides a good default buffer length for most use cases.
var buffer = typeof Buffer.alloc === 'function' ?
  function (len) {
    return Buffer.alloc(len || config.bufLength);
  } :
  function (len) {
    return new Buffer(len || config.bufLength);
  };
exports.buffer = buffer;

// Normalizes _unlinkSync() across platforms to match Unix behavior, i.e.
// file can be unlinked even if it's read-only, see https://github.com/joyent/node/issues/3006
function unlinkSync(file) {
  try {
    fs.unlinkSync(file);
  } catch (e) {
    // Try to override file permission
    /* istanbul ignore next */
    if (e.code === 'EPERM') {
      fs.chmodSync(file, '0666');
      fs.unlinkSync(file);
    } else {
      throw e;
    }
  }
}
exports.unlinkSync = unlinkSync;

// wrappers around common.statFollowLinks and common.statNoFollowLinks that clarify intent
// and improve readability
function statFollowLinks() {
  return fs.statSync.apply(fs, arguments);
}
exports.statFollowLinks = statFollowLinks;

function statNoFollowLinks() {
  return fs.lstatSync.apply(fs, arguments);
}
exports.statNoFollowLinks = statNoFollowLinks;

// e.g. 'shelljs_a5f185d0443ca...'
function randomFileName() {
  function randomHash(count) {
    if (count === 1) {
      return parseInt(16 * Math.random(), 10).toString(16);
    }
    var hash = '';
    for (var i = 0; i < count; i++) {
      hash += randomHash(1);
    }
    return hash;
  }

  return 'shelljs_' + randomHash(20);
}
exports.randomFileName = randomFileName;

// Common wrapper for all Unix-like commands that performs glob expansion,
// command-logging, and other nice things
function wrap(cmd, fn, options) {
  options = options || {};
  return function () {
    var retValue = null;

    state.currentCmd = cmd;
    state.error = null;
    state.errorCode = 0;

    try {
      var args = [].slice.call(arguments, 0);

      // Log the command to stderr, if appropriate
      if (config.verbose) {
        console.error.apply(console, [cmd].concat(args));
      }

      // If this is coming from a pipe, let's set the pipedValue (otherwise, set
      // it to the empty string)
      state.pipedValue = (this && typeof this.stdout === 'string') ? this.stdout : '';

      if (options.unix === false) { // this branch is for exec()
        retValue = fn.apply(this, args);
      } else { // and this branch is for everything else
        if (isObject(args[0]) && args[0].constructor.name === 'Object') {
          // a no-op, allowing the syntax `touch({'-r': file}, ...)`
        } else if (args.length === 0 || typeof args[0] !== 'string' || args[0].length <= 1 || args[0][0] !== '-') {
          args.unshift(''); // only add dummy option if '-option' not already present
        }

        // flatten out arrays that are arguments, to make the syntax:
        //    `cp([file1, file2, file3], dest);`
        // equivalent to:
        //    `cp(file1, file2, file3, dest);`
        args = args.reduce(function (accum, cur) {
          if (Array.isArray(cur)) {
            return accum.concat(cur);
          }
          accum.push(cur);
          return accum;
        }, []);

        // Convert ShellStrings (basically just String objects) to regular strings
        args = args.map(function (arg) {
          if (isObject(arg) && arg.constructor.name === 'String') {
            return arg.toString();
          }
          return arg;
        });

        // Expand the '~' if appropriate
        var homeDir = os.homedir();
        args = args.map(function (arg) {
          if (typeof arg === 'string' && arg.slice(0, 2) === '~/' || arg === '~') {
            return arg.replace(/^~/, homeDir);
          }
          return arg;
        });

        // Perform glob-expansion on all arguments after globStart, but preserve
        // the arguments before it (like regexes for sed and grep)
        if (!config.noglob && options.allowGlobbing === true) {
          args = args.slice(0, options.globStart).concat(expand(args.slice(options.globStart)));
        }

        try {
          // parse options if options are provided
          if (isObject(options.cmdOptions)) {
            args[0] = parseOptions(args[0], options.cmdOptions);
          }

          retValue = fn.apply(this, args);
        } catch (e) {
          /* istanbul ignore else */
          if (e instanceof CommandError) {
            retValue = e.returnValue;
          } else {
            throw e; // this is probably a bug that should be thrown up the call stack
          }
        }
      }
    } catch (e) {
      /* istanbul ignore next */
      if (!state.error) {
        // If state.error hasn't been set it's an error thrown by Node, not us - probably a bug...
        e.name = 'ShellJSInternalError';
        throw e;
      }
      if (config.fatal || options.handlesFatalDynamically) throw e;
    }

    if (options.wrapOutput &&
        (typeof retValue === 'string' || Array.isArray(retValue))) {
      retValue = new ShellString(retValue, state.error, state.errorCode);
    }

    state.currentCmd = 'shell.js';
    return retValue;
  };
} // wrap
exports.wrap = wrap;

// This returns all the input that is piped into the current command (or the
// empty string, if this isn't on the right-hand side of a pipe
function _readFromPipe() {
  return state.pipedValue;
}
exports.readFromPipe = _readFromPipe;

var DEFAULT_WRAP_OPTIONS = {
  allowGlobbing: true,
  canReceivePipe: false,
  cmdOptions: null,
  globStart: 1,
  handlesFatalDynamically: false,
  pipeOnly: false,
  wrapOutput: true,
  unix: true,
};

// This is populated during plugin registration
var pipeMethods = [];

// Register a new ShellJS command
function _register(name, implementation, wrapOptions) {
  wrapOptions = wrapOptions || {};

  // Validate options
  Object.keys(wrapOptions).forEach(function (option) {
    if (!DEFAULT_WRAP_OPTIONS.hasOwnProperty(option)) {
      throw new Error("Unknown option '" + option + "'");
    }
    if (typeof wrapOptions[option] !== typeof DEFAULT_WRAP_OPTIONS[option]) {
      throw new TypeError("Unsupported type '" + typeof wrapOptions[option] +
        "' for option '" + option + "'");
    }
  });

  // If an option isn't specified, use the default
  wrapOptions = Object.assign({}, DEFAULT_WRAP_OPTIONS, wrapOptions);

  if (shell.hasOwnProperty(name)) {
    throw new Error('Command `' + name + '` already exists');
  }

  if (wrapOptions.pipeOnly) {
    wrapOptions.canReceivePipe = true;
    shellMethods[name] = wrap(name, implementation, wrapOptions);
  } else {
    shell[name] = wrap(name, implementation, wrapOptions);
  }

  if (wrapOptions.canReceivePipe) {
    pipeMethods.push(name);
  }
}
exports.register = _register;
<|endoftext|>


<commit_before> Object.defineProperty(this, 'onsync', {
    configurable: false,
    enumerable: true,
    get: eventGetter('sync'),
    set: eventSetter('sync')
});

Registration = function() {
    return this;
};

Registration.prototype.unregister = function() {
    unregisterSync(this.id);
};

SyncEvent = function() {
    this.registration = new Registration();
    return this;
};

SyncEvent.prototype = new ExtendableEvent('sync');

FireSyncEvent = function(data) {
    var ev = new SyncEvent();
    ev.registration.id = data.id;
    /*
    ev.registration.minDelay = data.minDelay;
    ev.registration.maxDelay = data.maxDelay;
    ev.registration.minPeriod = data.minPeriod;
    ev.registration.minRequiredNetwork = data.minRequiredNetwork;
    ev.registration.allowOnBattery = data.allowOnBattery;
    ev.registration.idleRequired = data.idleRequired;*/
    dispatchEvent(ev);
    if(ev.promises instanceof Array) {
	return Promise.all(ev.promises).then(function(){
		sendSyncResponse(0, data.id);
	    },function(){
		sendSyncResponse(2, data.id);
	    });
    } else {
	sendSyncResponse(1, data.id);
	return Promise.resolve();
    }
};
<commit_msg>Added all properties to event registration object<commit_after> Object.defineProperty(this, 'onsync', {
    configurable: false,
    enumerable: true,
    get: eventGetter('sync'),
    set: eventSetter('sync')
});

Registration = function() {
    return this;
};

Registration.prototype.unregister = function() {
    unregisterSync(this.id);
};

SyncEvent = function() {
    this.registration = new Registration();
    return this;
};

SyncEvent.prototype = new ExtendableEvent('sync');

FireSyncEvent = function(data) {
    var ev = new SyncEvent();
    ev.registration.id = data.id;
    ev.registration.minDelay = data.minDelay;
    ev.registration.maxDelay = data.maxDelay;
    ev.registration.minPeriod = data.minPeriod;
    ev.registration.minRequiredNetwork = data.minRequiredNetwork;
    ev.registration.allowOnBattery = data.allowOnBattery;
    ev.registration.idleRequired = data.idleRequired;
    dispatchEvent(ev);
    if(ev.promises instanceof Array) {
	return Promise.all(ev.promises).then(function(){
		sendSyncResponse(0, data.id);
	    },function(){
		sendSyncResponse(2, data.id);
	    });
    } else {
	sendSyncResponse(1, data.id);
	return Promise.resolve();
    }
};
<|endoftext|>


<commit_before>(function ($) {
    var thisScript = $('script[src$="/static/js/widget.js"]'),
        debtDomain = thisScript[0].src.split('/static/js/widget.js')[0],
        iframe = $('<iframe>'),
        idToElement = {};

    $(window).on('message', function (e) {
        var targetElement, frame,
            originalEvent = e.originalEvent;

        if (originalEvent.data.ready) {
            $('.git-code-debt').each(function (elementId, el) {
                var element = $(el),
                    diff = element.text();

                idToElement[elementId] = element;

                iframe[0].contentWindow.postMessage(
                    {
                        elementId: elementId,
                        diff: diff
                    },
                    '*'
                );
            });
        } else if (originalEvent.data.metrics) {
            targetElement = idToElement[originalEvent.data.elementId];
            delete idToElement[originalEvent.data.elementId];
            frame = $('<iframe scrolling="no" seamless="seamless">');
            frame.css({border: 0, width: 0, height: 0});
            // In firefox, about:blank is loaded asynchronously
            // http://stackoverflow.com/questions/10531909/source-less-iframe-bug-firefox
            frame.on('load', function () {
                var reference,
                    frameBody = frame.contents().find('body').css('margin', 0),
                    stylesheet = $('<link>').attr({
                        rel: 'stylesheet',
                        href: debtDomain + '/static/css/git_code_debt.css'
                    });
                frameBody.append(stylesheet);
                reference = $('<div>').css('float', 'left').append(
                    $(originalEvent.data.metrics)
                );
                frameBody.append(reference);
                referenceTable = frameBody.find('table');
                frame[0].contentWindow.setInterval(
                    function () {
                        frame.width(reference.outerWidth(true));
                        frame.height(reference.outerHeight(true));
                    },
                    100
                );
                targetElement.show();
            });
            targetElement.empty().append(frame);

            if ($.isEmptyObject(idToElement)) {
                iframe.remove();
            }
        }
    });

    iframe.css({
        border: 0,
        width: 1,
        height: 1,
        position: 'absolute',
        left: -9999
    }).attr('src', debtDomain + '/widget/frame');
    $('body').append(iframe);

    // We loaded another version of jquery so let's attempt to not step
    // on other people's toes.
} (window.jq = $.noConflict(true)));
<commit_msg>Update SO link<commit_after>(function ($) {
    var thisScript = $('script[src$="/static/js/widget.js"]'),
        debtDomain = thisScript[0].src.split('/static/js/widget.js')[0],
        iframe = $('<iframe>'),
        idToElement = {};

    $(window).on('message', function (e) {
        var targetElement, frame,
            originalEvent = e.originalEvent;

        if (originalEvent.data.ready) {
            $('.git-code-debt').each(function (elementId, el) {
                var element = $(el),
                    diff = element.text();

                idToElement[elementId] = element;

                iframe[0].contentWindow.postMessage(
                    {
                        elementId: elementId,
                        diff: diff
                    },
                    '*'
                );
            });
        } else if (originalEvent.data.metrics) {
            targetElement = idToElement[originalEvent.data.elementId];
            delete idToElement[originalEvent.data.elementId];
            frame = $('<iframe scrolling="no" seamless="seamless">');
            frame.css({border: 0, width: 0, height: 0});
            // In firefox, about:blank is loaded asynchronously
            // https://stackoverflow.com/q/10531909/812183
            frame.on('load', function () {
                var reference,
                    frameBody = frame.contents().find('body').css('margin', 0),
                    stylesheet = $('<link>').attr({
                        rel: 'stylesheet',
                        href: debtDomain + '/static/css/git_code_debt.css'
                    });
                frameBody.append(stylesheet);
                reference = $('<div>').css('float', 'left').append(
                    $(originalEvent.data.metrics)
                );
                frameBody.append(reference);
                referenceTable = frameBody.find('table');
                frame[0].contentWindow.setInterval(
                    function () {
                        frame.width(reference.outerWidth(true));
                        frame.height(reference.outerHeight(true));
                    },
                    100
                );
                targetElement.show();
            });
            targetElement.empty().append(frame);

            if ($.isEmptyObject(idToElement)) {
                iframe.remove();
            }
        }
    });

    iframe.css({
        border: 0,
        width: 1,
        height: 1,
        position: 'absolute',
        left: -9999
    }).attr('src', debtDomain + '/widget/frame');
    $('body').append(iframe);

    // We loaded another version of jquery so let's attempt to not step
    // on other people's toes.
} (window.jq = $.noConflict(true)));
<|endoftext|>


<commit_before>(function() {
  function Spaceship( game ) {
    var self = this,
        playerWidth = 20,
        playerHeight = 30,
        playerVertices = [
          [ - 1 * playerHeight / 2, -1 * playerWidth / 2],
          [ - 1 * playerHeight / 2, playerWidth / 2],
          [playerHeight / 2, 0]
        ];

    $.extend(self, {
      init: function() {
        self.position   = new Vector(480, 420);
        self.angle      = -1.578;
        self.velocity   = new Vector(0, 0);
        self.limits     = [ game.canvas.offsetWidth - playerHeight,
                            5 + playerHeight,
                            game.canvas.offsetHeight - playerHeight,
                            5 + playerHeight
                          ];
        self.accelleration = 0.5;
        self.maxSpeed      = 7;
        self.rotationSpeed = 0.2;
        self.bullets       = [];
        self.lastFire      = -1000;
        self.fireSpeed     = 500;
      },

      draw: function() {
        self.position.add(self.velocity);

        game.paper.save();
        game.paper.translate(self.position.x, self.position.y);
        game.paper.rotate(self.angle);
        self.drawPlayer();
        game.paper.restore();

        return self;
      },

      drawPlayer: function() {
        game.paper.fillStyle = "white";
        game.util.tracePoly(playerVertices);
        game.paper.fill();
        game.paper.stroke();
      },

      moveForward: function() {
        self.speedCheck();
        self.velocity.add({
          x: Rotation.offsetX(self.angle, self.accelleration),
          y: Rotation.offsetY(self.angle, self.accelleration)
        });
        self.boundsCheck();
      },

      moveBackward: function() {
        self.velocity.mul(0.85);
        self.boundsCheck();
      },

      moveLeft: function() {
        self.angle -= self.rotationSpeed;
        self.boundsCheck();
      },

      moveRight: function() {
        self.angle += self.rotationSpeed;
        self.boundsCheck();
      },

      fire: function() {
        if ( (game.currentTime - self.lastFire) > self.fireSpeed ) {
          self.lastFire = game.currentTime;
          self.bullets.push(new Bullet(game, self.position.cp(), self.angle, self.velocity.cp()));
        }
      },

      drawBullets: function() {
        for( var i = self.bullets.length - 1; i >= 0; i-- ) {
          if ( self.bullets[i].isDead() ) {
            self.bullets.splice(i, 1);
          } else {
            self.bullets[i].draw();
          }
        }
      },

      boundsCheck: function() {
        var pos = self.position;

        if (pos.x >= self.limits[0]) {
          self.position.x = self.limits[0];
        } else if (pos.x <= self.limits[1]) {
          self.position.x = self.limits[1];
        }

        if (pos.y >= self.limits[2]) {
          self.position.y = self.limits[2];
        } else if (pos.y <= self.limits[3]) {
          self.position.y = self.limits[3];
        }
      },

      speedCheck: function() {
        if (self.velocity.len() > self.maxSpeed) {
          self.velocity.setLength(self.maxSpeed);
        }
      }
    });

    self.init();
  }

  window.Spaceship = Spaceship;

})();
<commit_msg>fixed angle and ship orientation<commit_after>(function() {
  function Spaceship( game ) {
    var self = this,
        playerVertices = [
          [-10, 10],
          [20, 0],
          [-10, -10]
        ];

    $.extend(self, {
      init: function() {
        self.position   = new Vector(480, 420);
        self.angle      = 270;
        self.velocity   = new Vector(0, 0);
        self.limits     = [ game.canvas.offsetWidth,
                            0,
                            game.canvas.offsetHeight,
                            0
                          ];
        self.accelleration = 0.6;
        self.maxSpeed      = 7;
        self.rotationSpeed = 7;
        self.bullets       = [];
        self.lastFire      = -1000;
        self.fireSpeed     = 500;
      },

      draw: function() {
        self.position.add(self.velocity);

        game.paper.save();
        game.paper.translate(self.position.x, self.position.y);
        game.paper.rotate(game.util.radians(self.angle));
        self.drawPlayer();
        game.paper.restore();

        return self;
      },

      drawPlayer: function() {
        game.paper.fillStyle = "white";
        game.util.tracePoly(playerVertices);
        game.paper.fill();
        game.paper.stroke();
      },

      moveForward: function() {
        self.speedCheck();
        self.velocity.add({
          x: Rotation.offsetX(self.angle, self.accelleration),
          y: Rotation.offsetY(self.angle, self.accelleration)
        });
        self.boundsCheck();
      },

      moveBackward: function() {
        self.velocity.mul(0.85);
        self.boundsCheck();
      },

      moveLeft: function() {
        self.angle -= self.rotationSpeed;
      },

      moveRight: function() {
        self.angle += self.rotationSpeed;
      },

      fire: function() {
        if ( (game.currentTime - self.lastFire) > self.fireSpeed ) {
          self.lastFire = game.currentTime;
          self.bullets.push(new Bullet(game, self.position.cp(), self.angle, self.velocity.cp()));
        }
      },

      drawBullets: function() {
        for( var i = self.bullets.length - 1; i >= 0; i-- ) {
          if ( self.bullets[i].isDead() ) {
            self.bullets.splice(i, 1);
          } else {
            self.bullets[i].draw();
          }
        }
      },

      boundsCheck: function() {
        var pos = self.position;

        if (pos.x >= self.limits[0]) {
          self.position.x = self.limits[0];
        } else if (pos.x <= self.limits[1]) {
          self.position.x = self.limits[1];
        }

        if (pos.y >= self.limits[2]) {
          self.position.y = self.limits[2];
        } else if (pos.y <= self.limits[3]) {
          self.position.y = self.limits[3];
        }
      },

      speedCheck: function() {
        if (self.velocity.len() > self.maxSpeed) {
          self.velocity.setLength(self.maxSpeed);
        }
      }
    });

    self.init();
  }

  window.Spaceship = Spaceship;

})();
<|endoftext|>


<commit_before>// @flow

import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
import { CONFERENCE_LEFT, CONFERENCE_WILL_JOIN } from '../conference';
import { MiddlewareRegistry } from '../redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { playSound, registerSound, unregisterSound } from '../sounds';

import {
    localParticipantIdChanged,
    localParticipantJoined,
    participantUpdated
} from './actions';
import {
    DOMINANT_SPEAKER_CHANGED,
    KICK_PARTICIPANT,
    MUTE_REMOTE_PARTICIPANT,
    PARTICIPANT_DISPLAY_NAME_CHANGED,
    PARTICIPANT_JOINED,
    PARTICIPANT_LEFT,
    PARTICIPANT_UPDATED
} from './actionTypes';
import {
    LOCAL_PARTICIPANT_DEFAULT_ID,
    PARTICIPANT_JOINED_SOUND_ID,
    PARTICIPANT_LEFT_SOUND_ID
} from './constants';
import {
    getAvatarURLByParticipantId,
    getLocalParticipant,
    getParticipantById,
    getParticipantCount
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';

declare var APP: Object;

/**
 * Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
 * updates respectively ID of local participant.
 *
 * @param {Store} store - The redux store.
 * @returns {Function}
 */
MiddlewareRegistry.register(store => next => action => {
    switch (action.type) {
    case APP_WILL_MOUNT:
        _registerSounds(store);

        return _localParticipantJoined(store, next, action);

    case APP_WILL_UNMOUNT:
        _unregisterSounds(store);
        break;

    case CONFERENCE_WILL_JOIN:
        store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
        break;

    case CONFERENCE_LEFT:
        store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
        break;

    case DOMINANT_SPEAKER_CHANGED: {
        // Ensure the raised hand state is cleared for the dominant speaker.

        const { conference, id } = action.participant;
        const participant = getLocalParticipant(store.getState());

        participant
            && store.dispatch(participantUpdated({
                conference,
                id,
                local: participant.id === id,
                raisedHand: false
            }));

        typeof APP === 'object' && APP.UI.markDominantSpeaker(id);

        break;
    }

    case KICK_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.kickParticipant(action.id);
        break;
    }

    case MUTE_REMOTE_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.muteParticipant(action.id);
        break;
    }

    // TODO Remove this middleware when the local display name update flow is
    // fully brought into redux.
    case PARTICIPANT_DISPLAY_NAME_CHANGED: {
        if (typeof APP !== 'undefined') {
            const participant = getLocalParticipant(store.getState());

            if (participant && participant.id === action.id) {
                APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
            }
        }

        break;
    }

    case PARTICIPANT_JOINED:
        _maybePlaySounds(store, action);

        return _participantJoinedOrUpdated(store, next, action);

    case PARTICIPANT_LEFT:
        _maybePlaySounds(store, action);
        break;

    case PARTICIPANT_UPDATED:
        return _participantJoinedOrUpdated(store, next, action);
    }

    return next(action);
});

/**
 * Initializes the local participant and signals that it joined.
 *
 * @private
 * @param {Store} store - The redux store.
 * @param {Dispatch} next - The redux dispatch function to dispatch the
 * specified action to the specified store.
 * @param {Action} action - The redux action which is being dispatched
 * in the specified store.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _localParticipantJoined({ getState, dispatch }, next, action) {
    const result = next(action);

    const settings = getState()['features/base/settings'];

    dispatch(localParticipantJoined({
        avatarID: settings.avatarID,
        avatarURL: settings.avatarURL,
        email: settings.email,
        name: settings.displayName
    }));

    return result;
}

/**
 * Plays sounds when participants join/leave conference.
 *
 * @param {Store} store - The redux store.
 * @param {Action} action - The redux action. Should be either
 * {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
 * @private
 * @returns {void}
 */
function _maybePlaySounds({ getState, dispatch }, action) {
    const state = getState();
    const { startAudioMuted } = state['features/base/config'];

    // We're not playing sounds for local participant
    // nor when the user is joining past the "startAudioMuted" limit.
    // The intention there was to not play user joined notification in big
    // conferences where 100th person is joining.
    if (!action.participant.local
            && (!startAudioMuted
                || getParticipantCount(state) < startAudioMuted)) {
        if (action.type === PARTICIPANT_JOINED) {
            dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
        } else if (action.type === PARTICIPANT_LEFT) {
            dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
        }
    }
}

/**
 * Notifies the feature base/participants that the action
 * {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} is being dispatched
 * within a specific redux store.
 *
 * @param {Store} store - The redux store in which the specified {@code action}
 * is being dispatched.
 * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
 * specified {@code action} in the specified {@code store}.
 * @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or
 * {@code PARTICIPANT_UPDATED} which is being dispatched in the specified
 * {@code store}.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _participantJoinedOrUpdated({ getState }, next, action) {
    const { participant: { id, local, raisedHand } } = action;

    // Send an external update of the local participant's raised hand state
    // if a new raised hand state is defined in the action.
    if (typeof raisedHand !== 'undefined') {
        if (local) {
            const { conference } = getState()['features/base/conference'];

            conference
                && conference.setLocalParticipantProperty(
                    'raisedHand',
                    raisedHand);
        }

        if (typeof APP === 'object') {
            if (local) {
                APP.UI.onLocalRaiseHandChanged(raisedHand);
                APP.UI.setLocalRaisedHandStatus(raisedHand);
            } else {
                const remoteParticipant = getParticipantById(getState(), id);

                remoteParticipant
                    && APP.UI.setRaisedHandStatus(
                        remoteParticipant.id,
                        remoteParticipant.name,
                        raisedHand);
            }
        }
    }

    // Notify external listeners of potential avatarURL changes.
    if (typeof APP === 'object') {
        const oldAvatarURL = getAvatarURLByParticipantId(getState(), id);

        // Allow the redux update to go through and compare the old avatar
        // to the new avatar and emit out change events if necessary.
        const result = next(action);
        const newAvatarURL = getAvatarURLByParticipantId(getState(), id);

        if (oldAvatarURL !== newAvatarURL) {
            const currentKnownId = local ? APP.conference.getMyUserId() : id;

            APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
            APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
        }

        return result;
    }

    return next(action);
}

/**
 * Registers sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _registerSounds({ dispatch }) {
    dispatch(
        registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_FILE));
    dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}

/**
 * Unregisters sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _unregisterSounds({ dispatch }) {
    dispatch(unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
    dispatch(unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
}
<commit_msg>Remove remote participants who are no longer of interest<commit_after>// @flow

import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
import { CONFERENCE_LEFT, CONFERENCE_WILL_JOIN } from '../conference';
import { MiddlewareRegistry, StateListenerRegistry } from '../redux';
import UIEvents from '../../../../service/UI/UIEvents';
import { playSound, registerSound, unregisterSound } from '../sounds';

import {
    localParticipantIdChanged,
    localParticipantJoined,
    participantLeft,
    participantUpdated
} from './actions';
import {
    DOMINANT_SPEAKER_CHANGED,
    KICK_PARTICIPANT,
    MUTE_REMOTE_PARTICIPANT,
    PARTICIPANT_DISPLAY_NAME_CHANGED,
    PARTICIPANT_JOINED,
    PARTICIPANT_LEFT,
    PARTICIPANT_UPDATED
} from './actionTypes';
import {
    LOCAL_PARTICIPANT_DEFAULT_ID,
    PARTICIPANT_JOINED_SOUND_ID,
    PARTICIPANT_LEFT_SOUND_ID
} from './constants';
import {
    getAvatarURLByParticipantId,
    getLocalParticipant,
    getParticipantById,
    getParticipantCount
} from './functions';
import { PARTICIPANT_JOINED_FILE, PARTICIPANT_LEFT_FILE } from './sounds';

declare var APP: Object;

/**
 * Middleware that captures CONFERENCE_JOINED and CONFERENCE_LEFT actions and
 * updates respectively ID of local participant.
 *
 * @param {Store} store - The redux store.
 * @returns {Function}
 */
MiddlewareRegistry.register(store => next => action => {
    switch (action.type) {
    case APP_WILL_MOUNT:
        _registerSounds(store);

        return _localParticipantJoined(store, next, action);

    case APP_WILL_UNMOUNT:
        _unregisterSounds(store);
        break;

    case CONFERENCE_WILL_JOIN:
        store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
        break;

    case CONFERENCE_LEFT:
        store.dispatch(localParticipantIdChanged(LOCAL_PARTICIPANT_DEFAULT_ID));
        break;

    case DOMINANT_SPEAKER_CHANGED: {
        // Ensure the raised hand state is cleared for the dominant speaker.

        const { conference, id } = action.participant;
        const participant = getLocalParticipant(store.getState());

        participant
            && store.dispatch(participantUpdated({
                conference,
                id,
                local: participant.id === id,
                raisedHand: false
            }));

        typeof APP === 'object' && APP.UI.markDominantSpeaker(id);

        break;
    }

    case KICK_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.kickParticipant(action.id);
        break;
    }

    case MUTE_REMOTE_PARTICIPANT: {
        const { conference } = store.getState()['features/base/conference'];

        conference.muteParticipant(action.id);
        break;
    }

    // TODO Remove this middleware when the local display name update flow is
    // fully brought into redux.
    case PARTICIPANT_DISPLAY_NAME_CHANGED: {
        if (typeof APP !== 'undefined') {
            const participant = getLocalParticipant(store.getState());

            if (participant && participant.id === action.id) {
                APP.UI.emitEvent(UIEvents.NICKNAME_CHANGED, action.name);
            }
        }

        break;
    }

    case PARTICIPANT_JOINED:
        _maybePlaySounds(store, action);

        return _participantJoinedOrUpdated(store, next, action);

    case PARTICIPANT_LEFT:
        _maybePlaySounds(store, action);
        break;

    case PARTICIPANT_UPDATED:
        return _participantJoinedOrUpdated(store, next, action);
    }

    return next(action);
});

/**
 * Syncs the redux state features/base/participants up with the redux state
 * features/base/conference by ensuring that the former does not contain remote
 * participants no longer relevant to the latter. Introduced to address an issue
 * with multiplying thumbnails in the filmstrip.
 */
StateListenerRegistry.register(
    /* selector */ state => {
        const { conference, joining } = state['features/base/conference'];

        return conference || joining;
    },
    /* listener */ (conference, { dispatch, getState }) => {
        for (const p of getState()['features/base/participants']) {
            !p.local
                && (!conference || p.conference !== conference)
                && dispatch(participantLeft(p.id, p.conference));
        }
    });

/**
 * Initializes the local participant and signals that it joined.
 *
 * @private
 * @param {Store} store - The redux store.
 * @param {Dispatch} next - The redux dispatch function to dispatch the
 * specified action to the specified store.
 * @param {Action} action - The redux action which is being dispatched
 * in the specified store.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _localParticipantJoined({ getState, dispatch }, next, action) {
    const result = next(action);

    const settings = getState()['features/base/settings'];

    dispatch(localParticipantJoined({
        avatarID: settings.avatarID,
        avatarURL: settings.avatarURL,
        email: settings.email,
        name: settings.displayName
    }));

    return result;
}

/**
 * Plays sounds when participants join/leave conference.
 *
 * @param {Store} store - The redux store.
 * @param {Action} action - The redux action. Should be either
 * {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
 * @private
 * @returns {void}
 */
function _maybePlaySounds({ getState, dispatch }, action) {
    const state = getState();
    const { startAudioMuted } = state['features/base/config'];

    // We're not playing sounds for local participant
    // nor when the user is joining past the "startAudioMuted" limit.
    // The intention there was to not play user joined notification in big
    // conferences where 100th person is joining.
    if (!action.participant.local
            && (!startAudioMuted
                || getParticipantCount(state) < startAudioMuted)) {
        if (action.type === PARTICIPANT_JOINED) {
            dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
        } else if (action.type === PARTICIPANT_LEFT) {
            dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
        }
    }
}

/**
 * Notifies the feature base/participants that the action
 * {@code PARTICIPANT_JOINED} or {@code PARTICIPANT_UPDATED} is being dispatched
 * within a specific redux store.
 *
 * @param {Store} store - The redux store in which the specified {@code action}
 * is being dispatched.
 * @param {Dispatch} next - The redux {@code dispatch} function to dispatch the
 * specified {@code action} in the specified {@code store}.
 * @param {Action} action - The redux action {@code PARTICIPANT_JOINED} or
 * {@code PARTICIPANT_UPDATED} which is being dispatched in the specified
 * {@code store}.
 * @private
 * @returns {Object} The value returned by {@code next(action)}.
 */
function _participantJoinedOrUpdated({ getState }, next, action) {
    const { participant: { id, local, raisedHand } } = action;

    // Send an external update of the local participant's raised hand state
    // if a new raised hand state is defined in the action.
    if (typeof raisedHand !== 'undefined') {
        if (local) {
            const { conference } = getState()['features/base/conference'];

            conference
                && conference.setLocalParticipantProperty(
                    'raisedHand',
                    raisedHand);
        }

        if (typeof APP === 'object') {
            if (local) {
                APP.UI.onLocalRaiseHandChanged(raisedHand);
                APP.UI.setLocalRaisedHandStatus(raisedHand);
            } else {
                const remoteParticipant = getParticipantById(getState(), id);

                remoteParticipant
                    && APP.UI.setRaisedHandStatus(
                        remoteParticipant.id,
                        remoteParticipant.name,
                        raisedHand);
            }
        }
    }

    // Notify external listeners of potential avatarURL changes.
    if (typeof APP === 'object') {
        const oldAvatarURL = getAvatarURLByParticipantId(getState(), id);

        // Allow the redux update to go through and compare the old avatar
        // to the new avatar and emit out change events if necessary.
        const result = next(action);
        const newAvatarURL = getAvatarURLByParticipantId(getState(), id);

        if (oldAvatarURL !== newAvatarURL) {
            const currentKnownId = local ? APP.conference.getMyUserId() : id;

            APP.UI.refreshAvatarDisplay(currentKnownId, newAvatarURL);
            APP.API.notifyAvatarChanged(currentKnownId, newAvatarURL);
        }

        return result;
    }

    return next(action);
}

/**
 * Registers sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _registerSounds({ dispatch }) {
    dispatch(
        registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_FILE));
    dispatch(registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_FILE));
}

/**
 * Unregisters sounds related with the participants feature.
 *
 * @param {Store} store - The redux store.
 * @private
 * @returns {void}
 */
function _unregisterSounds({ dispatch }) {
    dispatch(unregisterSound(PARTICIPANT_JOINED_SOUND_ID));
    dispatch(unregisterSound(PARTICIPANT_LEFT_SOUND_ID));
}
<|endoftext|>


<commit_before>doh._numNotApplicable = 0;
dohx = {
	add:function(obj){
		var tests = obj.tests;
		// Add requiredObjects to each tests.
		// TODO maybe add this magically as a test and make all tests just depend on it.
		if (obj.requiredObjects){
			for (var i=0, t; t=tests[i]; i++){
				t.requiredObjects = t.requiredObjects ? (t.requiredObjects.concat(obj.requiredObjects)) : obj.requiredObjects;
			}
		}
		var dohTests = [];
		for (var i=0, t; t=tests[i]; i++){
			// Copy the addIf=false value to each test of a group.
			if ("addIf" in obj){
				t.addIf = !!obj.addIf;
			}
			if (t.instructions || t.expectedResult){
				dohTests.push(dohx.extendManualTestObject(t));
			} else if(t.timeout){
				t.instructions = "Click 'GO' to run test.";
				dohTests.push(dohx.extendManualTestObject(t));
			} else {
				dohTests.push(dohx.extendTestObject(t));
			}
		}
		// If obj.name=="" doh runs the tests forever, funny bug.
		doh.register(obj.name || "Noname", dohTests);
	}
};

dohx._testObject = {
	name:"",
	requiredObjects:[],
	timeout:5 * 1000, // The phone might be a bit slow, so give it some time :-).

	getMissingRequiredObjects:function(){
		var ret = [];
		for (var i=0, o; o=this.requiredObjects[i]; i++){
			// Split up the object name by "." and check each part singlely to report
			// more detailed error, e.g. "Widget.Device.foo" maybe already "Widget" is
			// not defined. So report "'Widget' is not implemented" instead of
			// "'Widget.Device.foo' is not implemented."
			var parts = o.split(".");
			for (var j=0, l=parts.length, p; j<l; j++){
				p = parts.slice(0, j+1).join(".");
				if (!util.isObject(p)){
					ret.push(p);
				}
			}
		}
		return ret;
	},

	_runTest:function(t){
		var missing = this.getMissingRequiredObjects();
		if (missing.length){
			t.failure("'" + missing[0] + "' is not implemented!");
			return;
		}
		if (this.requiredObjects.length>0 && !this.test){
			t.assertTrue(true);
			return;
		}
		if (typeof this.test=="function"){
			// Maybe no test is given.
			return this.test(t);
		}
	},

	runTest:function(t){
		window.scrollTo(0, 0); // Show the manual test overlay always at top.
		return this._runTest(t)
	}
};

dohx._manualTestObject = {};
doh.util.mixin(dohx._manualTestObject, dohx._testObject);
doh.util.mixin(dohx._manualTestObject, {
	timeout:1 * 60 * 1000,
	instructions:"",

	_goClicked:function(){
		util.query(".manualTest .whatToDo .goButton")[0].setAttribute("disabled", "disabled");
		var t = doh._current.test; // We cant use this, since we dont have the context, so we use t.
		if (t.instructions && !t.expectedResult){
			doh._runTest();
			if (doh._testInFlight){ // If the test didn't finish yet show the countdown, otherwise don't.
				ui.dialog.showCountDown(t.startTime, t.timeout);
			}
		} else if (t.expectedResult){
			// If we need the user to confirm the test result, because we cant do this programmatically
			// we just run the test and trigger _runTest() later with the "user says ..." as result.
			try{
				if (t.setUp) t.setUp();
				t.test(t);
			}catch(e){
				// If the test call itself fails make the test fail too, so we dont "swallow" error messages.
				doh._current.test.test = function(t){
					throw e;
				};
				// Run the actual test, but DONT run setUp() since we already executed it before test(), see above.
				var oldSetUp = t.setUp;
				t.setUp = function(){};
				doh._runTest();
				t.setUp = oldSetUp; // Put back the old setup.
//console.log(e); maybe no test function is implemented, is possible and valid. See e.g. PowerInfo it makes use of that.
			}
			util.style(".manualTest .result", {display:"block"});
		}
	},

	_tearDown:function(){
// TODO implement calling the actual tearDown!!!!
		ui.dialog.hide();
	},
	_successfulTestFunction:function(){
		doh._current.test.setUp = function(){};
		doh._current.test.test = function(t){
			t.success("User confirmed success, pressed 'Yes'.");
		};
		doh._runTest();
	},
	_failTestFunction:function(){
		doh._current.test.setUp = function(){};
		doh._current.test.test = function(t){
			t.failure("User declined success, pressed 'No'.")
		};
		doh._runTest();
	}
});
// We are connecting on the actual dohx._manualTestObject object itself, which
// means each of the function CANT access the current test through "this", but
// must use "doh._current.test" to access the current test!
// We could of course connect each time for each test, that would just mean we
// would also have to clean up, too much hazzle imho. This way it's just scope mangling :-).
util.connect(".manualTest .whatToDo .goButton", "onclick", doh.util.hitch(dohx._manualTestObject, "_goClicked"));
util.connect(".manualTest .result .yesButton", "onclick", doh.util.hitch(dohx._manualTestObject, "_successfulTestFunction"));
util.connect(".manualTest .result .noButton", "onclick", doh.util.hitch(dohx._manualTestObject, "_failTestFunction"));

doh._runNextTest = function(){
	do{
		var hasNextTest = this._selectNextTest();
		if (!hasNextTest){
			return;
		}
		var c = this._current;
		if (c.test.addIf===false){
			// Pass in the following structure, which is actually created inside
			// doh, but we don't pass the test to doh.
			var test = {};
			doh.util.mixin(test, c.test);
			test.group = c.group;
// TODO I dont know if this is proper in this place.
			doh._numNotApplicable++;
			ui.notApplicable(test);
		}
	}while(c.test.addIf===false);
	var missingReqObj = c.test.getMissingRequiredObjects();
	// If there are requiredObjects that don't exist don't even show the dialog.
	if ((c.test.expectedResult || c.test.instructions) && missingReqObj.length==0){
		// Show the instructions. The click on 'GO' will then call this._runTest().
		var instructions;
		if (util.isArray(c.test.instructions)){
			// If it is an array, prefix every line with an increasing number.
			instructions = [];
			for (var i=0, l=c.test.instructions.length; i<l; i++){
				instructions.push((i+1) +") " + c.test.instructions[i]);
			}
			instructions = instructions.join('
');
		} else {
			instructions = c.test.instructions;
		}
		ui.dialog.show(c.test.name, instructions, c.test.expectedResult);
	} else {
		this._runTest();
	}
	return;
}

doh.config.testFunctionName = "runTest";

dohx.showInfo = function(){
	ui.showInfo(Array.prototype.slice.apply(arguments).join(" "));
}
dohx.hideInfo = function(){
	ui.hideInfo();
}

dohx.extendTestObject = function(obj){
	// summary: Clone the doh._testObject and add the properties of obj.
	var ret = {};
	doh.util.mixin(ret, dohx._testObject);
	doh.util.mixin(ret, obj);
	return ret;
};

dohx.extendManualTestObject = function(obj){
	var ret = {};
	doh.util.mixin(ret, dohx._manualTestObject);
	doh.util.mixin(ret, obj);
	if (!ret.instructions){
		ret.instructions = "Click 'GO' to run test!";
	}
	if (ret.tearDown){
		var _actualTearDown = ret.tearDown;
		ret.tearDown = function(t){
			ret._tearDown(t); // Make sure our tearDown is called first, so the actual one can fail.
			_actualTearDown.apply(ret, [t]);
		}
	} else {
		ret.tearDown = ret._tearDown;
	}
	return ret;
};

doh.assert.assertJilException = function(e, excType){
	// If multiple asserts would work the following would be easier, but doh2 is not capable of that (yet).
	var isInstanceof = e instanceof Widget.Exception;
	var isExcTypeOk = e.type == excType
	var msg = !isInstanceof ? "Exception not instance of Widget.Exception" :
				(!isExcTypeOk ? "Exception.type is not the expected '" + excType + "' but was '"+ e.type +"'": "");
	doh.assert.assertTrue(isInstanceof && isExcTypeOk, msg);
}
<commit_msg>Show the ID in the header.<commit_after>doh._numNotApplicable = 0;
dohx = {
	add:function(obj){
		var tests = obj.tests;
		// Add requiredObjects to each tests.
		// TODO maybe add this magically as a test and make all tests just depend on it.
		if (obj.requiredObjects){
			for (var i=0, t; t=tests[i]; i++){
				t.requiredObjects = t.requiredObjects ? (t.requiredObjects.concat(obj.requiredObjects)) : obj.requiredObjects;
			}
		}
		var dohTests = [];
		for (var i=0, t; t=tests[i]; i++){
			// Copy the addIf=false value to each test of a group.
			if ("addIf" in obj){
				t.addIf = !!obj.addIf;
			}
			if (t.instructions || t.expectedResult){
				dohTests.push(dohx.extendManualTestObject(t));
			} else if(t.timeout){
				t.instructions = "Click 'GO' to run test.";
				dohTests.push(dohx.extendManualTestObject(t));
			} else {
				dohTests.push(dohx.extendTestObject(t));
			}
		}
		// If obj.name=="" doh runs the tests forever, funny bug.
		doh.register(obj.name || "Noname", dohTests);
	}
};

dohx._testObject = {
	name:"",
	requiredObjects:[],
	timeout:5 * 1000, // The phone might be a bit slow, so give it some time :-).

	getMissingRequiredObjects:function(){
		var ret = [];
		for (var i=0, o; o=this.requiredObjects[i]; i++){
			// Split up the object name by "." and check each part singlely to report
			// more detailed error, e.g. "Widget.Device.foo" maybe already "Widget" is
			// not defined. So report "'Widget' is not implemented" instead of
			// "'Widget.Device.foo' is not implemented."
			var parts = o.split(".");
			for (var j=0, l=parts.length, p; j<l; j++){
				p = parts.slice(0, j+1).join(".");
				if (!util.isObject(p)){
					ret.push(p);
				}
			}
		}
		return ret;
	},

	_runTest:function(t){
		var missing = this.getMissingRequiredObjects();
		if (missing.length){
			t.failure("'" + missing[0] + "' is not implemented!");
			return;
		}
		if (this.requiredObjects.length>0 && !this.test){
			t.assertTrue(true);
			return;
		}
		if (typeof this.test=="function"){
			// Maybe no test is given.
			return this.test(t);
		}
	},

	runTest:function(t){
		window.scrollTo(0, 0); // Show the manual test overlay always at top.
		return this._runTest(t)
	}
};

dohx._manualTestObject = {};
doh.util.mixin(dohx._manualTestObject, dohx._testObject);
doh.util.mixin(dohx._manualTestObject, {
	timeout:1 * 60 * 1000,
	instructions:"",

	_goClicked:function(){
		util.query(".manualTest .whatToDo .goButton")[0].setAttribute("disabled", "disabled");
		var t = doh._current.test; // We cant use this, since we dont have the context, so we use t.
		if (t.instructions && !t.expectedResult){
			doh._runTest();
			if (doh._testInFlight){ // If the test didn't finish yet show the countdown, otherwise don't.
				ui.dialog.showCountDown(t.startTime, t.timeout);
			}
		} else if (t.expectedResult){
			// If we need the user to confirm the test result, because we cant do this programmatically
			// we just run the test and trigger _runTest() later with the "user says ..." as result.
			try{
				if (t.setUp) t.setUp();
				t.test(t);
			}catch(e){
				// If the test call itself fails make the test fail too, so we dont "swallow" error messages.
				doh._current.test.test = function(t){
					throw e;
				};
				// Run the actual test, but DONT run setUp() since we already executed it before test(), see above.
				var oldSetUp = t.setUp;
				t.setUp = function(){};
				doh._runTest();
				t.setUp = oldSetUp; // Put back the old setup.
//console.log(e); maybe no test function is implemented, is possible and valid. See e.g. PowerInfo it makes use of that.
			}
			util.style(".manualTest .result", {display:"block"});
		}
	},

	_tearDown:function(){
// TODO implement calling the actual tearDown!!!!
		ui.dialog.hide();
	},
	_successfulTestFunction:function(){
		doh._current.test.setUp = function(){};
		doh._current.test.test = function(t){
			t.success("User confirmed success, pressed 'Yes'.");
		};
		doh._runTest();
	},
	_failTestFunction:function(){
		doh._current.test.setUp = function(){};
		doh._current.test.test = function(t){
			t.failure("User declined success, pressed 'No'.")
		};
		doh._runTest();
	}
});
// We are connecting on the actual dohx._manualTestObject object itself, which
// means each of the function CANT access the current test through "this", but
// must use "doh._current.test" to access the current test!
// We could of course connect each time for each test, that would just mean we
// would also have to clean up, too much hazzle imho. This way it's just scope mangling :-).
util.connect(".manualTest .whatToDo .goButton", "onclick", doh.util.hitch(dohx._manualTestObject, "_goClicked"));
util.connect(".manualTest .result .yesButton", "onclick", doh.util.hitch(dohx._manualTestObject, "_successfulTestFunction"));
util.connect(".manualTest .result .noButton", "onclick", doh.util.hitch(dohx._manualTestObject, "_failTestFunction"));

doh._runNextTest = function(){
	do{
		var hasNextTest = this._selectNextTest();
		if (!hasNextTest){
			return;
		}
		var c = this._current;
		if (c.test.addIf===false){
			// Pass in the following structure, which is actually created inside
			// doh, but we don't pass the test to doh.
			var test = {};
			doh.util.mixin(test, c.test);
			test.group = c.group;
// TODO I dont know if this is proper in this place.
			doh._numNotApplicable++;
			ui.notApplicable(test);
		}
	}while(c.test.addIf===false);
	var missingReqObj = c.test.getMissingRequiredObjects();
	// If there are requiredObjects that don't exist don't even show the dialog.
	if ((c.test.expectedResult || c.test.instructions) && missingReqObj.length==0){
		// Show the instructions. The click on 'GO' will then call this._runTest().
		var instructions;
		if (util.isArray(c.test.instructions)){
			// If it is an array, prefix every line with an increasing number.
			instructions = [];
			for (var i=0, l=c.test.instructions.length; i<l; i++){
				instructions.push((i+1) +") " + c.test.instructions[i]);
			}
			instructions = instructions.join('
');
		} else {
			instructions = c.test.instructions;
		}
		ui.dialog.show(c.test.id + ": " + c.test.name, instructions, c.test.expectedResult);
	} else {
		this._runTest();
	}
	return;
}

doh.config.testFunctionName = "runTest";

dohx.showInfo = function(){
	ui.showInfo(Array.prototype.slice.apply(arguments).join(" "));
}
dohx.hideInfo = function(){
	ui.hideInfo();
}

dohx.extendTestObject = function(obj){
	// summary: Clone the doh._testObject and add the properties of obj.
	var ret = {};
	doh.util.mixin(ret, dohx._testObject);
	doh.util.mixin(ret, obj);
	return ret;
};

dohx.extendManualTestObject = function(obj){
	var ret = {};
	doh.util.mixin(ret, dohx._manualTestObject);
	doh.util.mixin(ret, obj);
	if (!ret.instructions){
		ret.instructions = "Click 'GO' to run test!";
	}
	if (ret.tearDown){
		var _actualTearDown = ret.tearDown;
		ret.tearDown = function(t){
			ret._tearDown(t); // Make sure our tearDown is called first, so the actual one can fail.
			_actualTearDown.apply(ret, [t]);
		}
	} else {
		ret.tearDown = ret._tearDown;
	}
	return ret;
};

doh.assert.assertJilException = function(e, excType){
	// If multiple asserts would work the following would be easier, but doh2 is not capable of that (yet).
	var isInstanceof = e instanceof Widget.Exception;
	var isExcTypeOk = e.type == excType
	var msg = !isInstanceof ? "Exception not instance of Widget.Exception" :
				(!isExcTypeOk ? "Exception.type is not the expected '" + excType + "' but was '"+ e.type +"'": "");
	doh.assert.assertTrue(isInstanceof && isExcTypeOk, msg);
}
<|endoftext|>


<commit_before>var
  _ = require('lodash'),
  config = require('./config')(),
  amqp = require('amqplib'),
  Promise = require('bluebird'),
  uuid = require('node-uuid'),
  KUZZLE_EXCHANGE = 'amq.topic',
  ApiRT = require('./apiRT');

/** CONSTRUCT **/
var ApiAMQP = function () {
  this.amqpClient = null;
  this.amqpChannel = null;

  ApiRT.call(this);
};
ApiAMQP.prototype = new ApiRT();

/** SPECIFIC FOR AMQP */
ApiAMQP.prototype.init = function (world) {
  this.world = world;
  this.responses = null;
  this.clientId = uuid.v4();
  this.subscribedRooms = {};

  if (!this.amqpClient) {
    this.amqpClient = amqp.connect(config.amqpUrl);
    this.amqpChannel = this.amqpClient.then(function (connection) {
      return connection.createChannel();
    });
  }
};

ApiAMQP.prototype.disconnect = function () {
  if (this.amqpClient) {
    this.amqpClient
      .then(connection => connection.close.bind(connection))
      .catch(err => console.error('error= ', err));

    this.amqpClient = null;
    this.amqpChannel = null;
  }
};

ApiAMQP.prototype.send = function (message, waitForAnswer) {
  var
    topic = 'kuzzle',
    listen = (waitForAnswer !== undefined) ? waitForAnswer : true;

  if (!message.clientId) {
    message.clientId = this.clientId;
  }

  message.metadata = this.world.metadata;

  if (this.world.currentUser && this.world.currentUser.token) {
    if (!message.headers) {
      message.headers = {};
    }
    message.headers = _.extend(message.headers, {authorization: 'Bearer ' + this.world.currentUser.token});
  }

  return new Promise((resolve, reject) => {
    this.amqpChannel.then(channel => {
      if (listen) {
        channel.assertQueue(null, {autoDelete: true, exclusive: true, durable: false})
          .then(queue => {
            channel.consume(queue.queue, reply => {
              channel.ack(reply);
              channel.cancel(reply.fields.consumerTag).then(() => {
                var unpacked = JSON.parse((new Buffer(reply.content)).toString());

                if (unpacked.error) {
                  unpacked.error.statusCode = unpacked.status;
                  return reject(unpacked.error);
                }

                resolve(unpacked);
              });
            })
            .then(() => {
              channel.publish(KUZZLE_EXCHANGE, topic, new Buffer(JSON.stringify(message)), {replyTo: queue.queue});
            });
          })
          .catch(error => reject(error.message));
      }
      else {
        channel.publish(KUZZLE_EXCHANGE, topic, new Buffer(JSON.stringify(message)));
        return resolve({});
      }
    });
  });
};

ApiAMQP.prototype.sendAndListen = function (message) {
  var
    sendResponse,
    channel;

  message.clientId = uuid.v4();
  this.subscribedRooms[message.clientId] = {};

  return this.send(message)
    .then(response => {
      sendResponse = response;
      return this.amqpClient;
    })
    .then(connection => connection.createChannel())
    .then(response => {
      channel = response;
      this.subscribedRooms[message.clientId][sendResponse.result.roomId] = channel;
      return channel.assertQueue(sendResponse.result.channel);
    })
    .then(() => channel.bindQueue(sendResponse.result.channel, KUZZLE_EXCHANGE, sendResponse.result.channel))
    .then(() => {
      channel.consume(sendResponse.result.channel, reply => {
        var notification = JSON.parse((new Buffer(reply.content)).toString());
        channel.ack(reply);
        this.responses = notification;
      });

      return sendResponse;
    })
    .catch(error => Promise.reject(new Error(error.message)));
};

module.exports = ApiAMQP;
<commit_msg>improved AMQP client disconnection in functional tests<commit_after>var
  _ = require('lodash'),
  config = require('./config')(),
  amqp = require('amqplib'),
  Promise = require('bluebird'),
  uuid = require('node-uuid'),
  KUZZLE_EXCHANGE = 'amq.topic',
  ApiRT = require('./apiRT');

/** CONSTRUCT **/
var ApiAMQP = function () {
  this.amqpClient = null;
  this.amqpChannel = null;

  ApiRT.call(this);
};
ApiAMQP.prototype = new ApiRT();

/** SPECIFIC FOR AMQP */
ApiAMQP.prototype.init = function (world) {
  this.world = world;
  this.responses = null;
  this.clientId = uuid.v4();
  this.subscribedRooms = {};

  if (!this.amqpClient) {
    this.amqpClient = amqp.connect(config.amqpUrl);
    this.amqpChannel = this.amqpClient.then(function (connection) {
      return connection.createChannel();
    });
  }
};

ApiAMQP.prototype.disconnect = function () {
  if (this.amqpClient) {
    this.amqpClient
      .then(connection => {
        connection.close.bind(connection);
        this.amqpClient = null;
        this.amqpChannel = null;
      });
  }
};

ApiAMQP.prototype.send = function (message, waitForAnswer) {
  var
    topic = 'kuzzle',
    listen = (waitForAnswer !== undefined) ? waitForAnswer : true;

  if (!message.clientId) {
    message.clientId = this.clientId;
  }

  message.metadata = this.world.metadata;

  if (this.world.currentUser && this.world.currentUser.token) {
    if (!message.headers) {
      message.headers = {};
    }
    message.headers = _.extend(message.headers, {authorization: 'Bearer ' + this.world.currentUser.token});
  }

  return new Promise((resolve, reject) => {
    this.amqpChannel.then(channel => {
      if (listen) {
        channel.assertQueue(null, {autoDelete: true, exclusive: true, durable: false})
          .then(queue => {
            channel.consume(queue.queue, reply => {
              channel.ack(reply);
              channel.cancel(reply.fields.consumerTag).then(() => {
                var unpacked = JSON.parse((new Buffer(reply.content)).toString());

                if (unpacked.error) {
                  unpacked.error.statusCode = unpacked.status;
                  return reject(unpacked.error);
                }

                resolve(unpacked);
              });
            })
            .then(() => {
              channel.publish(KUZZLE_EXCHANGE, topic, new Buffer(JSON.stringify(message)), {replyTo: queue.queue});
            });
          })
          .catch(error => reject(error.message));
      }
      else {
        channel.publish(KUZZLE_EXCHANGE, topic, new Buffer(JSON.stringify(message)));
        return resolve({});
      }
    });
  });
};

ApiAMQP.prototype.sendAndListen = function (message) {
  var
    sendResponse,
    channel;

  message.clientId = uuid.v4();
  this.subscribedRooms[message.clientId] = {};

  return this.send(message)
    .then(response => {
      sendResponse = response;
      return this.amqpClient;
    })
    .then(connection => connection.createChannel())
    .then(response => {
      channel = response;
      this.subscribedRooms[message.clientId][sendResponse.result.roomId] = channel;
      return channel.assertQueue(sendResponse.result.channel);
    })
    .then(() => channel.bindQueue(sendResponse.result.channel, KUZZLE_EXCHANGE, sendResponse.result.channel))
    .then(() => {
      channel.consume(sendResponse.result.channel, reply => {
        var notification = JSON.parse((new Buffer(reply.content)).toString());
        channel.ack(reply);
        this.responses = notification;
      });

      return sendResponse;
    })
    .catch(error => Promise.reject(new Error(error.message)));
};

module.exports = ApiAMQP;
<|endoftext|>