<commit_before>/*
 * Copyright (c) 2014 Modulus
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

module.exports = function (modulus) {
  var help = new modulus.help('Project', modulus);

  // list commands
  help.add('list', function () {
    this.line('project list (list)'.verbose);
    this.line('View all projects you have.'.input);
  });

  modulus.program
    .command('project list')
    .description('Lists all projects you currently have access to.')
    .on('--help', help.commands.list)
    .action(function () {
      modulus.runCommand(modulus.commands.project.list, true);
    });

  modulus.program
    .command('list')
    .description('Lists all projects you currently have access to.')
    .on('--help', help.commands.list)
    .action(function () {
      modulus.runCommand(modulus.commands.project.list, true);
    });

  // create command
  help.add('create', function () {
    this.line('project create <name>'.verbose);
    this.line('Creates a new project.'.input);
    this.line('A prompt for name will occur if none is specified.'.input);
    this.line('  options:'.input);
    this.line('    -s, --servo-size    Servo size for the new project [192, 396, 512, 1024, 2048].'.input);
    this.line('    -r, --runtime       Runtime for the new project [Node.js, PHP, Java, Static].'.input);
  });

  modulus.program.option(
    '-s, --servo-size [value]',
    'Servo size for the new project [192, 396, 512, 1024, 2048]');

  modulus.program.option(
    '-r, --runtime [value]',
    'Runtime for the new project [Node.js, PHP, Java, Static]');

  modulus.program
    .command('project create')
    .description('Creates a new project.')
    .on('--help', help.commands.create)
    .action(function (name) {
      if (typeof name !== 'string') {
        name = '';
      }
      modulus.runCommand(
        modulus.commands.project.create,
        [name, modulus.program.servoSize, modulus.program.runtime],
        true);
    });

  // delete command
  help.add('delete', function () {
    this.line('project delete'.verbose);
    this.line('Deletes a project.'.input);
    this.line('A prompt for name will occur if none is specified.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to restart. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project delete')
    .description('Deletes a project')
    .on('--help', help.commands.delete)
    .action(function() {
      modulus.runCommand(modulus.commands.project.delete, [modulus.program.projectName], true);
    });

  // restart command
  help.add('restart', function () {
    this.line('project restart'.verbose);
    this.line('Restarts a project.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to restart. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project restart')
    .description('Restarts a running project.')
    .on('--help', help.commands.restart)
    .action(function () {
      modulus.runCommand(modulus.commands.project.restart, [modulus.program.projectName], true);
    });

  // stop command
  help.add('stop', function () {
    this.line('project stop'.verbose);
    this.line('Stops a running project'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to stop. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project stop')
    .description('Stops a running project.')
    .on('--help', help.commands.stop)
    .action(function () {
      modulus.runCommand(modulus.commands.project.stop, [modulus.program.projectName], true);
    });

  // start command
  help.add('start', function () {
    this.line('project start'.verbose);
    this.line('Starts a project'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to start. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project start')
    .description('Starts a stopped project.')
    .on('--help', help.commands.start)
    .action(function () {
      modulus.runCommand(modulus.commands.project.start, [modulus.program.projectName], true);
    });

  // deploy command
  help.add('deploy', function () {
    this.line('project deploy (deploy) [options] <directory>'.verbose);
    this.line('Deploys a directory\'s contents to the selected project.'.input);
    this.line('The current working directory is used if no directory is specified.'.input);
    this.line('By default a project type will be detected and any additional actions required will be taken.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name      Name of the project to deploy. Prompts are skipped when specified.'.input);
    this.line('    -m, --include-modules   Flag that indicates whether or not to include the node_modules folder in the bundle.'.input);
    this.line('    -r, --registry          Specify a registry to install modules from.'.input);
    this.line('    -w, --with-tests        Specifies that tests should be run before deploying this project.'.input);
    this.line('    -D, --meteor-debug      Override debug mode for meteor project.'.input);
  });

  modulus.program
    .option('-p, --project-name [value]', 'Name of the project to retrieve logs from. Prompts are skipped when specified', null)
    .option('-d, --download', 'Flag that signals to download the logs instead of printing them.')
    .option('-o, --output [value]', 'Specifies the file to download to. Must be file type tar.gz')
    .option('-m, --include-modules', 'Flag that indicates whether or not to include the node_modules folder in the bundle.')
    .option('-r, --registry [value]', 'Specify a registry to install modules from', '')
    .option('-w, --with-tests', 'Specifies that tests should be run before deploying this project.')
    .option('-D, --meteor-debug', 'Override debug mode for meteor projects.');

  modulus.program
    .command('project deploy')
    .description('Deploys the current directory to selected project.')
    .on('--help', help.commands.deploy)
    .action(function (dir) {
      modulus.runCommand(
        modulus.commands.project.deploy,
        [
          modulus.program.projectName,
          dir,
          modulus.program.includeModules,
          modulus.program.registry,
          modulus.program.meteorDebug,
          modulus.program.withTests
        ],
        true);
    });

  modulus.program
    .command('deploy')
    .description('Deploys the current directory to selected project.')
    .on('--help', help.commands.deploy)
    .action(function (dir) {
      modulus.runCommand(
        modulus.commands.project.deploy,
        [
          modulus.program.projectName,
          dir,
          modulus.program.includeModules,
          modulus.program.registry,
          modulus.program.meteorDebug,
          modulus.program.withTests
        ],
        true);
    });

  // logs command
  help.add('logs', function () {
    this.line('project logs'.verbose);
    this.line('Gets logs for the selected project.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name   Name of the project to retrieve logs from. Prompts are skipped when specified.'.input);
    this.line('    -d, --download       Flag that signals to download the logs instead of printing them.'.input);
    this.line('    -o, --output         Specifies the file to download to. Must be file type tar.gz'.input);
    this.line('');
  });

  // help for logs clear command
  help.add('logClear', function () {
    this.line('project logs clear'.verbose);
    this.line('Cleans logs for the selected project.'.input);
  });

  var logsHandler = function (dir) {
    modulus.runCommand(
      modulus.commands.project.getLogs,
      [
        modulus.program.projectName,
        modulus.program.download,
        modulus.program.output,
        dir
      ],
      true);
  };

  modulus.program
    .command('project logs')
    .description('Gets logs for the selected project.')
    .on('--help', function () {
      help.commands.logs();
      help.commands.logClear();
    })
    .action(logsHandler);

  // project logs -> project log alias
  modulus.program
    .command('project log')
    .description('Gets logs for the selected project.')
    .on('--help', function () {
      help.commands.logs();
      help.commands.logClear();
    })
    .action(logsHandler);

  // project logs -> logs alias
  modulus.program
    .command('logs')
    .description('Gets logs for the selected project.')
    .on('--help', function () {
      help.commands.logs();
      help.commands.logClear();
    })
    .action(logsHandler);

  // log clear command
  modulus.program
    .command('project logs clear')
    .description('Clears logs for the selected project.')
    .on('--help', help.commands.logClear)
    .action(function () {
      modulus.runCommand(modulus.commands.project.clearLogs, [modulus.program.projectName], true);
    });

  // project logs clear -> logs clear alias
  modulus.program
    .command('logs clear')
    .description('Clears logs for the selected project.')
    .on('--help', help.commands.logClear)
    .action(function () {
      modulus.runCommand(modulus.commands.project.clearLogs, [modulus.program.projectName], true);
    });

  // log stream command
  help.add('logStream', function () {
    this.line('project logs tail'.verbose);
    this.line('Streams logs for the selected project.'.input);
    this.line('  options:'.input);
    this.line('    -S, --servo    Index of a specific servo to tail. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .option('-S, --servo [value]', 'Index of a specific servo to tail. Prompts are skipped when specified.');

  modulus.program
    .command('project logs tail')
    .description('Streams logs for the selected project.')
    .on('--help', help.commands.logStream)
    .action(function () {
      modulus.runCommand(modulus.commands.project.streamLogs, [modulus.program.projectName, modulus.program.servo], true);
    });

  // project logs tail -> logs tail alias
  modulus.program
    .command('logs tail')
    .description('Streams logs for the selected project.')
    .on('--help', help.commands.logStream)
    .action(function () {
      modulus.runCommand(modulus.commands.project.streamLogs, [modulus.program.projectName, modulus.program.servo], true);
    });

  // scale command
  help.add('scale', function () {
    this.line('project scale <servos>'.verbose);
    this.line('Scales a project to use the number of servos given.'.input);
    this.line('    -p, --project-name   Name of the project to scale. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .option('-p, --project-name [value]', 'Name of the project to scale. Prompts are skipped when specified');

  modulus.program
    .command('project scale')
    .description('Scales a project to the number of servos specified.')
    .on('--help', help.commands.scale)
    .action(function () {
      var args = Array.prototype.slice.call(arguments);
      args.pop(); // remove last element, it's always internal commander stuff.
      modulus.runCommand(modulus.commands.project.scale, [args, modulus.program.projectName], true);
    });

  help.add('run', function() {
    this.line('project run (run)'.verbose);
    this.line('Run your node application locally with your environment variables.'.input);
    this.line('Please note you must be within your applications root directory.'.input);
    this.line('A main file must be specified in the package.json'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to obtain env vars. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('run')
    .description('Run your node application locally with your environment variables. Please note you must be within your applications root directory')
    .on('--help', help.commands.run)
    .action(function(envVars) {
      modulus.runCommand(modulus.commands.project.runApp, [modulus.program.projectName], true);
    });

  modulus.program
    .command('project run')
    .description('Run your node application locally with your environment variables. Please note you must be within your applications root directory')
    .on('--help', help.commands.run)
    .action(function(envVars) {
      modulus.runCommand(modulus.commands.project.runApp, [modulus.program.projectName], true);
    });

  // Generic help for project commands
  modulus.program
    .command('project')
    .description('Lists available project commands.')
    .on('--help', help.getPrinter())
    .action(help.getPrinter());

  return {
    base : 'project',
    help : help
  };
};
<commit_msg>Add flag to set node env from cli with flag `-n`<commit_after>/*
 * Copyright (c) 2014 Modulus
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

module.exports = function (modulus) {
  var help = new modulus.help('Project', modulus);

  // list commands
  help.add('list', function () {
    this.line('project list (list)'.verbose);
    this.line('View all projects you have.'.input);
  });

  modulus.program
    .command('project list')
    .description('Lists all projects you currently have access to.')
    .on('--help', help.commands.list)
    .action(function () {
      modulus.runCommand(modulus.commands.project.list, true);
    });

  modulus.program
    .command('list')
    .description('Lists all projects you currently have access to.')
    .on('--help', help.commands.list)
    .action(function () {
      modulus.runCommand(modulus.commands.project.list, true);
    });

  // create command
  help.add('create', function () {
    this.line('project create <name>'.verbose);
    this.line('Creates a new project.'.input);
    this.line('A prompt for name will occur if none is specified.'.input);
    this.line('  options:'.input);
    this.line('    -s, --servo-size    Servo size for the new project [192, 396, 512, 1024, 2048].'.input);
    this.line('    -r, --runtime       Runtime for the new project [Node.js, PHP, Java, Static].'.input);
  });

  modulus.program.option(
    '-s, --servo-size [value]',
    'Servo size for the new project [192, 396, 512, 1024, 2048]');

  modulus.program.option(
    '-r, --runtime [value]',
    'Runtime for the new project [Node.js, PHP, Java, Static]');

  modulus.program
    .command('project create')
    .description('Creates a new project.')
    .on('--help', help.commands.create)
    .action(function (name) {
      if (typeof name !== 'string') {
        name = '';
      }
      modulus.runCommand(
        modulus.commands.project.create,
        [name, modulus.program.servoSize, modulus.program.runtime],
        true);
    });

  // delete command
  help.add('delete', function () {
    this.line('project delete'.verbose);
    this.line('Deletes a project.'.input);
    this.line('A prompt for name will occur if none is specified.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to restart. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project delete')
    .description('Deletes a project')
    .on('--help', help.commands.delete)
    .action(function() {
      modulus.runCommand(modulus.commands.project.delete, [modulus.program.projectName], true);
    });

  // restart command
  help.add('restart', function () {
    this.line('project restart'.verbose);
    this.line('Restarts a project.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to restart. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project restart')
    .description('Restarts a running project.')
    .on('--help', help.commands.restart)
    .action(function () {
      modulus.runCommand(modulus.commands.project.restart, [modulus.program.projectName], true);
    });

  // stop command
  help.add('stop', function () {
    this.line('project stop'.verbose);
    this.line('Stops a running project'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to stop. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project stop')
    .description('Stops a running project.')
    .on('--help', help.commands.stop)
    .action(function () {
      modulus.runCommand(modulus.commands.project.stop, [modulus.program.projectName], true);
    });

  // start command
  help.add('start', function () {
    this.line('project start'.verbose);
    this.line('Starts a project'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to start. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .command('project start')
    .description('Starts a stopped project.')
    .on('--help', help.commands.start)
    .action(function () {
      modulus.runCommand(modulus.commands.project.start, [modulus.program.projectName], true);
    });

  // deploy command
  help.add('deploy', function () {
    this.line('project deploy (deploy) [options] <directory>'.verbose);
    this.line('Deploys a directory\'s contents to the selected project.'.input);
    this.line('The current working directory is used if no directory is specified.'.input);
    this.line('By default a project type will be detected and any additional actions required will be taken.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name      Name of the project to deploy. Prompts are skipped when specified.'.input);
    this.line('    -m, --include-modules   Flag that indicates whether or not to include the node_modules folder in the bundle.'.input);
    this.line('    -r, --registry          Specify a registry to install modules from.'.input);
    this.line('    -w, --with-tests        Specifies that tests should be run before deploying this project.'.input);
    this.line('    -D, --meteor-debug      Override debug mode for meteor project.'.input);
  });

  modulus.program
    .option('-p, --project-name [value]', 'Name of the project to retrieve logs from. Prompts are skipped when specified', null)
    .option('-d, --download', 'Flag that signals to download the logs instead of printing them.')
    .option('-o, --output [value]', 'Specifies the file to download to. Must be file type tar.gz')
    .option('-m, --include-modules', 'Flag that indicates whether or not to include the node_modules folder in the bundle.')
    .option('-r, --registry [value]', 'Specify a registry to install modules from', '')
    .option('-w, --with-tests', 'Specifies that tests should be run before deploying this project.')
    .option('-D, --meteor-debug', 'Override debug mode for meteor projects.')
    .option('-n, --node-env [value]', 'Specifies node version when running application using `modulus run`.');

  modulus.program
    .command('project deploy')
    .description('Deploys the current directory to selected project.')
    .on('--help', help.commands.deploy)
    .action(function (dir) {
      modulus.runCommand(
        modulus.commands.project.deploy,
        [
          modulus.program.projectName,
          dir,
          modulus.program.includeModules,
          modulus.program.registry,
          modulus.program.meteorDebug,
          modulus.program.withTests
        ],
        true);
    });

  modulus.program
    .command('deploy')
    .description('Deploys the current directory to selected project.')
    .on('--help', help.commands.deploy)
    .action(function (dir) {
      modulus.runCommand(
        modulus.commands.project.deploy,
        [
          modulus.program.projectName,
          dir,
          modulus.program.includeModules,
          modulus.program.registry,
          modulus.program.meteorDebug,
          modulus.program.withTests
        ],
        true);
    });

  // logs command
  help.add('logs', function () {
    this.line('project logs'.verbose);
    this.line('Gets logs for the selected project.'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name   Name of the project to retrieve logs from. Prompts are skipped when specified.'.input);
    this.line('    -d, --download       Flag that signals to download the logs instead of printing them.'.input);
    this.line('    -o, --output         Specifies the file to download to. Must be file type tar.gz'.input);
    this.line('');
  });

  // help for logs clear command
  help.add('logClear', function () {
    this.line('project logs clear'.verbose);
    this.line('Cleans logs for the selected project.'.input);
  });

  var logsHandler = function (dir) {
    modulus.runCommand(
      modulus.commands.project.getLogs,
      [
        modulus.program.projectName,
        modulus.program.download,
        modulus.program.output,
        dir
      ],
      true);
  };

  modulus.program
    .command('project logs')
    .description('Gets logs for the selected project.')
    .on('--help', function () {
      help.commands.logs();
      help.commands.logClear();
    })
    .action(logsHandler);

  // project logs -> project log alias
  modulus.program
    .command('project log')
    .description('Gets logs for the selected project.')
    .on('--help', function () {
      help.commands.logs();
      help.commands.logClear();
    })
    .action(logsHandler);

  // project logs -> logs alias
  modulus.program
    .command('logs')
    .description('Gets logs for the selected project.')
    .on('--help', function () {
      help.commands.logs();
      help.commands.logClear();
    })
    .action(logsHandler);

  // log clear command
  modulus.program
    .command('project logs clear')
    .description('Clears logs for the selected project.')
    .on('--help', help.commands.logClear)
    .action(function () {
      modulus.runCommand(modulus.commands.project.clearLogs, [modulus.program.projectName], true);
    });

  // project logs clear -> logs clear alias
  modulus.program
    .command('logs clear')
    .description('Clears logs for the selected project.')
    .on('--help', help.commands.logClear)
    .action(function () {
      modulus.runCommand(modulus.commands.project.clearLogs, [modulus.program.projectName], true);
    });

  // log stream command
  help.add('logStream', function () {
    this.line('project logs tail'.verbose);
    this.line('Streams logs for the selected project.'.input);
    this.line('  options:'.input);
    this.line('    -S, --servo    Index of a specific servo to tail. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .option('-S, --servo [value]', 'Index of a specific servo to tail. Prompts are skipped when specified.');

  modulus.program
    .command('project logs tail')
    .description('Streams logs for the selected project.')
    .on('--help', help.commands.logStream)
    .action(function () {
      modulus.runCommand(modulus.commands.project.streamLogs, [modulus.program.projectName, modulus.program.servo], true);
    });

  // project logs tail -> logs tail alias
  modulus.program
    .command('logs tail')
    .description('Streams logs for the selected project.')
    .on('--help', help.commands.logStream)
    .action(function () {
      modulus.runCommand(modulus.commands.project.streamLogs, [modulus.program.projectName, modulus.program.servo], true);
    });

  // scale command
  help.add('scale', function () {
    this.line('project scale <servos>'.verbose);
    this.line('Scales a project to use the number of servos given.'.input);
    this.line('    -p, --project-name   Name of the project to scale. Prompts are skipped when specified.'.input);
  });

  modulus.program
    .option('-p, --project-name [value]', 'Name of the project to scale. Prompts are skipped when specified');

  modulus.program
    .command('project scale')
    .description('Scales a project to the number of servos specified.')
    .on('--help', help.commands.scale)
    .action(function () {
      var args = Array.prototype.slice.call(arguments);
      args.pop(); // remove last element, it's always internal commander stuff.
      modulus.runCommand(modulus.commands.project.scale, [args, modulus.program.projectName], true);
    });

  help.add('run', function() {
    this.line('project run (run)'.verbose);
    this.line('Run your node application locally with your environment variables.'.input);
    this.line('Please note you must be within your applications root directory.'.input);
    this.line('A main file must be specified in the package.json'.input);
    this.line('  options:'.input);
    this.line('    -p, --project-name    Name of the project to obtain env vars. Prompts are skipped when specified.'.input);
    this.line('    -n, --node-env        Name of the node environment to run the application under. Defaults to "development".'.input);
  });

  modulus.program
    .command('run')
    .description('Run your node application locally with your environment variables. Please note you must be within your applications root directory')
    .on('--help', help.commands.run)
    .action(function(envVars) {
      modulus.runCommand(modulus.commands.project.runApp, [modulus.program.projectName, modulus.program.nodeEnv], true);
    });

  modulus.program
    .command('project run')
    .description('Run your node application locally with your environment variables. Please note you must be within your applications root directory')
    .on('--help', help.commands.run)
    .action(function(envVars) {
      modulus.runCommand(modulus.commands.project.runApp, [modulus.program.projectName, modulus.program.nodeEnv], true);
    });

  // Generic help for project commands
  modulus.program
    .command('project')
    .description('Lists available project commands.')
    .on('--help', help.getPrinter())
    .action(help.getPrinter());

  return {
    base : 'project',
    help : help
  };
};
<|endoftext|>


<commit_before>function makeAckable(send) {

  var currentId = 1; // increment with each ackable message sent

  var messages = [];

  function resend() {
    var resendCutoff = Date.now() - 2500;
    messages.forEach(function(m) {
      if (m.at < resendCutoff) send(m.t, m.d);
    });
  }

  setInterval(resend, 1000);

  return {
    resend: resend,
    register: function(t, d) {
      d.a = currentId++;
      messages.push({
        t: t,
        d: d,
        at: Date.now()
      });
    },
    gotAck: function(id) {
      messages = messages.filter(function(m) {
        return m.d.a !== id;
      });
    }
  };
}

// versioned events, acks, retries, resync
lichess.StrongSocket = function(url, version, settings) {

  var now = Date.now;

  var settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
  var options = settings.options;
  var ws;
  var pingSchedule;
  var connectSchedule;
  var ackable = makeAckable(function(t, d) { send(t, d) });
  var lastPingTime = now();
  var pongCount = 0;
  var averageLag = 0;
  var tryOtherUrl = false;
  var autoReconnect = true;
  var nbConnects = 0;
  var storage = lichess.storage.make('surl6');

  var connect = function() {
    destroy();
    autoReconnect = true;
    var params = $.param(settings.params);
    if (version !== false) params += (params ? '&' : '') + 'v=' + version;
    var fullUrl = options.protocol + '//' + baseUrl() + url + '?' + params;
    debug("connection attempt to " + fullUrl);
    try {
      ws = new WebSocket(fullUrl);
      ws.onerror = function(e) {
        onError(e);
      };
      ws.onclose = function() {
        if (autoReconnect) {
          debug('Will autoreconnect in ' + options.autoReconnectDelay);
          scheduleConnect(options.autoReconnectDelay);
        }
      };
      ws.onopen = function() {
        debug("connected to " + fullUrl);
        onSuccess();
        $('body').removeClass('offline').addClass('online').addClass(nbConnects > 1 ? 'reconnected' : '');
        pingNow();
        lichess.pubsub.emit('socket.open')();
        ackable.resend();
      };
      ws.onmessage = function(e) {
        if (e.data == 0) return pong();
        var m = JSON.parse(e.data);
        // if (Math.random() > 0.5) {
        //   console.log(m, 'skip');
        //   return;
        // }
        if (m.t === 'n') pong();
        // else debug(e.data);
        if (m.t === 'b') m.d.forEach(handle);
        else handle(m);
      };
    } catch (e) {
      onError(e);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var send = function(t, d, o, noRetry) {
    o = o || {};
    var msg = { t: t };
    if (d !== undefined) {
      if (o.withLag) d.l = Math.round(averageLag);
      if (o.millis >= 0) d.s = Math.round(o.millis * 0.1).toString(36);
      msg.d = d;
    }
    if (o.ackable) {
      msg.d = msg.d || {}; // can't ack message without data
      ackable.register(t, msg.d); // adds d.a, the ack ID we expect to get back
    }
    var message = JSON.stringify(msg);
    debug("send " + message);
    try {
      ws.send(message);
    } catch (e) {
      // maybe sent before socket opens,
      // try again a second later.
      if (!noRetry) setTimeout(function() {
        send(t, msg.d, o, true);
      }, 1000);
    }
  };
  lichess.pubsub.on('socket.send', send);

  var scheduleConnect = function(delay) {
    if (options.idle) delay = 10 * 1000 + Math.random() * 10 * 1000;
    // debug('schedule connect ' + delay);
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    connectSchedule = setTimeout(function() {
      $('body').addClass('offline').removeClass('online');
      tryOtherUrl = true;
      connect();
    }, delay);
  };

  var schedulePing = function(delay) {
    clearTimeout(pingSchedule);
    pingSchedule = setTimeout(pingNow, delay);
  };

  var pingNow = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    var pingData = (options.isAuth && pongCount % 8 == 2) ? JSON.stringify({
      t: 'p',
      l: Math.round(0.1 * averageLag)
    }) : null;
    try {
      ws.send(pingData);
      lastPingTime = now();
    } catch (e) {
      debug(e, true);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var computePingDelay = function() {
    return options.pingDelay + (options.idle ? 1000 : 0);
  };

  var pong = function() {
    clearTimeout(connectSchedule);
    schedulePing(computePingDelay());
    var currentLag = Math.min(now() - lastPingTime, 10000);
    pongCount++;

    // Average first 4 pings, then switch to decaying average.
    var mix = pongCount > 4 ? 0.1 : 1 / pongCount;
    averageLag += mix * (currentLag - averageLag);

    lichess.pubsub.emit('socket.lag')(averageLag);
  };

  var handle = function(m) {
    if (m.v) {
      if (m.v <= version) {
        debug("already has event " + m.v);
        return;
      }
      // it's impossible but according to previous login, it happens nonetheless
      if (m.v > version + 1) return lichess.reload();
      version = m.v;
    }
    switch (m.t || false) {
      case false:
        break;
      case 'resync':
        lichess.reload();
        break;
      case 'ack':
        ackable.gotAck(m.d);
        break;
      default:
        lichess.pubsub.emit('socket.in.' + m.t)(m.d);
        var processed = settings.receive && settings.receive(m.t, m.d);
        if (!processed && settings.events[m.t]) settings.events[m.t](m.d || null, m);
    }
  };

  var debug = function(msg, always) {
    if (always || options.debug) {
      console.debug("[" + options.name + " " + settings.params.sri + "]", msg);
    }
  };

  var destroy = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    disconnect();
    ws = null;
  };

  var disconnect = function(onNextConnect) {
    if (ws) {
      debug("Disconnect");
      autoReconnect = false;
      ws.onerror = ws.onclose = ws.onopen = ws.onmessage = $.noop;
      ws.close();
    }
    if (onNextConnect) options.onNextConnect = onNextConnect;
  };

  var onError = function(e) {
    options.debug = true;
    debug('error: ' + JSON.stringify(e));
    tryOtherUrl = true;
    clearTimeout(pingSchedule);
  };

  var onSuccess = function() {
    nbConnects++;
    if (nbConnects == 1) {
      options.onFirstConnect();
      var disconnectTimeout;
      lichess.idleTimer(10 * 60 * 1000, function() {
        options.idle = true;
        disconnectTimeout = setTimeout(lichess.socket.destroy, 2 * 60 * 60 * 1000);
      }, function() {
        options.idle = false;
        if (ws) clearTimeout(disconnectTimeout);
        else location.reload();
      });
    }
    if (options.onNextConnect) {
      options.onNextConnect();
      delete options.onNextConnect;
    }
  };

  var baseUrl = function() {
    var urls = options.baseUrls, url = storage.get();
    if (!url) {
      url = urls[0];
      storage.set(url);
    } else if (tryOtherUrl) {
      tryOtherUrl = false;
      url = urls[(urls.indexOf(url) + 1) % urls.length];
      storage.set(url);
    }
    return url;
  };

  connect();
  window.addEventListener('unload', destroy);

  return {
    connect: connect,
    disconnect: disconnect,
    send: send,
    destroy: destroy,
    options: options,
    pingInterval: function() {
      return computePingDelay() + averageLag;
    },
    averageLag: function() {
      return averageLag;
    },
    getVersion: function() {
      return version;
    }
  };
};
lichess.StrongSocket.sri = Math.random().toString(36).slice(2, 12);

lichess.StrongSocket.defaults = {
  events: {
    fen: function(e) {
      $('.mini-board-' + e.id).each(function() {
        lichess.parseFen($(this).data("fen", e.fen).data("lastmove", e.lm));
      });
    },
    challenges: function(d) {
      lichess.challengeApp.update(d);
    },
    notifications: function(d) {
      lichess.notifyApp.update(d, true);
    }
  },
  params: {
    sri: lichess.StrongSocket.sri
  },
  options: {
    name: "unnamed",
    idle: false,
    pingMaxLag: 9000, // time to wait for pong before reseting the connection
    pingDelay: 2500, // time between pong and ping
    autoReconnectDelay: 3500,
    protocol: location.protocol === 'https:' ? 'wss:' : 'ws:',
    baseUrls: (function(d) {
      return [d].concat((d === 'socket.lichess.org' ? [5, 6, 7, 8, 9] : []).map(function(port) {
        return d + ':' + (9020 + port);
      }));
    })(document.body.getAttribute('data-socket-domain')),
    onFirstConnect: $.noop
  }
};
<commit_msg>Revert "Disable VersionCheck on client"<commit_after>function makeAckable(send) {

  var currentId = 1; // increment with each ackable message sent

  var messages = [];

  function resend() {
    var resendCutoff = Date.now() - 2500;
    messages.forEach(function(m) {
      if (m.at < resendCutoff) send(m.t, m.d);
    });
  }

  setInterval(resend, 1000);

  return {
    resend: resend,
    register: function(t, d) {
      d.a = currentId++;
      messages.push({
        t: t,
        d: d,
        at: Date.now()
      });
    },
    gotAck: function(id) {
      messages = messages.filter(function(m) {
        return m.d.a !== id;
      });
    }
  };
}

// versioned events, acks, retries, resync
lichess.StrongSocket = function(url, version, settings) {

  var now = Date.now;

  var settings = $.extend(true, {}, lichess.StrongSocket.defaults, settings);
  var options = settings.options;
  var ws;
  var pingSchedule;
  var connectSchedule;
  var ackable = makeAckable(function(t, d) { send(t, d) });
  var lastPingTime = now();
  var pongCount = 0;
  var averageLag = 0;
  var tryOtherUrl = false;
  var autoReconnect = true;
  var nbConnects = 0;
  var storage = lichess.storage.make('surl6');

  var connect = function() {
    destroy();
    autoReconnect = true;
    var params = $.param(settings.params);
    if (version !== false) params += (params ? '&' : '') + 'v=' + version;
    var fullUrl = options.protocol + '//' + baseUrl() + url + '?' + params;
    debug("connection attempt to " + fullUrl);
    try {
      ws = new WebSocket(fullUrl);
      ws.onerror = function(e) {
        onError(e);
      };
      ws.onclose = function() {
        if (autoReconnect) {
          debug('Will autoreconnect in ' + options.autoReconnectDelay);
          scheduleConnect(options.autoReconnectDelay);
        }
      };
      ws.onopen = function() {
        debug("connected to " + fullUrl);
        onSuccess();
        $('body').removeClass('offline').addClass('online').addClass(nbConnects > 1 ? 'reconnected' : '');
        pingNow();
        lichess.pubsub.emit('socket.open')();
        ackable.resend();
      };
      ws.onmessage = function(e) {
        if (e.data == 0) return pong();
        var m = JSON.parse(e.data);
        // if (Math.random() > 0.5) {
        //   console.log(m, 'skip');
        //   return;
        // }
        if (m.t === 'n') pong();
        // else debug(e.data);
        if (m.t === 'b') m.d.forEach(handle);
        else handle(m);
      };
    } catch (e) {
      onError(e);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var send = function(t, d, o, noRetry) {
    o = o || {};
    var msg = { t: t };
    if (d !== undefined) {
      if (o.withLag) d.l = Math.round(averageLag);
      if (o.millis >= 0) d.s = Math.round(o.millis * 0.1).toString(36);
      msg.d = d;
    }
    if (o.ackable) {
      msg.d = msg.d || {}; // can't ack message without data
      ackable.register(t, msg.d); // adds d.a, the ack ID we expect to get back
    }
    var message = JSON.stringify(msg);
    debug("send " + message);
    try {
      ws.send(message);
    } catch (e) {
      // maybe sent before socket opens,
      // try again a second later.
      if (!noRetry) setTimeout(function() {
        send(t, msg.d, o, true);
      }, 1000);
    }
  };
  lichess.pubsub.on('socket.send', send);

  var scheduleConnect = function(delay) {
    if (options.idle) delay = 10 * 1000 + Math.random() * 10 * 1000;
    // debug('schedule connect ' + delay);
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    connectSchedule = setTimeout(function() {
      $('body').addClass('offline').removeClass('online');
      tryOtherUrl = true;
      connect();
    }, delay);
  };

  var schedulePing = function(delay) {
    clearTimeout(pingSchedule);
    pingSchedule = setTimeout(pingNow, delay);
  };

  var pingNow = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    var pingData = (options.isAuth && pongCount % 8 == 2) ? JSON.stringify({
      t: 'p',
      l: Math.round(0.1 * averageLag),
      v: version
    }) : null;
    try {
      ws.send(pingData);
      lastPingTime = now();
    } catch (e) {
      debug(e, true);
    }
    scheduleConnect(options.pingMaxLag);
  };

  var computePingDelay = function() {
    return options.pingDelay + (options.idle ? 1000 : 0);
  };

  var pong = function() {
    clearTimeout(connectSchedule);
    schedulePing(computePingDelay());
    var currentLag = Math.min(now() - lastPingTime, 10000);
    pongCount++;

    // Average first 4 pings, then switch to decaying average.
    var mix = pongCount > 4 ? 0.1 : 1 / pongCount;
    averageLag += mix * (currentLag - averageLag);

    lichess.pubsub.emit('socket.lag')(averageLag);
  };

  var handle = function(m) {
    if (m.v) {
      if (m.v <= version) {
        debug("already has event " + m.v);
        return;
      }
      // it's impossible but according to previous login, it happens nonetheless
      if (m.v > version + 1) return lichess.reload();
      version = m.v;
    }
    switch (m.t || false) {
      case false:
        break;
      case 'resync':
        lichess.reload();
        break;
      case 'ack':
        ackable.gotAck(m.d);
        break;
      default:
        lichess.pubsub.emit('socket.in.' + m.t)(m.d);
        var processed = settings.receive && settings.receive(m.t, m.d);
        if (!processed && settings.events[m.t]) settings.events[m.t](m.d || null, m);
    }
  };

  var debug = function(msg, always) {
    if (always || options.debug) {
      console.debug("[" + options.name + " " + settings.params.sri + "]", msg);
    }
  };

  var destroy = function() {
    clearTimeout(pingSchedule);
    clearTimeout(connectSchedule);
    disconnect();
    ws = null;
  };

  var disconnect = function(onNextConnect) {
    if (ws) {
      debug("Disconnect");
      autoReconnect = false;
      ws.onerror = ws.onclose = ws.onopen = ws.onmessage = $.noop;
      ws.close();
    }
    if (onNextConnect) options.onNextConnect = onNextConnect;
  };

  var onError = function(e) {
    options.debug = true;
    debug('error: ' + JSON.stringify(e));
    tryOtherUrl = true;
    clearTimeout(pingSchedule);
  };

  var onSuccess = function() {
    nbConnects++;
    if (nbConnects == 1) {
      options.onFirstConnect();
      var disconnectTimeout;
      lichess.idleTimer(10 * 60 * 1000, function() {
        options.idle = true;
        disconnectTimeout = setTimeout(lichess.socket.destroy, 2 * 60 * 60 * 1000);
      }, function() {
        options.idle = false;
        if (ws) clearTimeout(disconnectTimeout);
        else location.reload();
      });
    }
    if (options.onNextConnect) {
      options.onNextConnect();
      delete options.onNextConnect;
    }
  };

  var baseUrl = function() {
    var urls = options.baseUrls, url = storage.get();
    if (!url) {
      url = urls[0];
      storage.set(url);
    } else if (tryOtherUrl) {
      tryOtherUrl = false;
      url = urls[(urls.indexOf(url) + 1) % urls.length];
      storage.set(url);
    }
    return url;
  };

  connect();
  window.addEventListener('unload', destroy);

  return {
    connect: connect,
    disconnect: disconnect,
    send: send,
    destroy: destroy,
    options: options,
    pingInterval: function() {
      return computePingDelay() + averageLag;
    },
    averageLag: function() {
      return averageLag;
    },
    getVersion: function() {
      return version;
    }
  };
};
lichess.StrongSocket.sri = Math.random().toString(36).slice(2, 12);

lichess.StrongSocket.defaults = {
  events: {
    fen: function(e) {
      $('.mini-board-' + e.id).each(function() {
        lichess.parseFen($(this).data("fen", e.fen).data("lastmove", e.lm));
      });
    },
    challenges: function(d) {
      lichess.challengeApp.update(d);
    },
    notifications: function(d) {
      lichess.notifyApp.update(d, true);
    }
  },
  params: {
    sri: lichess.StrongSocket.sri
  },
  options: {
    name: "unnamed",
    idle: false,
    pingMaxLag: 9000, // time to wait for pong before reseting the connection
    pingDelay: 2500, // time between pong and ping
    autoReconnectDelay: 3500,
    protocol: location.protocol === 'https:' ? 'wss:' : 'ws:',
    baseUrls: (function(d) {
      return [d].concat((d === 'socket.lichess.org' ? [5, 6, 7, 8, 9] : []).map(function(port) {
        return d + ':' + (9020 + port);
      }));
    })(document.body.getAttribute('data-socket-domain')),
    onFirstConnect: $.noop
  }
};
<|endoftext|>


<commit_before>// Deps.
var
  AWS     = require("aws-sdk"),
  async   = require("async"),
  moment  = require("moment-timezone"),
  util    = require("util");
var
  log     = util.log;

// Other stuff.
var SECOND = 1000;
// Table names.
var tables = {
  FIDS:      "fids",
  AIRCRAFT:  "aircraft",
  POSITIONS: "positions",
  OTP:       "otp",
}

// Set region for CTI account.
AWS.config.update({ region: "us-west-2" });

// Create a service interface instance. New wont create a new db, just interface to service.
var dynDb = new AWS.DynamoDB({
  httpOptions: {
    proxy: process.env.http_proxy
  },
  apiVersion: 'latest',
  sslEnabled: false // Our proxy and / or my machine is doing weird things.
});

//
// Translate object to AWS format.
function translate(input)
{
  var output = {};
  // Iterate over keys, add key, value, type to awsData.
  if (typeof input === "object")
  {
    for (var key in input)
    {
      if (input[key] === undefined) continue;
      output[key] = { "S": input[key] + "" };
    }
  }
  else
  {
    output = { "S": input + "" };
  }
  return output;
};

//
// Exports.

// I guess these are the data store APIs.
// Save aircraft position.
exports.saveAircraftPositions = function(data)
{
  batchWrite(tables.POSITIONS, data);
}
// Save FIDS data.
exports.saveFids = function(data)
{
  batchWrite(tables.FIDS, data);
}
// Save FIDS data.
exports.saveAircraft = function(data)
{
  batchWrite(tables.AIRCRAFT, data);
}
// Save OTP data.
exports.saveOtp = function(data)
{
  batchWrite(tables.OTP, data);
}
// Save image data into an S3 bucket.
exports.saveImage = function(data)
{
  var s3 = new AWS.S3();

  s3.putObject({
    // ACL: "authenticated-read",
    Body: data.buffer,
    Bucket: "ctiweatherzone2",
    Key: data.portCode + "_" + data.weather + ".png",
    ContentType: "image/png",
    Metadata: {
      "weather": data.weather,
      "port": data.portCode,
      "utc": Math.floor(moment.utc().valueOf() / 1000) + ""
    }
  },
  function(error, res)
  {
    if (error) log("S3 error: " + error);
    log("S3 write" + res.ETag);
    exports.getImage(data);
  });
}

//
// Batch write requests.
exports.batchWrite = batchWrite;
function batchWrite(table, data)
{
  var batch = [];
  // Loop over data structure (must be array of FIDS entries). Take standard CTI data
  // and convert to AWS data. Create batches of 25 items.
  while (data.length)
  {
    var item = data.pop();
    var awsItem = {
      PutRequest: {
        Item: {},
      }
    };

    awsItem.PutRequest.Item = translate(item);

    // Add item to AWS array.
    batch.push(awsItem);
    // Send a batch and reset data array.
    if (data.length === 0 || batch.length == 25)
    {
      queueBatch(table, batch);
      batch = [];
    }
  }
}

var batches = [];
// Send a batch write operation to AWS. Batches must not exceed 25 items. Provisioning restraints
// require that we limit the amount of data being send to AWS. Don't send a batch closer than 1
// second from the last.
function queueBatch(table, batch)
{
  batches.push({ table: table, batch: batch});
}

// Pop batches off the array and send them every second.
(function()
{
  setInterval(function()
  {
    var batch = batches.pop();
    if (batch)
    {
      sendBatch(batch);
    }
  }, SECOND);
})();

// Use the write batch function to send items to AWS dynamoDB.
function sendBatch(batch)
{
  var sendRequest = true;
  var attemptNo = 1;
  // While we want to keep making request, please keep making requests. We use the async
  // library here rather than a straight up while loop. This is necessary because the AWS
  // batchWriteItem is async and would cause an ordinary while loop to make repeated same-requests.
  async.whilst(
    // Test function;
    function()
    {
      return attemptNo < 3 && sendRequest;
    },
    // Function to call each time test function passes. This function will be called
    // until the batchWriteItem callback returns no unprocessed items.
    function(cb)
    {
      log("Sending batch for table " + batch.table + ", attempt (" + attemptNo + ") to AWS.");
      attemptNo++;

      // Request parameters.
      var params = {
        ReturnConsumedCapacity: "TOTAL",
        RequestItems: {}
      };
      params.RequestItems[batch.table] = batch.batch;

      dynDb.batchWriteItem(params, function(error, awsResults)
      {
        log("AWS batch write request returned.");
        // There is an error. Stop processing immediately.
        if (error)
        {
          log("AWS batch write request errored: " + error);
          log("Batch contained " + batch.batch.length + " items");
          log(util.inspect(awsResults.unprocessedItems));
          sendRequest = false; // Remove this line to make multiple attempts.
          cb();
        }

        // Report consumed capacity.
        log(util.inspect(awsResults.ConsumedCapacity));

        // No error. Please continue.
        awsData = awsResults.unprocessedItems;
        // Test the data response. If there are no more unprocessed items,
        // signify that we do not wish to continue making AWS calls.
        if (!awsResults.unprocessedItems) sendRequest = false;
        // We have finished. call the callback.
        cb();
      });
    },
    // Function to call when repeated execution of main function has ceased.
    function(error)
    {
      log("Stopped sending data to AWS.");
      if (error) log (error);
    }
  );
}

//
// Clean the data coming out of dynamoDB.
function deStupify(input)
{
  input.forEach(function(record)
  {
    for (field in record)
    {
      record[field] = record[field].S;
    }
  });
  return input;
}

//
// Get an aws filter.
function getFilter(params)
{
  var paramMap = {
    // Flight update date.
    "date": {
      name: "udc",
      props: {
        "AttributeValueList": [
          { "S": params.date }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Schedulted date time within a range.
    "flightDate": {
      name: "shgtm",
      props: {
        "AttributeValueList": [
          { "S": (params.flightDate ? params.flightDate[0] : null) },
          { "S": (params.flightDate ? params.flightDate[1] : null) }
        ],
        "ComparisonOperator": "BETWEEN"
      }
    },
    // Gate is assigned / not assigned.
    "gate": {
      name: "gate",
      props: {
        "ComparisonOperator": (params.gate === "gate" ? "NOT_NULL" : "NULL")
      }
    },
    // Fleet
    "fleet": {
      name: "airln",
      props: {
        "AttributeValueList": [
          { "S": params.fleet }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Fleet-like
    "fleets": {
      name: "airln",
      props: {
        "AttributeValueList": [
          { "S": params.fleets }
        ],
        "ComparisonOperator": "BEGINS_WITH"
      }
    },
    // Tail
    "tail": {
      name: "tail",
      props: {
        "AttributeValueList": [
          { "S": params.tail }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Equipment
    "equipment": {
      name: "equip",
      props: {
        "AttributeValueList": [
          { "S": params.equipment }
        ],
        "ComparisonOperator": "BEGINS_WITH"
      }
    },
    // Port of interest
    "portCode": {
      name: "pcode",
      props: {
        "AttributeValueList": [
          { "S": params.portCode }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Destination port
    "destination": {
      name: "dport",
      props: {
        "AttributeValueList": [
          { "S": params.destination }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Flight code
    "flight": {
      name: "fcode",
      props: {
        "AttributeValueList": [
          { "S": params.flight }
        ],
        "ComparisonOperator": "BEGINS_WITH"
      }
    }
  };

  var awsFilter = {
    // "gate": {
    //   "ComparisonOperator": "NOT_NULL"
    // }
  };
  // Build the AWS query params from the passed in params.
  for (param in params) {
    log(util.inspect(param));
    awsFilter[paramMap[param].name] = paramMap[param].props;
  }

  return awsFilter;
}

//
// Wrapper function for getting items via table scan.
function getItems(table, params, cb)
{
  var awsFilter = getFilter(params);

  log("requesting data from table " + table);
  // Get entries based on params.
  dynDb.scan({
    "TableName": table,
    "Select": "ALL_ATTRIBUTES",
    "ScanFilter": awsFilter
  }, function(error, data)
  {
    if (data)
    {
      log("Items returned: " + data.Count);
      data.Items = deStupify(data.Items);
    }
    cb(error, data);
  });
}

//
// This is the data retrieval API.

//
// Get FIDS data for a particular date.
exports.getFIDS = function(params, cb)
{
  getItems(tables.FIDS, params, cb);
}
//
// Get FIDS data for a particular date.
exports.getPositions = function(params, cb)
{
  getItems(tables.POSITIONS, params, cb);
}
//
// Get data for an aircraft.
exports.getAircraft = function(params, cb)
{
  getItems(tables.AIRCRAFT, params, cb);
}
//
// Get raw data for OTP calculation and return results.
exports.getOTP = function(params, cb)
{
  // Default to current date.
  params.date = params.date || moment().format("YYYY-MM-DD");

  // Create a range.
  var lower = Math.floor(moment(params.date).hour(0).minute(0).utc().valueOf() / 1000);
  var upper = Math.floor(moment(params.date).hour(23).minute(59).utc().valueOf() / 1000);
  log(params.date + ", " + lower + ", " + upper);

  // Remove the date parameter.
  delete params.date;
  // Add a new parameter.
  params.flightDate = [lower + "", upper + ""];

  getItems(tables.OTP, params, cb);
}
//
// Get image data from an S3 bucket.
exports.getImage = function(data)
{
  var s3 = new AWS.S3();

  s3.getObject({
    Bucket: "ctiweatherzone2",
    Key: data.portCode + "_" + data.weather + ".png",
    ResponseContentType: "image/png"
  },
  function(error, res)
  {
    if (error) log("S3 error: " + error);
    log("S3 read" + res);
  });
}
<commit_msg>seems fine now<commit_after>// Deps.
var
  AWS     = require("aws-sdk"),
  async   = require("async"),
  moment  = require("moment-timezone"),
  util    = require("util");
var
  log     = util.log;

// Other stuff.
var SECOND = 1000;
// Table names.
var tables = {
  FIDS:      "fids",
  AIRCRAFT:  "aircraft",
  POSITIONS: "positions",
  OTP:       "otp",
}

// Set region for CTI account.
AWS.config.update({ region: "us-west-2" });

// Create a service interface instance. New wont create a new db, just interface to service.
var dynDb = new AWS.DynamoDB({
  httpOptions: {
    proxy: process.env.http_proxy
  },
  apiVersion: 'latest',
  sslEnabled: false // Our proxy and / or my machine is doing weird things.
});

//
// Translate object to AWS format.
function translate(input)
{
  var output = {};
  // Iterate over keys, add key, value, type to awsData.
  if (typeof input === "object")
  {
    for (var key in input)
    {
      if (input[key] === undefined) continue;
      output[key] = { "S": input[key] + "" };
    }
  }
  else
  {
    output = { "S": input + "" };
  }
  return output;
};

//
// Exports.

// I guess these are the data store APIs.
// Save aircraft position.
exports.saveAircraftPositions = function(data)
{
  batchWrite(tables.POSITIONS, data);
}
// Save FIDS data.
exports.saveFids = function(data)
{
  batchWrite(tables.FIDS, data);
}
// Save FIDS data.
exports.saveAircraft = function(data)
{
  batchWrite(tables.AIRCRAFT, data);
}
// Save OTP data.
exports.saveOtp = function(data)
{
  batchWrite(tables.OTP, data);
}
// Save image data into an S3 bucket.
exports.saveImage = function(data)
{
  var s3 = new AWS.S3();

  s3.putObject({
    ACL: "bucket-owner-full-control",
    Body: data.buffer,
    Bucket: "ctiweatherzone2",
    Key: data.portCode + "_" + data.weather + ".png",
    ContentType: "image/png",
    Metadata: {
      "weather": data.weather,
      "port": data.portCode,
      "utc": Math.floor(moment.utc().valueOf() / 1000) + ""
    }
  },
  function(error, res)
  {
    if (error) log("S3 error: " + error);
    log("S3 write" + res.ETag);
  });
}

//
// Batch write requests.
exports.batchWrite = batchWrite;
function batchWrite(table, data)
{
  var batch = [];
  // Loop over data structure (must be array of FIDS entries). Take standard CTI data
  // and convert to AWS data. Create batches of 25 items.
  while (data.length)
  {
    var item = data.pop();
    var awsItem = {
      PutRequest: {
        Item: {},
      }
    };

    awsItem.PutRequest.Item = translate(item);

    // Add item to AWS array.
    batch.push(awsItem);
    // Send a batch and reset data array.
    if (data.length === 0 || batch.length == 25)
    {
      queueBatch(table, batch);
      batch = [];
    }
  }
}

var batches = [];
// Send a batch write operation to AWS. Batches must not exceed 25 items. Provisioning restraints
// require that we limit the amount of data being send to AWS. Don't send a batch closer than 1
// second from the last.
function queueBatch(table, batch)
{
  batches.push({ table: table, batch: batch});
}

// Pop batches off the array and send them every second.
(function()
{
  setInterval(function()
  {
    var batch = batches.pop();
    if (batch)
    {
      sendBatch(batch);
    }
  }, SECOND);
})();

// Use the write batch function to send items to AWS dynamoDB.
function sendBatch(batch)
{
  var sendRequest = true;
  var attemptNo = 1;
  // While we want to keep making request, please keep making requests. We use the async
  // library here rather than a straight up while loop. This is necessary because the AWS
  // batchWriteItem is async and would cause an ordinary while loop to make repeated same-requests.
  async.whilst(
    // Test function;
    function()
    {
      return attemptNo < 3 && sendRequest;
    },
    // Function to call each time test function passes. This function will be called
    // until the batchWriteItem callback returns no unprocessed items.
    function(cb)
    {
      log("Sending batch for table " + batch.table + ", attempt (" + attemptNo + ") to AWS.");
      attemptNo++;

      // Request parameters.
      var params = {
        ReturnConsumedCapacity: "TOTAL",
        RequestItems: {}
      };
      params.RequestItems[batch.table] = batch.batch;

      dynDb.batchWriteItem(params, function(error, awsResults)
      {
        log("AWS batch write request returned.");
        // There is an error. Stop processing immediately.
        if (error)
        {
          log("AWS batch write request errored: " + error);
          log("Batch contained " + batch.batch.length + " items");
          log(util.inspect(awsResults.unprocessedItems));
          sendRequest = false; // Remove this line to make multiple attempts.
          cb();
        }

        // Report consumed capacity.
        log(util.inspect(awsResults.ConsumedCapacity));

        // No error. Please continue.
        awsData = awsResults.unprocessedItems;
        // Test the data response. If there are no more unprocessed items,
        // signify that we do not wish to continue making AWS calls.
        if (!awsResults.unprocessedItems) sendRequest = false;
        // We have finished. call the callback.
        cb();
      });
    },
    // Function to call when repeated execution of main function has ceased.
    function(error)
    {
      log("Stopped sending data to AWS.");
      if (error) log (error);
    }
  );
}

//
// Clean the data coming out of dynamoDB.
function deStupify(input)
{
  input.forEach(function(record)
  {
    for (field in record)
    {
      record[field] = record[field].S;
    }
  });
  return input;
}

//
// Get an aws filter.
function getFilter(params)
{
  var paramMap = {
    // Flight update date.
    "date": {
      name: "udc",
      props: {
        "AttributeValueList": [
          { "S": params.date }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Schedulted date time within a range.
    "flightDate": {
      name: "shgtm",
      props: {
        "AttributeValueList": [
          { "S": (params.flightDate ? params.flightDate[0] : null) },
          { "S": (params.flightDate ? params.flightDate[1] : null) }
        ],
        "ComparisonOperator": "BETWEEN"
      }
    },
    // Gate is assigned / not assigned.
    "gate": {
      name: "gate",
      props: {
        "ComparisonOperator": (params.gate === "gate" ? "NOT_NULL" : "NULL")
      }
    },
    // Fleet
    "fleet": {
      name: "airln",
      props: {
        "AttributeValueList": [
          { "S": params.fleet }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Fleet-like
    "fleets": {
      name: "airln",
      props: {
        "AttributeValueList": [
          { "S": params.fleets }
        ],
        "ComparisonOperator": "BEGINS_WITH"
      }
    },
    // Tail
    "tail": {
      name: "tail",
      props: {
        "AttributeValueList": [
          { "S": params.tail }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Equipment
    "equipment": {
      name: "equip",
      props: {
        "AttributeValueList": [
          { "S": params.equipment }
        ],
        "ComparisonOperator": "BEGINS_WITH"
      }
    },
    // Port of interest
    "portCode": {
      name: "pcode",
      props: {
        "AttributeValueList": [
          { "S": params.portCode }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Destination port
    "destination": {
      name: "dport",
      props: {
        "AttributeValueList": [
          { "S": params.destination }
        ],
        "ComparisonOperator": "EQ"
      }
    },
    // Flight code
    "flight": {
      name: "fcode",
      props: {
        "AttributeValueList": [
          { "S": params.flight }
        ],
        "ComparisonOperator": "BEGINS_WITH"
      }
    }
  };

  var awsFilter = {
    // "gate": {
    //   "ComparisonOperator": "NOT_NULL"
    // }
  };
  // Build the AWS query params from the passed in params.
  for (param in params) {
    log(util.inspect(param));
    awsFilter[paramMap[param].name] = paramMap[param].props;
  }

  return awsFilter;
}

//
// Wrapper function for getting items via table scan.
function getItems(table, params, cb)
{
  var awsFilter = getFilter(params);

  log("requesting data from table " + table);
  // Get entries based on params.
  dynDb.scan({
    "TableName": table,
    "Select": "ALL_ATTRIBUTES",
    "ScanFilter": awsFilter
  }, function(error, data)
  {
    if (data)
    {
      log("Items returned: " + data.Count);
      data.Items = deStupify(data.Items);
    }
    cb(error, data);
  });
}

//
// This is the data retrieval API.

//
// Get FIDS data for a particular date.
exports.getFIDS = function(params, cb)
{
  getItems(tables.FIDS, params, cb);
}
//
// Get FIDS data for a particular date.
exports.getPositions = function(params, cb)
{
  getItems(tables.POSITIONS, params, cb);
}
//
// Get data for an aircraft.
exports.getAircraft = function(params, cb)
{
  getItems(tables.AIRCRAFT, params, cb);
}
//
// Get raw data for OTP calculation and return results.
exports.getOTP = function(params, cb)
{
  // Default to current date.
  params.date = params.date || moment().format("YYYY-MM-DD");

  // Create a range.
  var lower = Math.floor(moment(params.date).hour(0).minute(0).utc().valueOf() / 1000);
  var upper = Math.floor(moment(params.date).hour(23).minute(59).utc().valueOf() / 1000);
  log(params.date + ", " + lower + ", " + upper);

  // Remove the date parameter.
  delete params.date;
  // Add a new parameter.
  params.flightDate = [lower + "", upper + ""];

  getItems(tables.OTP, params, cb);
}
//
// Get image data from an S3 bucket.
exports.getImage = function(data)
{
  var s3 = new AWS.S3();

  s3.getObject({
    Bucket: "ctiweatherzone2",
    Key: data.portCode + "_" + data.weather + ".png",
    ResponseContentType: "image/png"
  },
  function(error, res)
  {
    if (error) log("S3 error: " + error);
    log("S3 read" + res);
  });
}
<|endoftext|>


<commit_before>/*******************************************************
 * Utility functions
 *******************************************************/

function drawBorder() {
  /* Draw the border for the game. */
  ctx.strokeStyle = '#000000';
  ctx.lineWidth = 2;
  ctx.beginPath();

  // 1 is half the line width, so there is no overlap
  var y = {
    min : GRID_OFFSET.y.start - 1,
    max : canvas.height - GRID_OFFSET.y.start + 1
  };
  var x = {
    min : GRID_OFFSET.x.start - 1,
    max : canvas.width - GRID_OFFSET.x.start + 1
  };

  // Left border
  ctx.moveTo(x.min, y.min);
  ctx.lineTo(x.min, y.max);

  // Bottom border
  ctx.lineTo(x.max, y.max);

  // Right boder
  ctx.lineTo(x.max, y.min);

  ctx.stroke();
  ctx.closePath();
}

// We initially draw the border
drawBorder();

function nop() {};

function rowFull(row) {
  /* Check if a row is full */
  for (var i = 0; i < 10; ++i) {
    if (!row[i]) return false;
  }
  return true;
}

function shuffle(arr) {
  /* Randomly order an array */
  var index = arr.length;
  var nextSwap;
  var temp;

  while(index) {
    // Choose a random element
    nextSwap = Math.floor((Math.random() * index));

    // One less item to choose from now
    index -= 1;

    // Swap this item with a randomly chosen item
    temp = arr[index];
    arr[index] = arr[nextSwap];
    arr[nextSwap] = temp;
  }

  // Might as well return something
  return arr;
}

function makeNewRow() {
  /* Create a new object with columns to represent a row */
  return { '-1' : true, '10' : true };
}

function copyPoint(pt) {
  /* Return a copy of the point */
  return { x : pt.x, y : pt.y };
}

function pointsEqual() {
  /* Determine if a set of points are all equal to each other */
  var lop = arguments[0] instanceof Array ? arguments[0] : arguments;
  var first = lop[0];
  for (var i = 1; i < lop.length; ++i) {
    if (first.x !== lop[i].x || first.y !== lop[i].y) return false;
  }
  return true;
}

<commit_msg>requireify utils<commit_after>/*******************************************************
 * Utility functions
 *******************************************************/

define(['globals'], function(Globals) {
  var Util = {};

  Util.drawBorder = function() {
    /* Draw the border for the game. */
    ctx.strokeStyle = '#000000';
    ctx.lineWidth = 2;
    ctx.beginPath();

    // 1 is half the line width, so there is no overlap
    var y = {
      min : Globals.GRID_OFFSET.y.start - 1,
      max : canvas.height - Globals.GRID_OFFSET.y.start + 1
    };
    var x = {
      min : Globals.GRID_OFFSET.x.start - 1,
      max : canvas.width - Globals.GRID_OFFSET.x.start + 1
    };

    // Left border
    ctx.moveTo(x.min, y.min);
    ctx.lineTo(x.min, y.max);

    // Bottom border
    ctx.lineTo(x.max, y.max);

    // Right boder
    ctx.lineTo(x.max, y.min);

    ctx.stroke();
    ctx.closePath();
  };

  // We initially draw the border
  drawBorder();

  Util.nop = function() {};

  Util.rowFull = function(row) {
    /* Check if a row is full */
    for (var i = 0; i < 10; ++i) {
      if (!row[i]) return false;
    }
    return true;
  };

  Util.shuffle = function(arr) {
    /* Randomly order an array */
    var index = arr.length;
    var nextSwap;
    var temp;

    while(index) {
      // Choose a random element
      nextSwap = Math.floor((Math.random() * index));

      // One less item to choose from now
      index -= 1;

      // Swap this item with a randomly chosen item
      temp = arr[index];
      arr[index] = arr[nextSwap];
      arr[nextSwap] = temp;
    }

    // Might as well return something
    return arr;
  };

  Util.makeNewRow = function() {
    /* Create a new object with columns to represent a row */
    return { '-1' : true, '10' : true };
  };

  Util.copyPoint = function(pt) {
    /* Return a copy of the point */
    return { x : pt.x, y : pt.y };
  };

  Util.pointsEqual = function() {
    /* Determine if a set of points are all equal to each other */
    var lop = arguments[0] instanceof Array ? arguments[0] : arguments;
    var first = lop[0];
    for (var i = 1; i < lop.length; ++i) {
      if (first.x !== lop[i].x || first.y !== lop[i].y) return false;
    }
    return true;
  };

  return Util;

});

<|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Florent Cailhol @ooflorent
*/

"use strict";

exports.STAGE_BASIC = 0;
exports.STAGE_DEFAULT = 10;
exports.STAGE_ADVANCED = 20;
<commit_msg>Change stage values<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Florent Cailhol @ooflorent
*/

"use strict";

exports.STAGE_BASIC = -10;
exports.STAGE_DEFAULT = 0;
exports.STAGE_ADVANCED = 10;
<|endoftext|>


<commit_before> var mongoose         = require('mongoose'),
    Schema            = mongoose.Schema,
    ObjectId          = Schema.ObjectId,
    ContactDetail     = require('../schemas').ContactDetail,
    Link              = require('../schemas').Link,
    slug              = require ('slug');

var PersonSchema = module.exports = new Schema({

  // Overview fields
  name: {
    type: String,
    required: true,
    trim: true,
    form_label: 'Name',
    form_help_text: "The person's most well known name",
    set: function(v) {
      // If we don't already have a slug set it
      if (!this.slug) this.slug = v;
      // Don't change the value
      return v;
    },
  },
  slug: {
    type:      String,
    required:  true,
    lowercase: true,
    trim:      true,
    unique:    true,
    set: function(v) { return slug(v); },
  },
  summary: {
    type: String,
    form_label: 'Summary',
    form_help_text: "A brief bit of text to help identify the person",
    form_input_type: 'textarea',
  },

  // Profile fields - dob, dod, gender

  // Names - historic and alternative names

  // Contact details such as phone numbers, email, postal addresses etc.
  contact_details: [ContactDetail],

  // Links to other sites that have relevant information
  links: [Link],

  // Images of this person
  images: [{type: ObjectId, ref: "Image"}],

}, { strict: true } );

PersonSchema.methods.deduplicate_slug = function deduplicate_slug (cb) {
  var self = this;

  // find other entries in the database that have the same slug
  this.collection.findOne({slug: self.slug, _id: { $ne: self._id } }, function(err, doc) {

    if (err) return cb(err);

    // if nothing found then no need to change slug
    if ( ! doc ) return cb(null);

    // we have a conflict, increment the slug
    var matches = self.slug.match(/^(.*)\-(\d+)$/);

    if ( !matches ) {
      self.slug = self.slug + '-1';
    } else {
      var base_slug = matches[1];
      var counter   = parseInt(matches[2]) + 1;
      self.slug     = base_slug + '-' + counter;
    }

    return self.deduplicate_slug(cb); // recurse

  });
};

PersonSchema
  .virtual('slug_url')
  .get( function () { return '/person/' + this.slug;} );

PersonSchema.methods.deduplicate_slug = function deduplicate_slug (cb) {
  var self = this;

  // find other entries in the database that have the same slug
  this.collection.findOne({slug: self.slug, _id: { $ne: self._id } }, function(err, doc) {

    if (err) return cb(err);

    // if nothing found then no need to change slug
    if ( ! doc ) return cb(null);

    // we have a conflict, increment the slug
    var matches = self.slug.match(/^(.*)\-(\d+)$/);

    if ( !matches ) {
      self.slug = self.slug + '-1';
    } else {
      var base_slug = matches[1];
      var counter   = parseInt(matches[2]) + 1;
      self.slug     = base_slug + '-' + counter;
    }

    return self.deduplicate_slug(cb); // recurse

  });
};

<commit_msg>remove duplicate code<commit_after> var mongoose         = require('mongoose'),
    Schema            = mongoose.Schema,
    ObjectId          = Schema.ObjectId,
    ContactDetail     = require('../schemas').ContactDetail,
    Link              = require('../schemas').Link,
    slug              = require ('slug');

var PersonSchema = module.exports = new Schema({

  // Overview fields
  name: {
    type: String,
    required: true,
    trim: true,
    form_label: 'Name',
    form_help_text: "The person's most well known name",
    set: function(v) {
      // If we don't already have a slug set it
      if (!this.slug) this.slug = v;
      // Don't change the value
      return v;
    },
  },
  slug: {
    type:      String,
    required:  true,
    lowercase: true,
    trim:      true,
    unique:    true,
    set: function(v) { return slug(v); },
  },
  summary: {
    type: String,
    form_label: 'Summary',
    form_help_text: "A brief bit of text to help identify the person",
    form_input_type: 'textarea',
  },

  // Profile fields - dob, dod, gender

  // Names - historic and alternative names

  // Contact details such as phone numbers, email, postal addresses etc.
  contact_details: [ContactDetail],

  // Links to other sites that have relevant information
  links: [Link],

  // Images of this person
  images: [{type: ObjectId, ref: "Image"}],

}, { strict: true } );

PersonSchema
  .virtual('slug_url')
  .get( function () { return '/person/' + this.slug;} );

PersonSchema.methods.deduplicate_slug = function deduplicate_slug (cb) {
  var self = this;

  // find other entries in the database that have the same slug
  this.collection.findOne({slug: self.slug, _id: { $ne: self._id } }, function(err, doc) {

    if (err) return cb(err);

    // if nothing found then no need to change slug
    if ( ! doc ) return cb(null);

    // we have a conflict, increment the slug
    var matches = self.slug.match(/^(.*)\-(\d+)$/);

    if ( !matches ) {
      self.slug = self.slug + '-1';
    } else {
      var base_slug = matches[1];
      var counter   = parseInt(matches[2]) + 1;
      self.slug     = base_slug + '-' + counter;
    }

    return self.deduplicate_slug(cb); // recurse

  });
};

<|endoftext|>


<commit_before>"use strict";

function gsuiSlider() {
	var root = gsuiSlider.template.cloneNode( true );

	this.rootElement = root;
	this._elInput = root.querySelector( "input" );
	this._elLine = root.querySelector( ".gsui-line" );
	this._elLineColor = root.querySelector( ".gsui-lineColor" );
	this._elSvg = root.querySelector( "svg" );
	this._elSvgLine = root.querySelector( ".gsui-svgLine" );
	this._elSvgLineColor = root.querySelector( ".gsui-svgLineColor" );
	root._gsuiSlider_instance = this;
	root.onwheel = this._wheel.bind( this );
	root.onmousedown = this._mousedown.bind( this );
	root.onmousemove = this._mousemove.bind( this );
	root.onmouseup = this._mouseup.bind( this );
	root.onmouseleave = this._mouseleave.bind( this );
}

gsuiSlider.template = document.querySelector( "#gsuiSlider-template" );
gsuiSlider.template.remove();
gsuiSlider.template.removeAttribute( "id" );

document.addEventListener( "pointerlockchange", _ => {
	var el = document.pointerLockElement;

	if ( el && ( el = el._gsuiSlider_instance ) ) {
		el._locked = true;
	}
} );

gsuiSlider.prototype = {
	remove() {
		delete this._attached;
		this.rootElement.remove();
	},
	attached() {
		this._attached = true;
		this.resized();
	},
	options( obj ) {
		var inp = this._elInput,
			clazz = this.rootElement.classList;

		this._circ = obj.type === "circular";
		this._axeX = obj.type === "linear-x";
		this._options = obj = Object.assign( {}, obj );
		obj.step = Math.max( 0, obj.step ) || ( obj.max - obj.min ) / 10;
		obj.scrollStep = Math.max( obj.step, obj.scrollStep || obj.step );
		obj.startFrom = Math.max( obj.min, Math.min( obj.startFrom || 0, obj.max ) );
		inp.min = obj.min;
		inp.max = obj.max;
		inp.step = obj.step;
		inp.value = obj.value;
		this._previousval = this._getInputVal();
		if ( this._circ ) {
			clazz.remove( "gsui-linear", "gsui-x", "gsui-y" );
			clazz.add( "gsui-circular" );
		} else {
			clazz.remove( "gsui-circular", "gsui-x", "gsui-y" );
			clazz.add( "gsui-linear", this._axeX ? "gsui-x" : "gsui-y" );
		}
		this._updateVal();
	},
	setValue( val, bymouse ) {
		var prval = this._getInputVal();

		this._elInput.value = val;
		val = this._getInputVal();
		if ( val !== prval ) {
			this._updateVal();
			if ( bymouse && this.oninput ) {
				this.oninput( +val );
			}
		}
		if ( !bymouse ) {
			this._previousval = val;
		}
	},
	resized() {
		var rc = this.rootElement.getBoundingClientRect();

		this.resize( rc.width, rc.height );
	},
	resize( w, h ) {
		this.rootElement.style.width = w + "px";
		this.rootElement.style.height = h + "px";
		if ( w !== this.width || h !== this.height ) {
			var thick = this._axeX ? h : w,
				size = Math.min( w, h ),
				size2 = size / 2,
				strokeW = ~~( size / 10 ),
				circR = ~~( ( size - strokeW ) / 2 );

			this.width = w;
			this.height = h;
			if ( this._circ ) {
				this._elSvg.setAttribute( "viewBox", "0 0 " + size + " " + size );
				this._elSvgLine.setAttribute( "cx", size2 );
				this._elSvgLine.setAttribute( "cy", size2 );
				this._elSvgLine.setAttribute( "r", circR );
				this._elSvgLineColor.setAttribute( "cx", size2 );
				this._elSvgLineColor.setAttribute( "cy", size2 );
				this._elSvgLineColor.setAttribute( "r", circR );
				this._elSvgLine.style.strokeWidth =
				this._elSvgLineColor.style.strokeWidth = strokeW;
				this._svgLineLen = circR * 2 * Math.PI;
			}
			this._updateVal();
		}
	},

	// private:
	_getInputVal() {
		var val = this._elInput.value;

		return Math.abs( +val ) < .000001 ? "0" : val;
	},
	_updateVal() {
		this.value = +this._getInputVal();
		if ( this._attached ) {
			var line,
				opt = this._options,
				inplen = opt.max - opt.min,
				prcval = ( this.value - opt.min ) / inplen,
				prcstart = ( opt.startFrom - opt.min ) / inplen,
				prclen = Math.abs( prcval - prcstart ),
				prcmin = Math.min( prcval, prcstart );

			if ( this._circ ) {
				line = this._elSvgLineColor.style;
				line.strokeDasharray = prclen * this._svgLineLen + ", 999999";
				line.transform = "rotate(" + ( 90 + prcmin * 360 ) + "deg)";
			} else {
				line = this._elLineColor.style;
				if ( this._axeX ) {
					line.left = prcmin * 100 + "%";
					line.width = prclen * 100 + "%";
				} else {
					line.bottom = prcmin * 100 + "%";
					line.height = prclen * 100 + "%";
				}
			}
		}
	},
	_onchange() {
		var val = this._getInputVal();

		if ( this._previousval !== val ) {
			this.onchange && this.onchange( +val );
			this._previousval = val;
		}
	},

	// events:
	_wheel( e ) {
		var d = e.deltaY > 0 ? -1 : 1;

		this.setValue( +this._getInputVal() + this._options.scrollStep * d, true );
		return false;
	},
	_mousedown( e ) {
		var opt = this._options,
			bcr = this._elLine.getBoundingClientRect(),
			size = this._circ ? this._svgLineLen :
				this._axeX ? bcr.width : bcr.height;

		this._onchange();
		this._pxval = ( opt.max - opt.min ) / size;
		this._pxmoved = 0;
		this.rootElement.requestPointerLock();
	},
	_mouseup( e ) {
		if ( this._locked ) {
			document.exitPointerLock();
			delete this._locked;
			this._onchange();
		}
	},
	_mousemove( e ) {
		if ( this._locked ) {
			var opt = this._options,
				mov = this._circ || !this._axeX ? -e.movementY : e.movementX,
				bound = ( opt.max - opt.min ) / 5,
				val = +this._previousval + ( this._pxmoved + mov ) * this._pxval;

			if ( opt.min - bound < val && val < opt.max + bound ) {
				this._pxmoved += mov;
			}
			this.setValue( val, true );
		}
	},
	_mouseleave() {
		this._onchange();
	}
};
<commit_msg>gsuiSlider: update the code itself<commit_after>"use strict";

class gsuiSlider {
	constructor() {
		const root = gsuiSlider.template.cloneNode( true );

		this.rootElement = root;
		this._elInput = root.querySelector( "input" );
		this._elLine = root.querySelector( ".gsui-line" );
		this._elLineColor = root.querySelector( ".gsui-lineColor" );
		this._elSvg = root.querySelector( "svg" );
		this._elSvgLine = root.querySelector( ".gsui-svgLine" );
		this._elSvgLineColor = root.querySelector( ".gsui-svgLineColor" );
		root._gsuiSlider_instance = this;
		root.onwheel = this._wheel.bind( this );
		root.onmousedown = this._mousedown.bind( this );
		root.onmousemove = this._mousemove.bind( this );
		root.onmouseup = this._mouseup.bind( this );
		root.onmouseleave = this._mouseleave.bind( this );
	}

	remove() {
		delete this._attached;
		this.rootElement.remove();
	}
	attached() {
		this._attached = true;
		this.resized();
	}
	options( obj ) {
		const inp = this._elInput,
			clazz = this.rootElement.classList;

		this._circ = obj.type === "circular";
		this._axeX = obj.type === "linear-x";
		this._options = obj = Object.assign( {}, obj );
		obj.step = Math.max( 0, obj.step ) || ( obj.max - obj.min ) / 10;
		obj.scrollStep = Math.max( obj.step, obj.scrollStep || obj.step );
		obj.startFrom = Math.max( obj.min, Math.min( obj.startFrom || 0, obj.max ) );
		inp.min = obj.min;
		inp.max = obj.max;
		inp.step = obj.step;
		inp.value = obj.value;
		this._previousval = this._getInputVal();
		if ( this._circ ) {
			clazz.remove( "gsui-linear", "gsui-x", "gsui-y" );
			clazz.add( "gsui-circular" );
		} else {
			clazz.remove( "gsui-circular", "gsui-x", "gsui-y" );
			clazz.add( "gsui-linear", this._axeX ? "gsui-x" : "gsui-y" );
		}
		this._updateVal();
	}
	setValue( val, bymouse ) {
		const prval = this._getInputVal();

		this._elInput.value = val;
		val = this._getInputVal();
		if ( val !== prval ) {
			this._updateVal();
			if ( bymouse && this.oninput ) {
				this.oninput( +val );
			}
		}
		if ( !bymouse ) {
			this._previousval = val;
		}
	}
	resized() {
		const rc = this.rootElement.getBoundingClientRect();

		this.resize( rc.width, rc.height );
	}
	resize( w, h ) {
		this.rootElement.style.width = w + "px";
		this.rootElement.style.height = h + "px";
		if ( w !== this.width || h !== this.height ) {
			const size = Math.min( w, h ),
				size2 = size / 2,
				strokeW = ~~( size / 10 ),
				circR = ~~( ( size - strokeW ) / 2 );

			this.width = w;
			this.height = h;
			if ( this._circ ) {
				this._elSvg.setAttribute( "viewBox", "0 0 " + size + " " + size );
				this._elSvgLine.setAttribute( "cx", size2 );
				this._elSvgLine.setAttribute( "cy", size2 );
				this._elSvgLine.setAttribute( "r", circR );
				this._elSvgLineColor.setAttribute( "cx", size2 );
				this._elSvgLineColor.setAttribute( "cy", size2 );
				this._elSvgLineColor.setAttribute( "r", circR );
				this._elSvgLine.style.strokeWidth =
				this._elSvgLineColor.style.strokeWidth = strokeW;
				this._svgLineLen = circR * 2 * Math.PI;
			}
			this._updateVal();
		}
	}

	// private:
	_getInputVal() {
		const val = this._elInput.value;

		return Math.abs( +val ) < .000001 ? "0" : val;
	}
	_updateVal() {
		this.value = +this._getInputVal();
		if ( this._attached ) {
			const opt = this._options,
				inplen = opt.max - opt.min,
				prcval = ( this.value - opt.min ) / inplen,
				prcstart = ( opt.startFrom - opt.min ) / inplen,
				prclen = Math.abs( prcval - prcstart ),
				prcmin = Math.min( prcval, prcstart );

			if ( this._circ ) {
				const line = this._elSvgLineColor.style;

				line.strokeDasharray = prclen * this._svgLineLen + ", 999999";
				line.transform = "rotate(" + ( 90 + prcmin * 360 ) + "deg)";
			} else {
				const line = this._elLineColor.style;

				if ( this._axeX ) {
					line.left = prcmin * 100 + "%";
					line.width = prclen * 100 + "%";
				} else {
					line.bottom = prcmin * 100 + "%";
					line.height = prclen * 100 + "%";
				}
			}
		}
	}
	_onchange() {
		const val = this._getInputVal();

		if ( this._previousval !== val ) {
			this.onchange && this.onchange( +val );
			this._previousval = val;
		}
	}

	// events:
	_wheel( e ) {
		const d = e.deltaY > 0 ? -1 : 1;

		this.setValue( +this._getInputVal() + this._options.scrollStep * d, true );
		return false;
	}
	_mousedown( e ) {
		const opt = this._options,
			bcr = this._elLine.getBoundingClientRect(),
			size = this._circ ? this._svgLineLen :
				this._axeX ? bcr.width : bcr.height;

		this._onchange();
		this._pxval = ( opt.max - opt.min ) / size;
		this._pxmoved = 0;
		this.rootElement.requestPointerLock();
	}
	_mouseup( e ) {
		if ( this._locked ) {
			document.exitPointerLock();
			delete this._locked;
			this._onchange();
		}
	}
	_mousemove( e ) {
		if ( this._locked ) {
			const { min, max } = this._options,
				mov = this._circ || !this._axeX ? -e.movementY : e.movementX,
				bound = ( max - min ) / 5,
				val = +this._previousval + ( this._pxmoved + mov ) * this._pxval;

			if ( min - bound < val && val < max + bound ) {
				this._pxmoved += mov;
			}
			this.setValue( val, true );
		}
	}
	_mouseleave() {
		this._onchange();
	}
}

gsuiSlider.template = document.querySelector( "#gsuiSlider-template" );
gsuiSlider.template.remove();
gsuiSlider.template.removeAttribute( "id" );

document.addEventListener( "pointerlockchange", () => {
	const el = document.pointerLockElement,
		slider = el && el._gsuiSlider_instance;

	if ( slider ) {
		slider._locked = true;
	}
} );
<|endoftext|>


<commit_before>import * as GameplayActions from '../../action/gameplay';

import { getMap, updateMap } from './map';
import { addList } from './map';

export default function gameplay(room, state, action) {
  const { type, payload } = action;
  if (state == null) {
    // Silently ignore everything other than START action, although this is
    // not recommended behavior. TODO Add proper error throwing
    if (type === GameplayActions.START) {
      // Start the game; Read the state from room value, inject users, and
      // do other stuff.
      // Server middleware router should treat this as PHASE_SUBMIT, although
      // some are different. But still, server should 'give' cards to users.

      // Do basic check; there must be at least two players in the game!
      // ...Pretty obvious though. Why are we doing this?
      if (room.users.length < 2) {
        throw new Error('There must be at least two players');
      }
      // First czar is *always* first player in the player list. This might be
      // changed, but this is enough for now.
      const czar = room.users[0];
      // Copy user list from the room. This is a list of users actually
      // participating in the game.
      const userList = room.users;
      // Build users table.
      const users = {};
      for (let userId of userList) {
        users[userId] = { id: userId, points: 0, cards: [] };
      }
      return {
        phase: 'wait', turn: 0, czar, userList, users
      };
    }
    return state;
  }
  switch (type) {
  case GameplayActions.START:
    throw new Error('Game already started');
  case GameplayActions.STOP:
    // Remove game state.
    return undefined;
  case GameplayActions.PHASE_SUBMIT:
    // Beginning of a turn, Nothing special really.
    if (state.phase !== 'wait' || state.phase !== 'end') {
      throw new Error('Phase must be wait or end');
    }
    // Merge the question card (payload) with the state.
    return Object.assign({}, state, {
      phase: 'submit',
      turn: state.turn + 1,
      questionCard: payload
    });
  case GameplayActions.PHASE_SELECT:
    // Server initiates this action after all players submit cards.
    // However just setting the state will be fine in here.
    if (state.phase !== 'submit') {
      throw new Error('Phase must be submit');
    }
    return Object.assign({}, state, {
      phase: 'select',
      answerCards: payload
    });
  case GameplayActions.PHASE_END:
    // A turn is over; switch the czar, turn and we're good to go.
    if (state.phase !== 'select') {
      throw new Error('Phase must be select');
    }
    return Object.assign({}, state, {
      phase: 'end',
      czar: (state.userList.indexOf(state.czar) + 1) % state.userList
    });
  case GameplayActions.DRAW:
    // Give cards to specific user.
    return Object.assign({}, state, {
      users: updateMap(room.users, payload.user,
        Object.assign({}, room.users[payload.user], {
          cards: room.users[payload.user].cards.concat(payload.cards)
        })
      )
    });
  case GameplayActions.SUBMIT:
    // User submits the card; we get list of card *INDEX*.
    return state;
  case GameplayActions.SELECT:
    // Czar selects cards; we get index of answer cards.
    return state;
  }
}
<commit_msg>Implement card submit action in gameplay reducer<commit_after>import * as GameplayActions from '../../action/gameplay';

import { getMap, updateMap } from './map';
import { addList } from './list';

export default function gameplay(room, state, action) {
  const { type, payload, meta } = action;
  if (state == null) {
    // Silently ignore everything other than START action, although this is
    // not recommended behavior. TODO Add proper error throwing
    if (type === GameplayActions.START) {
      // Start the game; Read the state from room value, inject users, and
      // do other stuff.
      // Server middleware router should treat this as PHASE_SUBMIT, although
      // some are different. But still, server should 'give' cards to users.

      // Do basic check; there must be at least two players in the game!
      // ...Pretty obvious though. Why are we doing this?
      if (room.users.length < 2) {
        throw new Error('There must be at least two players');
      }
      // First czar is *always* first player in the player list. This might be
      // changed, but this is enough for now.
      const czar = room.users[0];
      // Copy user list from the room. This is a list of users actually
      // participating in the game.
      const userList = room.users;
      // Build users table.
      const users = {};
      for (let userId of userList) {
        users[userId] = { id: userId, points: 0, cards: [] };
      }
      return {
        phase: 'wait', turn: 0, czar, userList, users
      };
    }
    return state;
  }
  // Retrieve connection ID of who initiated the action.
  const connectionId = meta && meta.target && meta.target.connection;
  switch (type) {
  case GameplayActions.START:
    throw new Error('Game already started');
  case GameplayActions.STOP:
    // Remove game state.
    return undefined;
  case GameplayActions.PHASE_SUBMIT:
    // Beginning of a turn, Nothing special really.
    if (state.phase !== 'wait' || state.phase !== 'end') {
      throw new Error('Phase must be wait or end');
    }
    // Merge the question card (payload) with the state.
    return Object.assign({}, state, {
      phase: 'submit',
      turn: state.turn + 1,
      questionCard: payload
    });
  case GameplayActions.PHASE_SELECT:
    // Server initiates this action after all players submit cards.
    // However just setting the state will be fine in here.
    if (state.phase !== 'submit') {
      throw new Error('Phase must be submit');
    }
    return Object.assign({}, state, {
      phase: 'select',
      answerCards: payload
    });
  case GameplayActions.PHASE_END:
    // A turn is over; switch the czar, turn and we're good to go.
    if (state.phase !== 'select') {
      throw new Error('Phase must be select');
    }
    return Object.assign({}, state, {
      phase: 'end',
      czar: (state.userList.indexOf(state.czar) + 1) % state.userList
    });
  case GameplayActions.DRAW:
    // Give cards to specific user.
    return Object.assign({}, state, {
      users: updateMap(room.users, payload.user,
        Object.assign({}, room.users[payload.user], {
          cards: room.users[payload.user].cards.concat(payload.cards)
        })
      )
    });
  case GameplayActions.SUBMIT:
    // User submits the card; we get list of card *INDEX*.
    // If clause exists to create its own scope - to prevent variable
    // confliction.
    if (payload) {
      // Pull cards from the card array.
      const user = state.users[connectionId];
      const cards = payload.map(id => user.cards[id]);
      // Validate if all cards exist. Throw error if any of those cards
      // are null.
      if (!cards.every(card => card)) {
        throw new Error('Wrong card specified');
      }
      // Remove cards from user's deck. Since we remove multiple items
      // from the array, it'd be better to work on a copy of the array
      // instance.
      // Note that Array.from is ES6 method; it might be better to use
      // array.slice if ES5 support is required. Although we're using
      // polyfill to resolve that.
      const userCards = Array.from(user.cards);
      payload.forEach(id => userCards.splice(id, 1));
      // That's enough - now let's combine those data to root state.
      return Object.assign({}, state, {
        answerCards: addList(state.answerCards, cards),
        users: updateMap(state.users, connectionId,
          Object.assign({}, user, { cards: userCards })
        )
      );
    }
    // Normally shouldn't reach here, as payload must be provided
    throw new Error('Payload must be specified');
  case GameplayActions.SELECT:
    // Czar selects cards; we get index of answer cards.
    return state;
  }
}
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');

var levels = require('./levels');
var utils = require('./utils');

/**
 * Handle the socket connections.
 *
 * @class
 * @param {Socket.io} io - The listening object
 */

function SocketHandler(io) {

  /**
   * Keep track of the last given id. Numbers are used so as to minimize the
   * packets size.
   *
   * @type {Number}
   */

  var lastId = 0;

  /**
   * Store the current state for all the players. The keys correspond to the
   * user ids, the value to their respective state.
   *
   * @type {Object}
   */

  var states = {};

  /**
   * Store the current game information (current level, etc)
   *
   * @type {Object}
   * @type {String} game.level - the name of the current level
   */

  var game = {
    level: {
      name: levels.getFirstKey(),
      data: levels.getFirst()
    }
  };

  /**
   * Called on each new user connection.
   */

  io.on('connection', function(socket) {

    var user = {};
    user.id = (lastId += 1);
    user.color = utils.getColorFromNumber(user.id);

    // give the information which could be useful when a user just arrives in a
    // game
    socket.emit('handshake', {
      user: user,
      game: game,
      states: _(_.cloneDeep(states))
        .forEach(function(value) {
          value.color = utils.getColorFromNumber(value.id);
        })
        .value()
    });

    // inform the other users
    socket.broadcast.emit('new_player', user);

    /**
     * Called when a player sends its position/velocity.
     */

    socket.on('state', function(data) {

      // save the user state
      data.id = user.id;
      states[user.id] = data;

      // broadcast the user state
      socket.broadcast.emit('state', states[user.id]);

    });

    /**
     * Called when a player finishes a level.
     */

    socket.on('level_finished', function() {

      var nextLevelName = levels.getNextKey(game.level.name);

      if (typeof nextLevelName === 'string') {
        game.level = {
          name: nextLevelName,
          data: levels[nextLevelName]
        };
        socket.broadcast.emit('current_level', game.level);
      } else {
        socket.broadcast.emit('no_more_levels');
      }

    });

    /**
     * Called when the user disconnects.
     */

    socket.on('disconnect', function() {

      // delete the user state
      delete states[user.id];

      // notify the others users
      io.emit('player_left', user);

    });

  });

}

module.exports = SocketHandler;
<commit_msg>trying to fix back<commit_after>'use strict';

var _ = require('lodash');

var levels = require('./levels');
var utils = require('./utils');

/**
 * Handle the socket connections.
 *
 * @class
 * @param {Socket.io} io - The listening object
 */

function SocketHandler(io) {

  /**
   * Keep track of the last given id. Numbers are used so as to minimize the
   * packets size.
   *
   * @type {Number}
   */

  var lastId = 0;

  /**
   * Store the current state for all the players. The keys correspond to the
   * user ids, the value to their respective state.
   *
   * @type {Object}
   */

  var states = {};

  /**
   * Store the current game information (current level, etc)
   *
   * @type {Object}
   * @type {String} game.level - the name of the current level
   */

  var game = {
    level: {
      name: levels.getFirstKey(),
      data: levels.getFirst()
    }
  };

  /**
   * Called on each new user connection.
   */

  io.on('connection', function(socket) {

    var user = {};
    user.id = (lastId += 1);
    user.color = utils.getColorFromNumber(user.id);

    // give the information which could be useful when a user just arrives in a
    // game
    socket.emit('handshake', {
      user: user,
      game: game,
      states: _(_.cloneDeep(states))
        .forEach(function(value) {
          value.color = utils.getColorFromNumber(value.id);
        })
        .value()
    });

    // inform the other users
    socket.broadcast.emit('new_player', user);

    /**
     * Called when a player sends its position/velocity.
     */

    socket.on('state', function(data) {

      // save the user state
      data.id = user.id;
      states[user.id] = data;

      // broadcast the user state
      socket.broadcast.emit('state', states[user.id]);

    });

    /**
     * Called when a player finishes a level.
     */

    socket.on('level_finished', function() {

      var nextLevelName = levels.getNextKey(game.level.name);

        console.log(nextLevelName);
      if (typeof nextLevelName === 'string') {
        game.level = {
          name: nextLevelName
        };
        io.emit('current_level', game.level);
      } else {
        socket.broadcast.emit('no_more_levels');
      }

    });

    /**
     * Called when the user disconnects.
     */

    socket.on('disconnect', function() {

      // delete the user state
      delete states[user.id];

      // notify the others users
      io.emit('player_left', user);

    });

  });

}

module.exports = SocketHandler;
<|endoftext|>