<commit_before>(function(global) {

  'use strict';

  var fabric  = global.fabric || (global.fabric = { }),
      extend = fabric.util.object.extend;

  /**
   * Mask filter class
   * See http://resources.aleph-1.com/mask/
   * @class fabric.Image.filters.Mask
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition
   */
  fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ {

    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: 'Mask',

    /**
     * Constructor
     * @memberOf fabric.Image.filters.Mask.prototype
     * @param {Object} [options] Options object
     * @param {fabric.Image} [options.mask] Mask image object
     * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)
     */
    initialize: function(options) {
      options = options || { };

      this.mask = options.mask;
      this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0;
    },

    /**
     * Applies filter to canvas element
     * @param {Object} canvasEl Canvas element to apply filter to
     */
    applyTo: function(canvasEl) {
      if (!this.mask) {
        return;
      }

      var context = canvasEl.getContext('2d'),
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
          data = imageData.data,
          maskEl = this.mask.getElement(),
          maskCanvasEl = fabric.util.createCanvasElement(),
          channel = this.channel,
          i,
          iLen = imageData.width * imageData.height * 4;

      maskCanvasEl.width = maskEl.width;
      maskCanvasEl.height = maskEl.height;

      maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height);

      var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height),
          maskData = maskImageData.data;

      for (i = 0; i < iLen; i += 4) {
        data[i + 3] = maskData[i + channel];
      }

      context.putImageData(imageData, 0, 0);
    },

    /**
     * Returns object representation of an instance
     * @return {Object} Object representation of an instance
     */
    toObject: function() {
      return extend(this.callSuper('toObject'), {
        mask: this.mask.toObject(),
        channel: this.channel
      });
    }
  });

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when a mask filter instance is created
   */
  fabric.Image.filters.Mask.fromObject = function(object, callback) {
    fabric.util.loadImage(object.mask.src, function(img) {
      object.mask = new fabric.Image(img, object.mask);
      callback && callback(new fabric.Image.filters.Mask(object));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @type Boolean
   * @default
   */
  fabric.Image.filters.Mask.async = true;

})(typeof exports !== 'undefined' ? exports : this);
<commit_msg>Fixed bug that caused image to be corrupted when a mask was applyed only if the image was bigger the mask.<commit_after>(function(global) {

  'use strict';

  var fabric  = global.fabric || (global.fabric = { }),
      extend = fabric.util.object.extend;

  /**
   * Mask filter class
   * See http://resources.aleph-1.com/mask/
   * @class fabric.Image.filters.Mask
   * @memberOf fabric.Image.filters
   * @extends fabric.Image.filters.BaseFilter
   * @see {@link fabric.Image.filters.Mask#initialize} for constructor definition
   */
  fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ {

    /**
     * Filter type
     * @param {String} type
     * @default
     */
    type: 'Mask',

    /**
     * Constructor
     * @memberOf fabric.Image.filters.Mask.prototype
     * @param {Object} [options] Options object
     * @param {fabric.Image} [options.mask] Mask image object
     * @param {Number} [options.channel=0] Rgb channel (0, 1, 2 or 3)
     */
    initialize: function(options) {
      options = options || { };

      this.mask = options.mask;
      this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0;
    },

    /**
     * Applies filter to canvas element
     * @param {Object} canvasEl Canvas element to apply filter to
     */
    applyTo: function(canvasEl) {
      if (!this.mask) {
        return;
      }

      var context = canvasEl.getContext('2d'),
          imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
          data = imageData.data,
          maskEl = this.mask.getElement(),
          maskCanvasEl = fabric.util.createCanvasElement(),
          channel = this.channel,
          i,
          iLen = imageData.width * imageData.height * 4;

      maskCanvasEl.width = maskEl.width;
      maskCanvasEl.height = maskEl.height;

      maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, canvasEl.width, canvasEl.height);

      var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, canvasEl.width, canvasEl.height),
          maskData = maskImageData.data;

      for (i = 0; i < iLen; i += 4) {
        data[i + 3] = maskData[i + channel];
      }

      context.putImageData(imageData, 0, 0);
    },

    /**
     * Returns object representation of an instance
     * @return {Object} Object representation of an instance
     */
    toObject: function() {
      return extend(this.callSuper('toObject'), {
        mask: this.mask.toObject(),
        channel: this.channel
      });
    }
  });

  /**
   * Returns filter instance from an object representation
   * @static
   * @param {Object} object Object to create an instance from
   * @param {Function} [callback] Callback to invoke when a mask filter instance is created
   */
  fabric.Image.filters.Mask.fromObject = function(object, callback) {
    fabric.util.loadImage(object.mask.src, function(img) {
      object.mask = new fabric.Image(img, object.mask);
      callback && callback(new fabric.Image.filters.Mask(object));
    });
  };

  /**
   * Indicates that instances of this type are async
   * @static
   * @type Boolean
   * @default
   */
  fabric.Image.filters.Mask.async = true;

})(typeof exports !== 'undefined' ? exports : this);
<|endoftext|>


<commit_before>var http = require123456('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World\n");
});

server.listen(8000);

console.log("Server is running at http://127.0.0.1:8000/");
<commit_msg>Update app.js<commit_after>var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World\n");
});

server.listen(8000);

console.log("Server is running at http://127.0.0.1:8000/");
<|endoftext|>


<commit_before>var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World\n");
});

server.listen(8000);

console.log("Server is running at http://127.0.0.1:8000/");
<commit_msg>Update app.js<commit_after>var http = require('http');

var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("My Mom\n");
});

server.listen(8000);

console.log("Server is running at http://127.0.0.1:8000/");
<|endoftext|>


<commit_before>#!/usr/bin/env node

/**
 * What's this?? It will help you create release blog
 * posts so you won't have to do the tedious work
 * of stitching together data from changelog, shasums etc,
 * but get a more or less complete release blog ready to go.
 *
 * Usage: $ node release-post.js [version]
 *
 * If the version argument is omitted, the latest version number
 * will be picked from https://nodejs.org/dist/index.json.
 *
 * It'll create a file with the blog post content
 * into ../locale/en/blog/release/vX.md ready for you to commit
 * or possibly edit by hand before committing.
 *
 * Happy releasing!
 */

'use strict'

const fs = require('fs')
const path = require('path')
const Handlebars = require('handlebars')
const fetch = require('node-fetch')

const downloads = require('./helpers/downloads')

function sendRequest (opts) {
  const options = {
    headers: { 'User-Agent': 'nodejs.org release blog post script' },
    ...opts
  }

  return fetch(options.url, options).then(resp => {
    if (resp.status !== 200) {
      throw new Error(`Invalid status code (!= 200) while retrieving ${options.url}: ${resp.status}`)
    }
    return options.json ? resp.json() : resp.text()
  })
}

function explicitVersion (version) {
  return version
    ? Promise.resolve(version)
    : Promise.reject(new Error('Invalid "version" argument'))
}

function findLatestVersion () {
  return sendRequest({ url: 'https://nodejs.org/dist/index.json', json: true })
    .then((versions) => versions[0].version.substr(1))
}

function fetchDocs (version) {
  return Promise.all([
    fetchChangelogBody(version),
    fetchAuthor(version),
    fetchVersionPolicy(version),
    fetchShasums(version),
    verifyDownloads(version)
  ]).then((results) => {
    const [changelog, author, versionPolicy, shasums, files] = results
    return {
      version,
      changelog,
      author,
      versionPolicy,
      shasums,
      files
    }
  })
}

function fetchAuthor (version) {
  return fetchChangelog(version)
    .then((section) => findAuthorLogin(version, section))
    .then((author) => sendRequest({
      url: `https://api.github.com/users/${author}`,
      json: true
    }))
    .then((githubRes) => githubRes.name)
}

function fetchChangelog (version) {
  const parts = version.split('.')
  const releaseLine = parts[0] === '0'
    ? parts.slice(0, 2).join('')
    : parts[0]

  return sendRequest({
    url: `https://raw.githubusercontent.com/nodejs/node/master/doc/changelogs/CHANGELOG_V${releaseLine}.md`
  }).then((data) => {
    // matches a complete release section
    const rxSection = new RegExp(`<a id="${version}"></a>\\n([\\s\\S]+?)(?:\\n<a id="|$)`)
    const matches = rxSection.exec(data)
    return matches
      ? matches[1].trim()
      : Promise.reject(new Error(`Couldn't find matching changelog for ${version}`))
  })
}

function fetchChangelogBody (version) {
  return fetchChangelog(version).then((section) => {
    const rxSectionBody = /(### Notable [\s\S]*)/

    // Make sure that all the console has been replaced
    // by "```shell-session" for metalsmith-prism's check to pass
    const rxSectionConsole = /```console/igm
    const matches = rxSectionBody.exec(section)
    return matches
      ? matches[1].trim().replace(rxSectionConsole, '```shell-session')
      : Promise.reject(new Error(`Could not find changelog body of ${version} release`))
  })
}

function fetchVersionPolicy (version) {
  return fetchChangelog(version).then((section) => {
    // matches the policy for a given version (Stable, LTS etc) in the changelog
    // ## 2015-10-07, Version 4.2.0 'Argon' (LTS), @jasnell
    // ## 2015-12-04, Version 0.12.9 (LTS), @rvagg
    const rxPolicy = /^## ?\d{4}-\d{2}-\d{2}, Version [^(].*\(([^)]+)\)/
    const matches = rxPolicy.exec(section)
    return matches
      ? matches[1]
      : Promise.reject(new Error(`Could not find version policy of ${version} in its changelog`))
  })
}

function fetchShasums (version) {
  return sendRequest({ url: `https://nodejs.org/dist/v${version}/SHASUMS256.txt.asc` })
    .then(null, () => '[INSERT SHASUMS HERE]')
}

function verifyDownloads (version) {
  const allDownloads = downloads(version)
  const reqs = allDownloads.map(urlOrComingSoon)
  return Promise.all(reqs)
}

function findAuthorLogin (version, section) {
  // looking for the @author part of the release header, eg:
  // ## 2016-03-08, Version 5.8.0 (Stable). @Fishrock123
  // ## 2015-10-13, Version 4.2.1 'Argon' (LTS), @jasnell
  // ## 2015-09-08, Version 4.0.0 (Stable), @rvagg
  const rxReleaseAuthor = /^## .*? \([^)]+\)[,.] @(\S+)/
  const matches = rxReleaseAuthor.exec(section)
  return matches
    ? matches[1]
    : Promise.reject(new Error(`Couldn't find @author of ${version} release :(`))
}

function urlOrComingSoon (binary) {
  return sendRequest({ url: binary.url, method: 'HEAD' }).then(
    () => `${binary.title}: ${binary.url}`,
    () => `${binary.title}: *Coming soon*`
  )
}

function renderPost (results) {
  const templateStr = fs.readFileSync(path.resolve(__dirname, 'release.hbs')).toString('utf8')
  const template = Handlebars.compile(templateStr, { noEscape: true })
  const view = Object.assign({
    date: new Date().toISOString(),
    versionSlug: slugify(results.version)
  }, results)

  return Object.assign({
    content: template(view)
  }, results)
}

function writeToFile (results) {
  const filepath = path.resolve(__dirname, '..', 'locale', 'en', 'blog', 'release', `v${results.version}.md`)

  return new Promise((resolve, reject) => {
    fs.access(filepath, fs.F_OK, (err) => {
      if (!err && process.argv[3] !== '--force') {
        return reject(new Error(`Release post for ${results.version} already exists!`))
      }

      fs.writeFile(filepath, results.content, (err1) => {
        if (err1) {
          return reject(new Error(`Failed to write Release post: Reason: ${err1.message}`))
        }

        resolve(filepath)
      })
    })
  })
}

function slugify (str) {
  return str.replace(/\./g, '-')
}

exports.explicitVersion = explicitVersion
exports.fetchShasums = fetchShasums
exports.writeToFile = writeToFile
exports.findLatestVersion = findLatestVersion
exports.verifyDownloads = verifyDownloads
exports.fetchChangelog = fetchChangelog
exports.fetchChangelogBody = fetchChangelogBody
exports.fetchAuthor = fetchAuthor
exports.fetchVersionPolicy = fetchVersionPolicy

// when script is executed directly,
// not required by another module, e.g:
// $ node scripts/release-post.js
if (require.main === module) {
  explicitVersion(process.argv[2])
    .then(null, findLatestVersion)
    .then(fetchDocs)
    .then(renderPost)
    .then(writeToFile)
    .then((filepath) => {
      console.log('Release post created:', filepath)
    }, (err) => {
      console.error('Some error occurred here!', err.stack)
      process.exit(1)
    })
}
<commit_msg>fix: print out "Coming Soon" when there's no downloadable link (#4102)<commit_after>#!/usr/bin/env node

/**
 * What's this?? It will help you create release blog
 * posts so you won't have to do the tedious work
 * of stitching together data from changelog, shasums etc,
 * but get a more or less complete release blog ready to go.
 *
 * Usage: $ node release-post.js [version]
 *
 * If the version argument is omitted, the latest version number
 * will be picked from https://nodejs.org/dist/index.json.
 *
 * It'll create a file with the blog post content
 * into ../locale/en/blog/release/vX.md ready for you to commit
 * or possibly edit by hand before committing.
 *
 * Happy releasing!
 */

'use strict'

const fs = require('fs')
const path = require('path')
const Handlebars = require('handlebars')
const fetch = require('node-fetch')

const downloads = require('./helpers/downloads')

function sendRequest (opts) {
  const options = {
    headers: { 'User-Agent': 'nodejs.org release blog post script' },
    ...opts
  }

  return fetch(options.url, options).then(resp => {
    if (resp.status !== 200) {
      throw new Error(`Invalid status code (!= 200) while retrieving ${options.url}: ${resp.status}`)
    }
    return options.json ? resp.json() : resp.text()
  })
}

function explicitVersion (version) {
  return version
    ? Promise.resolve(version)
    : Promise.reject(new Error('Invalid "version" argument'))
}

function findLatestVersion () {
  return sendRequest({ url: 'https://nodejs.org/dist/index.json', json: true })
    .then((versions) => versions[0].version.substr(1))
}

function fetchDocs (version) {
  return Promise.all([
    fetchChangelogBody(version),
    fetchAuthor(version),
    fetchVersionPolicy(version),
    fetchShasums(version),
    verifyDownloads(version)
  ]).then((results) => {
    const [changelog, author, versionPolicy, shasums, files] = results
    return {
      version,
      changelog,
      author,
      versionPolicy,
      shasums,
      files
    }
  })
}

function fetchAuthor (version) {
  return fetchChangelog(version)
    .then((section) => findAuthorLogin(version, section))
    .then((author) => sendRequest({
      url: `https://api.github.com/users/${author}`,
      json: true
    }))
    .then((githubRes) => githubRes.name)
}

function fetchChangelog (version) {
  const parts = version.split('.')
  const releaseLine = parts[0] === '0'
    ? parts.slice(0, 2).join('')
    : parts[0]

  return sendRequest({
    url: `https://raw.githubusercontent.com/nodejs/node/master/doc/changelogs/CHANGELOG_V${releaseLine}.md`
  }).then((data) => {
    // matches a complete release section
    const rxSection = new RegExp(`<a id="${version}"></a>\\n([\\s\\S]+?)(?:\\n<a id="|$)`)
    const matches = rxSection.exec(data)
    return matches
      ? matches[1].trim()
      : Promise.reject(new Error(`Couldn't find matching changelog for ${version}`))
  })
}

function fetchChangelogBody (version) {
  return fetchChangelog(version).then((section) => {
    const rxSectionBody = /(### Notable [\s\S]*)/

    // Make sure that all the console has been replaced
    // by "```shell-session" for metalsmith-prism's check to pass
    const rxSectionConsole = /```console/igm
    const matches = rxSectionBody.exec(section)
    return matches
      ? matches[1].trim().replace(rxSectionConsole, '```shell-session')
      : Promise.reject(new Error(`Could not find changelog body of ${version} release`))
  })
}

function fetchVersionPolicy (version) {
  return fetchChangelog(version).then((section) => {
    // matches the policy for a given version (Stable, LTS etc) in the changelog
    // ## 2015-10-07, Version 4.2.0 'Argon' (LTS), @jasnell
    // ## 2015-12-04, Version 0.12.9 (LTS), @rvagg
    const rxPolicy = /^## ?\d{4}-\d{2}-\d{2}, Version [^(].*\(([^)]+)\)/
    const matches = rxPolicy.exec(section)
    return matches
      ? matches[1]
      : Promise.reject(new Error(`Could not find version policy of ${version} in its changelog`))
  })
}

function fetchShasums (version) {
  return sendRequest({ url: `https://nodejs.org/dist/v${version}/SHASUMS256.txt.asc` })
    .then(null, () => '[INSERT SHASUMS HERE]')
}

function verifyDownloads (version) {
  const allDownloads = downloads(version)
  const reqs = allDownloads.map(urlOrComingSoon)
  return Promise.all(reqs)
}

function findAuthorLogin (version, section) {
  // looking for the @author part of the release header, eg:
  // ## 2016-03-08, Version 5.8.0 (Stable). @Fishrock123
  // ## 2015-10-13, Version 4.2.1 'Argon' (LTS), @jasnell
  // ## 2015-09-08, Version 4.0.0 (Stable), @rvagg
  const rxReleaseAuthor = /^## .*? \([^)]+\)[,.] @(\S+)/
  const matches = rxReleaseAuthor.exec(section)
  return matches
    ? matches[1]
    : Promise.reject(new Error(`Couldn't find @author of ${version} release :(`))
}

function urlOrComingSoon (binary) {
  return sendRequest({ url: binary.url, method: 'HEAD' }).then(
    () => `${binary.title}: ${binary.url}`,
    () => {
      console.log(`\x1B[32m "${binary.title}" is Coming soon...\x1B[39m`)
      return `${binary.title}: *Coming soon*`
    }
  )
}

function renderPost (results) {
  const templateStr = fs.readFileSync(path.resolve(__dirname, 'release.hbs')).toString('utf8')
  const template = Handlebars.compile(templateStr, { noEscape: true })
  const view = Object.assign({
    date: new Date().toISOString(),
    versionSlug: slugify(results.version)
  }, results)

  return Object.assign({
    content: template(view)
  }, results)
}

function writeToFile (results) {
  const filepath = path.resolve(__dirname, '..', 'locale', 'en', 'blog', 'release', `v${results.version}.md`)

  return new Promise((resolve, reject) => {
    fs.access(filepath, fs.F_OK, (err) => {
      if (!err && process.argv[3] !== '--force') {
        return reject(new Error(`Release post for ${results.version} already exists!`))
      }

      fs.writeFile(filepath, results.content, (err1) => {
        if (err1) {
          return reject(new Error(`Failed to write Release post: Reason: ${err1.message}`))
        }

        resolve(filepath)
      })
    })
  })
}

function slugify (str) {
  return str.replace(/\./g, '-')
}

exports.explicitVersion = explicitVersion
exports.fetchShasums = fetchShasums
exports.writeToFile = writeToFile
exports.findLatestVersion = findLatestVersion
exports.verifyDownloads = verifyDownloads
exports.fetchChangelog = fetchChangelog
exports.fetchChangelogBody = fetchChangelogBody
exports.fetchAuthor = fetchAuthor
exports.fetchVersionPolicy = fetchVersionPolicy

// when script is executed directly,
// not required by another module, e.g:
// $ node scripts/release-post.js
if (require.main === module) {
  explicitVersion(process.argv[2])
    .then(null, findLatestVersion)
    .then(fetchDocs)
    .then(renderPost)
    .then(writeToFile)
    .then((filepath) => {
      console.log('Release post created:', filepath)
    }, (err) => {
      console.error('Some error occurred here!', err.stack)
      process.exit(1)
    })
}
<|endoftext|>


<commit_before>var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var stats = require('../stats.js');
var Sandbox = selftest.Sandbox;

// NOTE: This test will fail if your machine's time is skewed by more
// than 30 minutes. This is because the `fetchAppPackageUsage` method
// works by passing an hour time range.
selftest.define("report-stats", function () {
  var s = new Sandbox;

  var run = s.run("create", "foo");
  run.expectExit(0);
  s.cd("foo");

  // verify that identifier file exists for new apps
  var identifier = s.read(".meteor/identifier");
  selftest.expectEqual(!! identifier, true);
  selftest.expectEqual(identifier.length > 0, true);

  // verify that identifier file when running 'meteor bundle' on old
  // apps
  s.unlink(".meteor/identifier");
  run = s.run("bundle", "foo.tar.gz");
  run.waitSecs(30);
  run.expectExit(0);
  identifier = s.read(".meteor/identifier");
  selftest.expectEqual(!! identifier, true);
  selftest.expectEqual(identifier.length > 0, true);

  // we just ran 'meteor bundle' so let's test that we actually sent
  // package usage stats
  var usage = fetchPackageUsageForApp(identifier);
  selftest.expectEqual(usage.packages, stats.packageList(s.cwd));

  // TODO:
  // - test both the logged in state and the logged out state
  // - opt out
});

// Contact the package stats server and look for a given app
// identifier reported in the range (now - 30 minutes, now + 30
// minutes). Fails if packages for the same app was not recorded, or
// was recorded more than once.
//
// Returns the (unique) package usage document for the given app.
var fetchPackageUsageForApp = function (identifier) {
  var stats = testUtils.ddpConnect(/*xcxc*/ "test-packages-stats.meteor.com");
  var nowMinus30Minutes = new Date(new Date - 1000 * 60 * 30 /*ms*/);
  var nowPlus30Minutes = new Date(nowMinus30Minutes + 1000 * 60 * 60 /*ms*/);
  var usage = stats.call("fetchAppPackageUsage",
             nowMinus30Minutes, nowPlus30Minutes, "like a boss" /*apiKey*/);

  var found = null;
  usage.forEach(function (record) {
    if (record.appId === identifier) {
      if (found) {
        selftest.fail("Found app identifier twice in usage " +
                      "returned from package stats server");
      } else {
        found = record;
      }
    }
  });

  if (! found)
    selftest.fail("Couldn't find app identifier in usage " +
                  "returned from package stats server");

  return found;
};
<commit_msg>Fix stats reporting test date<commit_after>var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var stats = require('../stats.js');
var Sandbox = selftest.Sandbox;

// NOTE: This test will fail if your machine's time is skewed by more
// than 30 minutes. This is because the `fetchAppPackageUsage` method
// works by passing an hour time range.
selftest.define("report-stats", function () {
  var s = new Sandbox;

  var run = s.run("create", "foo");
  run.expectExit(0);
  s.cd("foo");

  // verify that identifier file exists for new apps
  var identifier = s.read(".meteor/identifier");
  selftest.expectEqual(!! identifier, true);
  selftest.expectEqual(identifier.length > 0, true);

  // verify that identifier file when running 'meteor bundle' on old
  // apps
  s.unlink(".meteor/identifier");
  run = s.run("bundle", "foo.tar.gz");
  run.waitSecs(30);
  run.expectExit(0);
  identifier = s.read(".meteor/identifier");
  selftest.expectEqual(!! identifier, true);
  selftest.expectEqual(identifier.length > 0, true);

  // we just ran 'meteor bundle' so let's test that we actually sent
  // package usage stats
  var usage = fetchPackageUsageForApp(identifier);
  selftest.expectEqual(usage.packages, stats.packageList(s.cwd));

  // TODO:
  // - test both the logged in state and the logged out state
  // - opt out
});

// Contact the package stats server and look for a given app
// identifier reported in the range (now - 30 minutes, now + 30
// minutes). Fails if packages for the same app was not recorded, or
// was recorded more than once.
//
// Returns the (unique) package usage document for the given app.
var fetchPackageUsageForApp = function (identifier) {
  var stats = testUtils.ddpConnect(/*xcxc*/ "test-packages-stats.meteor.com");
  var nowMinus30Minutes = new Date(new Date - 1000 * 60 * 30 /*ms*/);
  var nowPlus30Minutes = new Date(+nowMinus30Minutes + 1000 * 60 * 60 /*ms*/);
  var usage = stats.call("fetchAppPackageUsage",
             nowMinus30Minutes, nowPlus30Minutes, "like a boss" /*apiKey*/);

  var found = null;
  usage.forEach(function (record) {
    if (record.appId === identifier) {
      if (found) {
        selftest.fail("Found app identifier twice in usage " +
                      "returned from package stats server");
      } else {
        found = record;
      }
    }
  });

  if (! found)
    selftest.fail("Couldn't find app identifier in usage " +
                  "returned from package stats server");

  return found;
};
<|endoftext|>


<commit_before>$(document).on('click', '.set-desktop, .set-mobile', function(event){
  $('.controls a').removeClass('selected');
  $(this).addClass('selected');
});

$(document).on('click', '.set-desktop', function(event) {
  $('.frame-wrapper')
    .removeClass('mobile')
    .removeClass('iphone-4')
    .removeClass('iphone-5')
    .removeClass('ipad')
    .addClass('desktop');
  $('.orientation-toggle').addClass('disabled')
});

$(document).on('click', '.set-mobile', function(event) {
  $('.frame-wrapper')
    .removeClass('desktop')
    .addClass('mobile');
  $('.orientation-toggle').removeClass('disabled')
});

$(document).on('click', '.set-iphone-4', function(event) {
  $('.frame-wrapper')
    .removeClass('iphone-5')
    .removeClass('ipad')
    .addClass('iphone-4');
});

$(document).on('click', '.set-iphone-5', function(event) {
  $('.frame-wrapper')
    .removeClass('iphone-4')
    .removeClass('ipad')
    .addClass('iphone-5');
});

$(document).on('click', '.set-ipad', function(event) {
  $('.frame-wrapper')
    .removeClass('iphone-4')
    .removeClass('iphone-5')
    .addClass('ipad');
});

$(document).on('click', '.orientation-toggle', function(event) {
  if (!$('.orientation-toggle').hasClass('disabled')){
    if ($('.frame-wrapper').hasClass('landscape')){
      $('.frame-wrapper').removeClass('landscape');
      $('.controls').removeClass('landscape');
    } else {
      $('.frame-wrapper').addClass('landscape');
      $('.controls').addClass('landscape');
    }
  }
});

$(document).ready(function(){
  if (window.location.origin != 'http://cobyism.com/'){
    $('.frame').attr('src', '../');
  }
});<commit_msg>no trailing slash<commit_after>$(document).on('click', '.set-desktop, .set-mobile', function(event){
  $('.controls a').removeClass('selected');
  $(this).addClass('selected');
});

$(document).on('click', '.set-desktop', function(event) {
  $('.frame-wrapper')
    .removeClass('mobile')
    .removeClass('iphone-4')
    .removeClass('iphone-5')
    .removeClass('ipad')
    .addClass('desktop');
  $('.orientation-toggle').addClass('disabled')
});

$(document).on('click', '.set-mobile', function(event) {
  $('.frame-wrapper')
    .removeClass('desktop')
    .addClass('mobile');
  $('.orientation-toggle').removeClass('disabled')
});

$(document).on('click', '.set-iphone-4', function(event) {
  $('.frame-wrapper')
    .removeClass('iphone-5')
    .removeClass('ipad')
    .addClass('iphone-4');
});

$(document).on('click', '.set-iphone-5', function(event) {
  $('.frame-wrapper')
    .removeClass('iphone-4')
    .removeClass('ipad')
    .addClass('iphone-5');
});

$(document).on('click', '.set-ipad', function(event) {
  $('.frame-wrapper')
    .removeClass('iphone-4')
    .removeClass('iphone-5')
    .addClass('ipad');
});

$(document).on('click', '.orientation-toggle', function(event) {
  if (!$('.orientation-toggle').hasClass('disabled')){
    if ($('.frame-wrapper').hasClass('landscape')){
      $('.frame-wrapper').removeClass('landscape');
      $('.controls').removeClass('landscape');
    } else {
      $('.frame-wrapper').addClass('landscape');
      $('.controls').addClass('landscape');
    }
  }
});

$(document).ready(function(){
  if (window.location.origin != 'http://cobyism.com'){
    $('.frame').attr('src', '../');
  }
});<|endoftext|>


<commit_before>///
/// utility functions for dealing with urls and http
///

var os = require('os');
var util = require('util');

var _ = require('underscore');

var files = require('../fs/files.js');
var auth = require('../meteor-services/auth.js');
var config = require('../meteor-services/config.js');
var release = require('../packaging/release.js');
var Console = require('../console/console.js').Console;
var timeoutScaleFactor = require('./utils.js').timeoutScaleFactor;

import { WritableStreamBuffer } from 'stream-buffers';
import fiberHelpers from '../utils/fiber-helpers.js';

// Helper that tracks bytes written to a writable
var WritableWithProgress = function (writable, listener) {
  var self = this;
  self._inner = writable;
  self._listener = listener;
};

_.extend(WritableWithProgress.prototype, {
  write: function (chunk, encoding, callback) {
    var self = this;
    self._listener(chunk.length, false);
    return self._inner.write(chunk, encoding);
  },

  end: function (chunk, encoding, callback) {
    var self = this;
    self._listener(chunk ? chunk.length : 0, true);
    return self._inner.end(chunk, encoding);
  },

  _progress: function (n, done) {
    var self = this;

    var state = self._state;
    state.current += n;
    if (done) {
      state.current.done = true;
    }
    self.progress.reportProgress(state);
  },

  on: function (name, callback) {
    return this._inner.on(name, callback);
  },

  once: function () {
    return this._inner.once(...arguments);
  },

  emit: function () {
    return this._inner.emit(...arguments);
  }
});

// Compose a User-Agent header.
var getUserAgent = function () {
  var version;

  if (release.current) {
    version = release.current.isCheckout() ? 'checkout' : release.current.name;
  } else {
    // This happens when we haven't finished starting up yet (say, the
    // user passed --release 1.2.3 and we have to download 1.2.3
    // before we can get going), or if we are using an installed copy
    // of Meteor to 'meteor update'ing a project that was created by a
    // checkout and doesn't have a version yet.
    version = files.inCheckout() ? 'checkout' : files.getToolsVersion();
  }

  return util.format('Meteor/%s OS/%s (%s; %s; %s;)', version,
                     os.platform(), os.type(), os.release(), os.arch());
};

var httpHelpers = exports;
_.extend(exports, {
  // For testing purposes, do not use (obviously it doesn't really make
  // sense to have only one current request)
  _currentRequest: null,

  getUserAgent: getUserAgent,

  // A wrapper around request with the following improvements:
  //
  // - It will respect proxy environment variables if present
  //   (HTTP_PROXY or HTTPS_PROXY as appropriate).
  //
  // - It will set a reasonable User-Agent header.
  //
  // - If you omit the callback it will run synchronously. The return
  //   value will be an object with keys 'response' and 'body' (with
  //   the same meaning as the arguments to request's normal
  //   callback), or it will throw.
  //
  // - If Set-Cookie headers are present on the response, *and* you
  //   are using a callback, it will parse the cookies and include
  //   them as a setCookie attribute on the object passed to the
  //   callback. setCookie is a simple map from cookie name to cookie
  //   value. If you want expiration time and attributes you'll have
  //   to parse it yourself. If there are multiple Set-Cookie headers
  //   for the same cookie it is unspecified which one you'll get.
  //
  // - You can provide a 'bodyStream' option which is a stream that
  //   will be used for the body of the request.
  //
  // - For authenticated MDG services, you can set the
  //   'useSessionHeader' and/or 'useAuthHeader' options (to true) to
  //   send X-Meteor-Session/X-Meteor-Auth headers using values from
  //   the session file.
  //
  // - forceSSL is always set to true. Always. And followRedirect is
  //   set to false since it doesn't understand origins (see comment
  //   in implementation).
  //
  // NB: With useSessionHeader and useAuthHeader, this function will
  // read *and possibly write to* the session file, so if you are
  // writing auth code (in auth.js) and you call it, be sure to reread
  // the session file afterwards.
  request: function (urlOrOptions, callback) {
    var options;
    if (!_.isObject(urlOrOptions)) {
      options = { url: urlOrOptions };
    } else {
      options = _.clone(urlOrOptions);
    }

    var outputStream;
    if (_.has(options, 'outputStream')) {
      outputStream = options.outputStream;
      delete options.outputStream;
    }

    var bodyStream;
    if (_.has(options, 'bodyStream')) {
      bodyStream = options.bodyStream;
      delete options.bodyStream;
    }

    // Body stream length for progress
    var bodyStreamLength = 0;
    if (_.has(options, 'bodyStream')) {
      bodyStreamLength = options.bodyStreamLength;
      delete options.bodyStreamLength;
    } else {
      // Guess the body stream length as 1MB
      // Hopefully if it's much bigger the caller will set it
      // If it is much small, we will pleasantly surprise the user!
      if (bodyStream) {
        bodyStreamLength = 1024 * 1024;
      }
    }

    // Response length for progress
    var responseLength = 128 * 1024;
    if (_.has(options, 'responseLength')) {
      responseLength = options.responseLength;
      delete options.responseLength;
    }

    var progress = null;
    if (_.has(options, 'progress')) {
      progress = options.progress;
      delete options.progress;

      if (callback) {
        throw new Error("Not safe to use progress with callback");
      }
    }

    options.headers = _.extend({
      'User-Agent': getUserAgent()
    }, options.headers || {});

    // This should never, ever be false, or else why are you using SSL?
    options.forceSSL = true;
    if (process.env.CAFILE) {
      options.ca = files.readFile(process.env.CAFILE);
    }

    // followRedirect is very dangerous because request does not
    // appear to segregate cookies by origin, so any cookies (and
    // apparently headers as well, eg X-Meteor-Auth) sent on the
    // original request could get forwarded to an unexpected domain in
    // a redirect. This is almost certainly not something you ever
    // want.
    options.followRedirect = false;

    var useSessionHeader = options.useSessionHeader;
    delete options.useSessionHeader;
    var useAuthHeader = options.useAuthHeader;
    delete options.useAuthHeader;
    if (useSessionHeader || useAuthHeader) {
      var sessionHeader = auth.getSessionId(config.getAccountsDomain());
      if (sessionHeader) {
        options.headers['X-Meteor-Session'] = sessionHeader;
      }
      if (callback) {
        throw new Error("session header can't be used with callback");
      }
    }
    if (useAuthHeader) {
      var authHeader = auth.getSessionToken(config.getAccountsDomain());
      if (authHeader) {
        options.headers['X-Meteor-Auth'] = authHeader;
      }
    }

    var promise;
    if (! callback) {
      promise = new Promise(function (resolve, reject) {
        callback = function (err, response, body) {
          if (err) {
            reject(err);
            return;
          }

          var setCookie = {};
          _.each(response.headers["set-cookie"] || [], function (h) {
            var match = h.match(/^([^=\s]+)=([^;\s]+)/);
            if (match) {
              setCookie[match[1]] = match[2];
            }
          });

          if (useSessionHeader && _.has(response.headers, "x-meteor-session")) {
            auth.setSessionId(config.getAccountsDomain(),
                              response.headers['x-meteor-session']);
          }

          resolve({
            response: response,
            body: body,
            setCookie: setCookie
          });
        };
      });
    }

    // try to get proxy from environment.
    // similar code is in packages/ddp/stream_client_nodejs.js
    var proxy = process.env.HTTP_PROXY || process.env.http_proxy || null;
    // if we're going to an https url, try the https_proxy env variable first.
    if (/^https/i.test(options.url)) {
      proxy = process.env.HTTPS_PROXY || process.env.https_proxy || proxy;
    }
    if (proxy && !options.proxy) {
      options.proxy = proxy;
    }

    if (! _.has(options, "timeout")) {
      // 60 seconds for timeout between initial response headers and data,
      // and between chunks of data while reading the rest of the response.
      options.timeout = 60 * 1000 * timeoutScaleFactor;
    } else if (! (typeof options.timeout === "number" &&
                  options.timeout > 0)) {
      // The timeout can be disabled by passing anything other than a
      // positive number, e.g. { timeout: null }.
      delete options.timeout;
    }

    // request is the most heavy-weight of the tool's npm dependencies; don't
    // require it until we definitely need it.
    Console.debug("Doing HTTP request: ", options.method || 'GET', options.url);
    var request = require('request');
    var req = request(options, callback);

    // A handle for testing
    httpHelpers._currentRequest = req;

    var totalProgress = { current: 0, end: bodyStreamLength + responseLength, done: false };

    if (bodyStream) {
      var dest = req;
      if (progress) {
        dest = new WritableWithProgress(dest, function (n, done) {
          if (!totalProgress.done) {
            totalProgress.current += n;
            progress.reportProgress(totalProgress);
          }
        });
      }
      bodyStream.pipe(dest);
    }

    if (outputStream) {
      req.pipe(outputStream);
    }

    if (progress) {
      httpHelpers._addProgressEvents(req);
      req.on('progress', function (state) {
        if (!totalProgress.done) {
          totalProgress.current = bodyStreamLength + state.current;
          totalProgress.end = bodyStreamLength + state.end;
          totalProgress.done = state.done;

          progress.reportProgress(totalProgress);
        }
      });
    }

    if (promise) {
      try {
        return promise.await();
      } finally {
        if (progress) {
          progress.reportProgressDone();
        }
      }
    } else {
      return req;
    }
  },

  // Adds progress callbacks to a request
  // Based on request-progress
  _addProgressEvents: function (request) {
    var state;

    var emitProgress = function () {
      request.emit('progress', state);
    };

    request
      .on('response', function (response) {
        state = {};
        state.end = undefined;
        state.done = false;
        state.current = 0;
        var contentLength = response.headers['content-length'];
        if (contentLength) {
          state.end = Number(contentLength);
        }
        emitProgress();
      })
      .on('data', function (data) {
        state.current += data.length;
        emitProgress();
      })
      .on('end', function (data) {
        state.done = true;
        emitProgress();
      });
  },

  // A synchronous wrapper around request(...) that returns the response "body"
  // or throws.
  //
  // (This has gone through a few refactors and it might be possible
  // to fully roll it into httpHelpers.request() at this point.)
  getUrl: function (urlOrOptions) {
    try {
      var result = httpHelpers.request(urlOrOptions);
    } catch (e) {
      throw new files.OfflineError(e);
    }

    const response = result.response;
    const body = result.body;
    const href = response.request.href;

    if (response.statusCode >= 400 && response.statusCode < 600) {
      throw Error(
        body ||
          `Could not get ${href}; server returned [${response.statusCode}]`);
    } else {
      return body;
    }
  },

  getUrlWithResuming(urlOrOptions) {
    const options = _.isObject(urlOrOptions) ? _.clone(urlOrOptions) : {
      url: urlOrOptions,
    };

    const outputStream = new WritableStreamBuffer();

    const MAX_ATTEMPTS = 10;
    const RETRY_DELAY_SECS = 5;

    let lastSize = 0;
    function attempt(triesRemaining) {
      if (lastSize > 0) {
        options.headers = {
          ...options.headers,
          Range: `bytes=${outputStream.size()}-`
        };
      }

      try {
        return httpHelpers.request({
          outputStream,
          ...options,
        });
      } catch (e) {
        const size = outputStream.size();
        const useTry = size === lastSize;
        lastSize = outputStream.size();

        if (!useTry || triesRemaining > 0) {
          console.log(`Request failed, retrying`);
          return new Promise(resolve => {
            setTimeout(fiberHelpers.bindEnvironment(() => {
              resolve(attempt(useTry ? triesRemaining - 1 : triesRemaining));
            }, RETRY_DELAY_SECS * 1000));
          }).await();
        } else {
          console.log(`Request failed ${MAX_ATTEMPTS} times, failing`)
          throw new files.OfflineError(e);
        }
      }
    }

    const response = attempt(MAX_ATTEMPTS).response;
    if (response.statusCode >= 400 && response.statusCode < 600) {
      const href = response.request.href;
      throw Error(`Could not get ${href}; server returned [${response.statusCode}]`);
    } else {
      return outputStream.getContents();
    }
  }
});
<commit_msg>Better logging and progress<commit_after>///
/// utility functions for dealing with urls and http
///

var os = require('os');
var util = require('util');

var _ = require('underscore');

var files = require('../fs/files.js');
var auth = require('../meteor-services/auth.js');
var config = require('../meteor-services/config.js');
var release = require('../packaging/release.js');
var Console = require('../console/console.js').Console;
var timeoutScaleFactor = require('./utils.js').timeoutScaleFactor;

import { WritableStreamBuffer } from 'stream-buffers';
import fiberHelpers from '../utils/fiber-helpers.js';

// Helper that tracks bytes written to a writable
var WritableWithProgress = function (writable, listener) {
  var self = this;
  self._inner = writable;
  self._listener = listener;
};

_.extend(WritableWithProgress.prototype, {
  write: function (chunk, encoding, callback) {
    var self = this;
    self._listener(chunk.length, false);
    return self._inner.write(chunk, encoding);
  },

  end: function (chunk, encoding, callback) {
    var self = this;
    self._listener(chunk ? chunk.length : 0, true);
    return self._inner.end(chunk, encoding);
  },

  _progress: function (n, done) {
    var self = this;

    var state = self._state;
    state.current += n;
    if (done) {
      state.current.done = true;
    }
    self.progress.reportProgress(state);
  },

  on: function (name, callback) {
    return this._inner.on(name, callback);
  },

  once: function () {
    return this._inner.once(...arguments);
  },

  emit: function () {
    return this._inner.emit(...arguments);
  }
});

// Compose a User-Agent header.
var getUserAgent = function () {
  var version;

  if (release.current) {
    version = release.current.isCheckout() ? 'checkout' : release.current.name;
  } else {
    // This happens when we haven't finished starting up yet (say, the
    // user passed --release 1.2.3 and we have to download 1.2.3
    // before we can get going), or if we are using an installed copy
    // of Meteor to 'meteor update'ing a project that was created by a
    // checkout and doesn't have a version yet.
    version = files.inCheckout() ? 'checkout' : files.getToolsVersion();
  }

  return util.format('Meteor/%s OS/%s (%s; %s; %s;)', version,
                     os.platform(), os.type(), os.release(), os.arch());
};

var httpHelpers = exports;
_.extend(exports, {
  // For testing purposes, do not use (obviously it doesn't really make
  // sense to have only one current request)
  _currentRequest: null,

  getUserAgent: getUserAgent,

  // A wrapper around request with the following improvements:
  //
  // - It will respect proxy environment variables if present
  //   (HTTP_PROXY or HTTPS_PROXY as appropriate).
  //
  // - It will set a reasonable User-Agent header.
  //
  // - If you omit the callback it will run synchronously. The return
  //   value will be an object with keys 'response' and 'body' (with
  //   the same meaning as the arguments to request's normal
  //   callback), or it will throw.
  //
  // - If Set-Cookie headers are present on the response, *and* you
  //   are using a callback, it will parse the cookies and include
  //   them as a setCookie attribute on the object passed to the
  //   callback. setCookie is a simple map from cookie name to cookie
  //   value. If you want expiration time and attributes you'll have
  //   to parse it yourself. If there are multiple Set-Cookie headers
  //   for the same cookie it is unspecified which one you'll get.
  //
  // - You can provide a 'bodyStream' option which is a stream that
  //   will be used for the body of the request.
  //
  // - For authenticated MDG services, you can set the
  //   'useSessionHeader' and/or 'useAuthHeader' options (to true) to
  //   send X-Meteor-Session/X-Meteor-Auth headers using values from
  //   the session file.
  //
  // - forceSSL is always set to true. Always. And followRedirect is
  //   set to false since it doesn't understand origins (see comment
  //   in implementation).
  //
  // NB: With useSessionHeader and useAuthHeader, this function will
  // read *and possibly write to* the session file, so if you are
  // writing auth code (in auth.js) and you call it, be sure to reread
  // the session file afterwards.
  request: function (urlOrOptions, callback) {
    var options;
    if (!_.isObject(urlOrOptions)) {
      options = { url: urlOrOptions };
    } else {
      options = _.clone(urlOrOptions);
    }

    var outputStream;
    if (_.has(options, 'outputStream')) {
      outputStream = options.outputStream;
      delete options.outputStream;
    }

    var bodyStream;
    if (_.has(options, 'bodyStream')) {
      bodyStream = options.bodyStream;
      delete options.bodyStream;
    }

    // Body stream length for progress
    var bodyStreamLength = 0;
    if (_.has(options, 'bodyStream')) {
      bodyStreamLength = options.bodyStreamLength;
      delete options.bodyStreamLength;
    } else {
      // Guess the body stream length as 1MB
      // Hopefully if it's much bigger the caller will set it
      // If it is much small, we will pleasantly surprise the user!
      if (bodyStream) {
        bodyStreamLength = 1024 * 1024;
      }
    }

    // Response length for progress
    var responseLength = 128 * 1024;
    if (_.has(options, 'responseLength')) {
      responseLength = options.responseLength;
      delete options.responseLength;
    }

    var progress = null;
    if (_.has(options, 'progress')) {
      progress = options.progress;
      delete options.progress;

      if (callback) {
        throw new Error("Not safe to use progress with callback");
      }
    }

    options.headers = _.extend({
      'User-Agent': getUserAgent()
    }, options.headers || {});

    // This should never, ever be false, or else why are you using SSL?
    options.forceSSL = true;
    if (process.env.CAFILE) {
      options.ca = files.readFile(process.env.CAFILE);
    }

    // followRedirect is very dangerous because request does not
    // appear to segregate cookies by origin, so any cookies (and
    // apparently headers as well, eg X-Meteor-Auth) sent on the
    // original request could get forwarded to an unexpected domain in
    // a redirect. This is almost certainly not something you ever
    // want.
    options.followRedirect = false;

    var useSessionHeader = options.useSessionHeader;
    delete options.useSessionHeader;
    var useAuthHeader = options.useAuthHeader;
    delete options.useAuthHeader;
    if (useSessionHeader || useAuthHeader) {
      var sessionHeader = auth.getSessionId(config.getAccountsDomain());
      if (sessionHeader) {
        options.headers['X-Meteor-Session'] = sessionHeader;
      }
      if (callback) {
        throw new Error("session header can't be used with callback");
      }
    }
    if (useAuthHeader) {
      var authHeader = auth.getSessionToken(config.getAccountsDomain());
      if (authHeader) {
        options.headers['X-Meteor-Auth'] = authHeader;
      }
    }

    var promise;
    if (! callback) {
      promise = new Promise(function (resolve, reject) {
        callback = function (err, response, body) {
          if (err) {
            reject(err);
            return;
          }

          var setCookie = {};
          _.each(response.headers["set-cookie"] || [], function (h) {
            var match = h.match(/^([^=\s]+)=([^;\s]+)/);
            if (match) {
              setCookie[match[1]] = match[2];
            }
          });

          if (useSessionHeader && _.has(response.headers, "x-meteor-session")) {
            auth.setSessionId(config.getAccountsDomain(),
                              response.headers['x-meteor-session']);
          }

          resolve({
            response: response,
            body: body,
            setCookie: setCookie
          });
        };
      });
    }

    // try to get proxy from environment.
    // similar code is in packages/ddp/stream_client_nodejs.js
    var proxy = process.env.HTTP_PROXY || process.env.http_proxy || null;
    // if we're going to an https url, try the https_proxy env variable first.
    if (/^https/i.test(options.url)) {
      proxy = process.env.HTTPS_PROXY || process.env.https_proxy || proxy;
    }
    if (proxy && !options.proxy) {
      options.proxy = proxy;
    }

    if (! _.has(options, "timeout")) {
      // 60 seconds for timeout between initial response headers and data,
      // and between chunks of data while reading the rest of the response.
      options.timeout = 60 * 1000 * timeoutScaleFactor;
    } else if (! (typeof options.timeout === "number" &&
                  options.timeout > 0)) {
      // The timeout can be disabled by passing anything other than a
      // positive number, e.g. { timeout: null }.
      delete options.timeout;
    }

    // request is the most heavy-weight of the tool's npm dependencies; don't
    // require it until we definitely need it.
    Console.debug("Doing HTTP request: ", options.method || 'GET', options.url);
    var request = require('request');
    var req = request(options, callback);

    // A handle for testing
    httpHelpers._currentRequest = req;

    var totalProgress = { current: 0, end: bodyStreamLength + responseLength, done: false };

    if (bodyStream) {
      var dest = req;
      if (progress) {
        dest = new WritableWithProgress(dest, function (n, done) {
          if (!totalProgress.done) {
            totalProgress.current += n;
            progress.reportProgress(totalProgress);
          }
        });
      }
      bodyStream.pipe(dest);
    }

    if (outputStream) {
      req.pipe(outputStream);
    }

    if (progress) {
      httpHelpers._addProgressEvents(req);
      req.on('progress', function (state) {
        if (!totalProgress.done) {
          totalProgress.current = bodyStreamLength + state.current;
          totalProgress.end = bodyStreamLength + state.end;
          totalProgress.done = state.done;

          progress.reportProgress(totalProgress);
        }
      });
    }

    if (promise) {
      try {
        return promise.await();
      } finally {
        if (progress) {
          progress.reportProgressDone();
        }
      }
    } else {
      return req;
    }
  },

  // Adds progress callbacks to a request
  // Based on request-progress
  _addProgressEvents: function (request) {
    var state;

    var emitProgress = function () {
      request.emit('progress', state);
    };

    request
      .on('response', function (response) {
        state = {};
        state.end = undefined;
        state.done = false;
        state.current = 0;
        var contentLength = response.headers['content-length'];
        if (contentLength) {
          state.end = Number(contentLength);
        }
        emitProgress();
      })
      .on('data', function (data) {
        state.current += data.length;
        emitProgress();
      })
      .on('end', function (data) {
        state.done = true;
        emitProgress();
      });
  },

  // A synchronous wrapper around request(...) that returns the response "body"
  // or throws.
  //
  // (This has gone through a few refactors and it might be possible
  // to fully roll it into httpHelpers.request() at this point.)
  getUrl: function (urlOrOptions) {
    try {
      var result = httpHelpers.request(urlOrOptions);
    } catch (e) {
      throw new files.OfflineError(e);
    }

    const response = result.response;
    const body = result.body;
    const href = response.request.href;

    if (response.statusCode >= 400 && response.statusCode < 600) {
      throw Error(
        body ||
          `Could not get ${href}; server returned [${response.statusCode}]`);
    } else {
      return body;
    }
  },

  getUrlWithResuming(urlOrOptions) {
    const options = _.isObject(urlOrOptions) ? _.clone(urlOrOptions) : {
      url: urlOrOptions,
    };

    const outputStream = new WritableStreamBuffer();

    const MAX_ATTEMPTS = 10;
    const RETRY_DELAY_SECS = 5;
    const masterProgress = options.progress;

    let lastSize = 0;
    function attempt(triesRemaining) {
      if (lastSize > 0) {
        options.headers = {
          ...options.headers,
          Range: `bytes=${outputStream.size()}-`
        };
      }

      if (masterProgress) {
        options.progress = masterProgress.addChildTask({
          title: masterProgress._title
        });
      }

      try {
        return httpHelpers.request({
          outputStream,
          ...options,
        });
      } catch (e) {
        const size = outputStream.size();
        const useTry = size === lastSize;
        const change = size - lastSize;
        lastSize = outputStream.size();

        if (!useTry || triesRemaining > 0) {
          if (useTry) {
            Console.debug(`Request failed, ${triesRemaining - 1} attempts left`);
          } else {
            Console.debug(`Request failed after ${change} bytes, retrying`);
          }

          return new Promise(resolve => {
            setTimeout(fiberHelpers.bindEnvironment(() => {
              resolve(attempt(useTry ? triesRemaining - 1 : triesRemaining));
            }, RETRY_DELAY_SECS * 1000));
          }).await();
        } else {
          Console.debug(`Request failed ${MAX_ATTEMPTS} times: failing`);
          throw new files.OfflineError(e);
        }
      }
    }

    const response = attempt(MAX_ATTEMPTS).response;
    if (response.statusCode >= 400 && response.statusCode < 600) {
      const href = response.request.href;
      throw Error(`Could not get ${href}; server returned [${response.statusCode}]`);
    } else {
      return outputStream.getContents();
    }
  }
});
<|endoftext|>


<commit_before>define([ './team', './toast', './strings', './tab_teams', './swiss',
    './tab_ranking', './history', './tab_history', './storage', './options' ], function (Team, Toast, Strings, Tab_Teams, Swiss, Tab_Ranking, History, Tab_History, Storage, Options) {
  var Tab_Games, $tab, $stages, template, games, $games;

  // references to html elements of the games
  $games = [];
  // local copy of the running games
  games = [];

  Tab_Games = {};

  function isInt (n) {
    return n % 1 === 0;
  }

  function initStages () {
    $stages = [];

    $stages[stage.PREPARING] = $tab.find('.preparing');
    $stages[stage.RUNNING] = $tab.find('.running');
    $stages[stage.FINISHED] = $tab.find('.finished');
  }

  function stage (stage) {
    if (stage >= $stages.length) {
      return undefined;
    }

    if (stage === 0) {
      Tab_Teams.active(true);
    } else {
      Tab_Teams.active(false);
    }

    $stages.forEach(function ($stage) {
      $stage.hide();
    });

    $stages[stage].show();
  }

  stage.PREPARING = 0;
  stage.RUNNING = 1;
  stage.FINISHED = 2;

  /**
   * a round has started, so we init some stuff. called after Swiss.start() and
   * Swiss.newRound(), hence the separete function
   */
  function startRound () {
    var votes, team;

    new Toast(Strings.roundstarted.replace("%s", Swiss.getRound()));
    showRound();
    showRunning();
    showVotes();
    // TODO use event system
    Tab_Ranking.update();

    // tell the history that there's a new round
    // TODO use event system
    Tab_History.nextRound();

    // see if there's a byevote
    // TODO extract functions into a wrapper around Swiss?
    votes = Swiss.getRoundVotes();
    if (votes.bye !== undefined) {
      team = Team.get(votes.bye);
      // remember the byevote
      History.addBye(team);
      // immediately show the byevote
      // TODO FIXME use event system
      Tab_History.createBye(team.id);
    }

    // save changes
    // TODO event system
    Storage.changed();
  }

  function initPreparations () {
    // close registration and start swiss tournament
    // TODO other tournament types and options
    $tab.find('.preparing button').click(function () {
      // TODO dynamic threshold for different tournament types (grey others out)
      if (Team.count() < 2) {
        new Toast(Strings.notenoughteams);
        return;
      }

      // register all teams at the tournament
      Team.prepareTournament(Swiss);
      new Toast(Strings.registrationclosed);

      // show game overview and hide this button
      stage(stage.RUNNING);

      // start game and notify of possible failure (i.e. too few teams)
      if (Swiss.start() === undefined) {
        new Toast(Strings.startfailed, 5);
        return undefined;
      }

      startRound();
    });
  }

  function initGameTemplate () {
    var $form, $names, $teamnos, i, tmp, $anchor;

    if (template.game) {
      console.error('tab_games: template.game is already defined:');
      console.error(template.game);
      return;
    }

    $form = $stages[stage.RUNNING].find('.game.tpl');

    $form.detach();
    $form.removeClass('tpl');

    $names = [];
    tmp = $form.find('.name');
    for (i = 0; i < Options.maxteamsize; i += 1) {
      $names[i] = tmp.eq(i);
      $names[i + Options.maxteamsize] = tmp.eq(i + Options.maxteamsize);
    }

    tmp = $form.find('.teamno');
    $teamnos = [ tmp.eq(0), tmp.eq(1) ];

    $anchor = $stages[stage.RUNNING].find('.clear').eq(0);

    /**
     * disable/enable the submit button if input is valid
     */
    $stages[stage.RUNNING].delegate('.game input', 'change', function () {
      var $button = $(this).parent().find('button');
      if (readResults($(this).parents('.game').eq(0)) === undefined) {
        $button.attr('disabled', 'disabled');
        $button.attr('tabindex', '-1');
      } else {
        $button.removeAttr('disabled');
        $button.removeAttr('tabindex');
      }
    });

    $stages[stage.RUNNING].delegate('.game', 'submit', finishGame);

    template.game = {
      $anchor : $anchor,
      $form : $form,
      $names : $names,
      $teamnos : $teamnos,
    };
  }

  function initVoteTemplate () {
    var $vote, $containers, $names, $teamno, i, tmp, $anchors, $votesarea;

    if (template.vote) {
      console.error('tab_games: template.vote is already defined:');
      console.error(template.vote);
      return;
    }

    $votesarea = $stages[stage.RUNNING];
    $vote = $votesarea.find('.votes .tpl');

    $vote.detach();
    $vote.removeClass('tpl');

    $names = [];
    tmp = $vote.find('.name');
    for (i = 0; i < Options.maxteamsize; i += 1) {
      $names[i] = tmp.eq(i);
    }

    $teamno = $vote.find('.teamno');

    $containers = {
      up : $votesarea.find('.up'),
      down : $votesarea.find('.down'),
      bye : $votesarea.find('.bye'),
    };

    $anchors = {
      up : $containers.up.find('.clear'),
      down : $containers.down.find('.clear'),
      bye : $containers.bye.find('.clear'),
    };

    template.vote = {
      $anchors : $anchors,
      $containers : $containers,
      $vote : $vote,
      $names : $names,
      $teamno : $teamno
    };
  }

  function initTemplates () {
    var $gameform, $gnames, $gteamnos, $vote, i, tmp;

    if (template) {
      console.error('tab_games: template is already defined:');
      console.error(template);
      return;
    }

    template = {};

    initGameTemplate();
    initVoteTemplate();

    updateTemplates();
  }

  function updateTemplates () {
    var i, $g1, $g2, $v;

    for (i = 0; i < Options.maxteamsize; i += 1) {
      $g1 = template.game.$names[i];
      $g2 = template.game.$names[i + Options.maxteamsize];
      $v = template.vote.$names[i];

      if (i < Options.teamsize) {
        $g1.css('display', '');
        $g1.prev('br').css('display', '');
        $g2.css('display', '');
        $g2.prev('br').css('display', '');
        $v.css('display', '');
        $v.prev('br').css('display', '');
      } else {
        $g1.css('display', 'none');
        $g1.prev('br').css('display', 'none');
        $g2.css('display', 'none');
        $g2.prev('br').css('display', 'none');
        $v.css('display', 'none');
        $v.prev('br').css('display', 'none');
      }
    }
  }

  /**
   * create and show a box displaying a certain game
   */
  function appendGame (game) {
    var t1, t2, $game, i;

    t1 = Team.get(game.teams[0][0]);
    t2 = Team.get(game.teams[1][0]);

    for (i = 0; i < Options.teamsize; i += 1) {
      template.game.$names[i].text(t1.names[i]);
      template.game.$names[i + Options.maxteamsize].text(t2.names[i]);
    }

    template.game.$teamnos[0].text(t1.id + 1);
    template.game.$teamnos[1].text(t2.id + 1);

    $game = template.game.$form.clone();
    template.game.$anchor.before($game);

    $games.push($game);
    games.push(game);
  }

  /**
   * removes all games from the overview
   */
  function clearGames () {
    $stages[stage.RUNNING].find('.game').remove();
    $games = [];
    games = [];
  }

  /**
   * clears the overview and appends all open games of the tournament
   */
  function showRunning () {
    clearGames();

    Swiss.openGames().forEach(function (game) {
      appendGame(game);
    });

    if (Swiss.getRound() !== 0 && games.length === 0) {
      stage(stage.FINISHED);
    }
  }

  /**
   * update all appearances of the current round in the games tab
   */
  function showRound () {
    $tab.find('.round').text(Swiss.getRound());
  }

  /**
   * this function removes the game from the local reference arrays
   *
   * @param game
   *          the game in question
   * @returns Tab_Games on success, undefined otherwise
   */
  function removeGame (game) {
    var index;

    index = games.indexOf(game);

    if (index === -1) {
      // something's wrong
      return undefined;
    }

    games.splice(index, 1);
    $games[index].remove();
    $games.splice(index, 1);
  }

  function readResults ($container) {
    var $input, i, ret;

    ret = {
      index : $games.indexOf($container),
      points : []
    };

    $input = template.game.$form.find('.points');

    // game is invalid. Someone tampered with the system.
    if (ret.index === -1) {
      // redraw all games
      showRunning();
      Tab_Ranking.update();
      return undefined;
    }

    // read and validate
    for (i = 0; i < 2; i += 1) {
      // read and convert to number
      ret.points[i] = Number($($input[i]).val());

      // validate whether number and >= 0
      // TODO Options.maxpoints
      if (isNaN(ret.points[i]) || !isInt(ret.points[i]) || ret.points[i] < 0 || ret.points[i] > 13) {
        // FIXME find a better solution
        // flash?
        // $input.eq(i).focus();

        return undefined;
      }
    }

    // there has to be a winner
    if (ret.points[0] === ret.points[1]) {
      return undefined;
    }

    return ret;
  }

  /**
   * jQuery callback function. works with "this"
   */
  function finishGame () {
    var result, $input, i, res, index, points;

    // if someone wants to finish a game, do the following:
    // * verify that the game was running
    // * get and verify the points
    // * submit the result
    // * remove the game
    // * drink a toast to the game

    result = readResults($(this));

    if (result === undefined) {
      new Toast(Strings.invalidresult, 5);
      return false;
    }

    // TODO extract everything to a new function!

    index = result.index;
    points = result.points;

    if (Swiss.finishGame(games[index], points) === undefined) {
      // game was somehow invalid. Someone tampered with the system.

      // redraw all games
      showRunning();

      // TODO event handler
      Tab_Ranking.update();

      // notify the user of this failure
      new Toast(Strings.invalidresult, 5);

      return false;
    }

    // the game was accepted, store it in history
    res = History.add(games[index], points);
    // TODO avoid direct calls to another tab. use Tab_History.update somehow
    Tab_History.createBox(res);

    // game was accepted. remove it.
    removeGame(games[index]);
    // XXX keep game history
    // XXX why?

    if (points[0] > points[1]) {
      new Toast(Strings.gamefinished);
    }

    // verify for safety. Doesn't cost much
    if (games.length !== $games.length) {
      // game was somehow invalid. Someone tempered with the system.

      // redraw all games
      showRunning();

      Tab_Ranking.update();

      // notify the user of this failure
      new Toast(Strings.invalidresult, 5);

      return false;
    }

    // no games left? clean up and go to stage 2.
    if (games.length === 0) {
      clearGames();
      clearVotes();

      showRound();
      stage(stage.FINISHED);

      new Toast(Strings.roundfinished.replace('%s', Swiss.getRound()));
    }

    // save changes
    Storage.changed();

    Tab_Ranking.update();

    return false;
  }

  /**
   * remove all elements in the vote area
   */
  function clearVotes () {
    $stages[stage.RUNNING].find('.votes .vote').remove();
  }

  function createVoteBox (tid) {
    var team, i;
    team = Team.get(tid);

    template.vote.$teamno.text(team.id + 1);
    for (i = 0; i < Options.teamsize; i += 1) {
      template.vote.$names[i].text(team.names[i]);
    }

    return template.vote.$vote.clone();
  }

  /**
   * display the votes for the current round
   */
  function showVotes () {
    var votes, i, $containers, $anchors;

    // remove old votes
    clearVotes();

    // get votes
    votes = Swiss.getRoundVotes();

    $containers = template.vote.$containers;
    $anchors = template.vote.$anchors;

    // apply upvotes
    if (votes.up && votes.up.length !== 0) {
      $containers.up.show();
      votes.up.forEach(function (tid) {
        if (tid !== undefined) {
          $anchors.up.before(createVoteBox(tid));
        }
      });
    } else {
      $containers.up.hide();
    }

    // apply down
    if (votes.down && votes.down.length !== 0) {
      $containers.down.show();
      votes.down.forEach(function (tid) {
        if (tid !== undefined) {
          $anchors.down.before(createVoteBox(tid));
        }
      });
    } else {
      $containers.down.hide();
    }

    // apply bye
    if (votes.bye !== undefined) {
      $containers.bye.show();
      $anchors.bye.before(createVoteBox(votes.bye));
    } else {
      $containers.bye.hide();
    }
  }

  function newRound () {
    var i;

    for (i = 0; i < 10; i += 1) {
      if (Swiss.newRound() !== undefined) {
        break;
      }
    }

    if (i === 10) {
      new Toast(Strings.roundfailed, 5);
      return undefined;
    }

    // show game overview and hide this button
    stage(stage.RUNNING);

    startRound();
  }

  function initFinished () {
    $stages[stage.FINISHED].find('button.newround').click(newRound);
    $stages[stage.FINISHED].find('button.korounds').click(function () {
      new Toast(Strings.notimplemented);
    });
  }

  function initMaxWidth () {
    var $box;

    $box = $tab.find('.options .maxwidth');

    function maxwidthtest () {
      if ($box.prop('checked')) {
        $tab.addClass('maxwidth');
      } else {
        $tab.removeClass('maxwidth');
      }
    }

    $box.click(maxwidthtest);

    maxwidthtest();
  }

  function init () {
    var i;

    if ($tab) {
      console.error('tab_games: $tab is already defined:');
      console.error($tab);
      return;
    }

    $tab = $('#games');

    initStages();
    initPreparations();
    initTemplates();
    initFinished();
    initMaxWidth();
  }

  /**
   * reset an original state.
   *
   * TODO: test
   */
  Tab_Games.reset = function () {
    if (!$tab) {
      init();
    }

    clearGames();
    clearVotes();
    stage(stage.PREPARING);
  };

  /**
   * reset an original game state, respecting the current state of Swiss
   */
  Tab_Games.update = function () {

    if (Swiss.getRound() === 0) {
      // preparing
      stage(stage.PREPARING);
    } else {
      showRunning();
      showRound();
      showVotes();

      if (games.length === 0 || $games.length === 0) {
        stage(stage.FINISHED);
      } else {
        stage(stage.RUNNING);
      }
    }
  };

  return Tab_Games;
});
<commit_msg>tab_games.js: init() conversion complete<commit_after>define([ './team', './toast', './strings', './tab_teams', './swiss',
    './tab_ranking', './history', './tab_history', './storage', './options' ], function (Team, Toast, Strings, Tab_Teams, Swiss, Tab_Ranking, History, Tab_History, Storage, Options) {
  var Tab_Games, $tab, $stages, template, games, $games;

  // references to html elements of the games
  $games = [];
  // local copy of the running games
  games = [];

  Tab_Games = {};

  function isInt (n) {
    return n % 1 === 0;
  }

  function initStages () {
    $stages = [];

    $stages[stage.PREPARING] = $tab.find('.preparing');
    $stages[stage.RUNNING] = $tab.find('.running');
    $stages[stage.FINISHED] = $tab.find('.finished');
  }

  function stage (stage) {
    if (stage >= $stages.length) {
      return undefined;
    }

    if (stage === 0) {
      Tab_Teams.active(true);
    } else {
      Tab_Teams.active(false);
    }

    $stages.forEach(function ($stage) {
      $stage.hide();
    });

    $stages[stage].show();
  }

  stage.PREPARING = 0;
  stage.RUNNING = 1;
  stage.FINISHED = 2;

  /**
   * a round has started, so we init some stuff. called after Swiss.start() and
   * Swiss.newRound(), hence the separete function
   */
  function startRound () {
    var votes, team;

    new Toast(Strings.roundstarted.replace("%s", Swiss.getRound()));
    showRound();
    showRunning();
    showVotes();
    // TODO use event system
    Tab_Ranking.update();

    // tell the history that there's a new round
    // TODO use event system
    Tab_History.nextRound();

    // see if there's a byevote
    // TODO extract functions into a wrapper around Swiss?
    votes = Swiss.getRoundVotes();
    if (votes.bye !== undefined) {
      team = Team.get(votes.bye);
      // remember the byevote
      History.addBye(team);
      // immediately show the byevote
      // TODO FIXME use event system
      Tab_History.createBye(team.id);
    }

    // save changes
    // TODO event system
    Storage.changed();
  }

  function initPreparations () {
    // close registration and start swiss tournament
    // TODO other tournament types and options
    $tab.find('.preparing button').click(function () {
      // TODO dynamic threshold for different tournament types (grey others out)
      if (Team.count() < 2) {
        new Toast(Strings.notenoughteams);
        return;
      }

      // register all teams at the tournament
      Team.prepareTournament(Swiss);
      new Toast(Strings.registrationclosed);

      // show game overview and hide this button
      stage(stage.RUNNING);

      // start game and notify of possible failure (i.e. too few teams)
      if (Swiss.start() === undefined) {
        new Toast(Strings.startfailed, 5);
        return undefined;
      }

      startRound();
    });
  }

  function initGameTemplate () {
    var $form, $names, $teamnos, i, tmp, $anchor;

    if (template.game) {
      console.error('tab_games: template.game is already defined:');
      console.error(template.game);
      return;
    }

    $form = $stages[stage.RUNNING].find('.game.tpl');

    $form.detach();
    $form.removeClass('tpl');

    $names = [];
    tmp = $form.find('.name');
    for (i = 0; i < Options.maxteamsize; i += 1) {
      $names[i] = tmp.eq(i);
      $names[i + Options.maxteamsize] = tmp.eq(i + Options.maxteamsize);
    }

    tmp = $form.find('.teamno');
    $teamnos = [ tmp.eq(0), tmp.eq(1) ];

    $anchor = $stages[stage.RUNNING].find('.clear').eq(0);

    /**
     * disable/enable the submit button if input is valid
     */
    $stages[stage.RUNNING].delegate('.game input', 'change', function () {
      var $button = $(this).parent().find('button');

      if (readResults($(this).parents('.game')) === undefined) {
        $button.attr('disabled', 'disabled');
        $button.attr('tabindex', '-1');
      } else {
        $button.removeAttr('disabled');
        $button.removeAttr('tabindex');
      }

    });

    $stages[stage.RUNNING].delegate('.game', 'submit', finishGame);

    template.game = {
      $anchor : $anchor,
      $form : $form,
      $names : $names,
      $teamnos : $teamnos,
    };
  }

  function initVoteTemplate () {
    var $vote, $containers, $names, $teamno, i, tmp, $anchors, $votesarea;

    if (template.vote) {
      console.error('tab_games: template.vote is already defined:');
      console.error(template.vote);
      return;
    }

    $votesarea = $stages[stage.RUNNING];
    $vote = $votesarea.find('.votes .tpl');

    $vote.detach();
    $vote.removeClass('tpl');

    $names = [];
    tmp = $vote.find('.name');
    for (i = 0; i < Options.maxteamsize; i += 1) {
      $names[i] = tmp.eq(i);
    }

    $teamno = $vote.find('.teamno');

    $containers = {
      up : $votesarea.find('.up'),
      down : $votesarea.find('.down'),
      bye : $votesarea.find('.bye'),
    };

    $anchors = {
      up : $containers.up.find('.clear'),
      down : $containers.down.find('.clear'),
      bye : $containers.bye.find('.clear'),
    };

    template.vote = {
      $anchors : $anchors,
      $containers : $containers,
      $vote : $vote,
      $names : $names,
      $teamno : $teamno
    };
  }

  function initTemplates () {
    var $gameform, $gnames, $gteamnos, $vote, i, tmp;

    if (template) {
      console.error('tab_games: template is already defined:');
      console.error(template);
      return;
    }

    template = {};

    initGameTemplate();
    initVoteTemplate();

    updateTemplates();
  }

  function updateTemplates () {
    var i, $g1, $g2, $v;

    for (i = 0; i < Options.maxteamsize; i += 1) {
      $g1 = template.game.$names[i];
      $g2 = template.game.$names[i + Options.maxteamsize];
      $v = template.vote.$names[i];

      if (i < Options.teamsize) {
        $g1.css('display', '');
        $g1.prev('br').css('display', '');
        $g2.css('display', '');
        $g2.prev('br').css('display', '');
        $v.css('display', '');
        $v.prev('br').css('display', '');
      } else {
        $g1.css('display', 'none');
        $g1.prev('br').css('display', 'none');
        $g2.css('display', 'none');
        $g2.prev('br').css('display', 'none');
        $v.css('display', 'none');
        $v.prev('br').css('display', 'none');
      }
    }
  }

  /**
   * create and show a box displaying a certain game
   */
  function appendGame (game) {
    var t1, t2, $game, i;

    t1 = Team.get(game.teams[0][0]);
    t2 = Team.get(game.teams[1][0]);

    for (i = 0; i < Options.teamsize; i += 1) {
      template.game.$names[i].text(t1.names[i]);
      template.game.$names[i + Options.maxteamsize].text(t2.names[i]);
    }

    template.game.$teamnos[0].text(t1.id + 1);
    template.game.$teamnos[1].text(t2.id + 1);

    $game = template.game.$form.clone();
    template.game.$anchor.before($game);

    $games.push($game);
    games.push(game);
  }

  /**
   * removes all games from the overview
   */
  function clearGames () {
    $stages[stage.RUNNING].find('.game').remove();
    $games = [];
    games = [];
  }

  /**
   * clears the overview and appends all open games of the tournament
   */
  function showRunning () {
    clearGames();

    Swiss.openGames().forEach(function (game) {
      appendGame(game);
    });

    if (Swiss.getRound() !== 0 && games.length === 0) {
      stage(stage.FINISHED);
    }
  }

  /**
   * update all appearances of the current round in the games tab
   */
  function showRound () {
    $tab.find('.round').text(Swiss.getRound());
  }

  /**
   * this function removes the game from the local reference arrays
   *
   * @param game
   *          the game in question
   * @returns Tab_Games on success, undefined otherwise
   */
  function removeGame (game) {
    var index;

    index = games.indexOf(game);

    if (index === -1) {
      // something's wrong
      return undefined;
    }

    games.splice(index, 1);
    $games[index].remove();
    $games.splice(index, 1);
  }

  function readResults ($container) {
    var $input, i, ret;

    ret = {
      index : -1,
      points : []
    };

    //
    for (i = 0; i < $games.length; i += 1) {
      if ($games[i].data() === $container.data()) {
        ret.index = i;
        break;
      }
    }

    $input = $container.find('.points');

    // game is invalid. Someone tampered with the system.
    if (ret.index === -1) {
      // redraw all games
      showRunning();
      Tab_Ranking.update();
      return undefined;
    }

    // read and validate
    for (i = 0; i < 2; i += 1) {
      // read and convert to number
      ret.points[i] = Number($($input[i]).val());

      // validate whether number and >= 0
      // TODO Options.maxpoints
      if (isNaN(ret.points[i]) || !isInt(ret.points[i]) || ret.points[i] < 0 || ret.points[i] > 13) {
        // FIXME find a better solution
        // flash?
        // $input.eq(i).focus();

        return undefined;
      }
    }

    // there has to be a winner
    if (ret.points[0] === ret.points[1]) {
      return undefined;
    }

    return ret;
  }

  /**
   * jQuery callback function. works with "this"
   */
  function finishGame () {
    var result, $input, i, res, index, points;

    // if someone wants to finish a game, do the following:
    // * verify that the game was running
    // * get and verify the points
    // * submit the result
    // * remove the game
    // * drink a toast to the game

    result = readResults($(this));

    if (result === undefined) {
      new Toast(Strings.invalidresult, 5);
      return false;
    }

    // TODO extract everything to a new function!

    index = result.index;
    points = result.points;

    if (Swiss.finishGame(games[index], points) === undefined) {
      // game was somehow invalid. Someone tampered with the system.

      // redraw all games
      showRunning();

      // TODO event handler
      Tab_Ranking.update();

      // notify the user of this failure
      new Toast(Strings.invalidresult, 5);

      return false;
    }

    // the game was accepted, store it in history
    res = History.add(games[index], points);
    // TODO avoid direct calls to another tab. use Tab_History.update somehow
    Tab_History.createBox(res);

    // game was accepted. remove it.
    removeGame(games[index]);
    // XXX keep game history
    // XXX why?

    if (points[0] > points[1]) {
      new Toast(Strings.gamefinished);
    }

    // verify for safety. Doesn't cost much
    if (games.length !== $games.length) {
      // game was somehow invalid. Someone tempered with the system.

      // redraw all games
      showRunning();

      Tab_Ranking.update();

      // notify the user of this failure
      new Toast(Strings.invalidresult, 5);

      return false;
    }

    // no games left? clean up and go to stage 2.
    if (games.length === 0) {
      clearGames();
      clearVotes();

      showRound();
      stage(stage.FINISHED);

      new Toast(Strings.roundfinished.replace('%s', Swiss.getRound()));
    }

    // save changes
    Storage.changed();

    Tab_Ranking.update();

    return false;
  }

  /**
   * remove all elements in the vote area
   */
  function clearVotes () {
    $stages[stage.RUNNING].find('.votes .vote').remove();
  }

  function createVoteBox (tid) {
    var team, i;
    team = Team.get(tid);

    template.vote.$teamno.text(team.id + 1);
    for (i = 0; i < Options.teamsize; i += 1) {
      template.vote.$names[i].text(team.names[i]);
    }

    return template.vote.$vote.clone();
  }

  /**
   * display the votes for the current round
   */
  function showVotes () {
    var votes, i, $containers, $anchors;

    // remove old votes
    clearVotes();

    // get votes
    votes = Swiss.getRoundVotes();

    $containers = template.vote.$containers;
    $anchors = template.vote.$anchors;

    // apply upvotes
    if (votes.up && votes.up.length !== 0) {
      $containers.up.show();
      votes.up.forEach(function (tid) {
        if (tid !== undefined) {
          $anchors.up.before(createVoteBox(tid));
        }
      });
    } else {
      $containers.up.hide();
    }

    // apply down
    if (votes.down && votes.down.length !== 0) {
      $containers.down.show();
      votes.down.forEach(function (tid) {
        if (tid !== undefined) {
          $anchors.down.before(createVoteBox(tid));
        }
      });
    } else {
      $containers.down.hide();
    }

    // apply bye
    if (votes.bye !== undefined) {
      $containers.bye.show();
      $anchors.bye.before(createVoteBox(votes.bye));
    } else {
      $containers.bye.hide();
    }
  }

  function newRound () {
    var i;

    for (i = 0; i < 10; i += 1) {
      if (Swiss.newRound() !== undefined) {
        break;
      }
    }

    if (i === 10) {
      new Toast(Strings.roundfailed, 5);
      return undefined;
    }

    // show game overview and hide this button
    stage(stage.RUNNING);

    startRound();
  }

  function initFinished () {
    $stages[stage.FINISHED].find('button.newround').click(newRound);
    $stages[stage.FINISHED].find('button.korounds').click(function () {
      new Toast(Strings.notimplemented);
    });
  }

  function initMaxWidth () {
    var $box;

    $box = $tab.find('.options .maxwidth');

    function maxwidthtest () {
      if ($box.prop('checked')) {
        $tab.addClass('maxwidth');
      } else {
        $tab.removeClass('maxwidth');
      }
    }

    $box.click(maxwidthtest);

    maxwidthtest();
  }

  function init () {
    var i;

    if ($tab) {
      console.error('tab_games: $tab is already defined:');
      console.error($tab);
      return;
    }

    $tab = $('#games');

    initStages();
    initPreparations();
    initTemplates();
    initFinished();
    initMaxWidth();
  }

  /**
   * reset an original state.
   *
   * TODO: test
   */
  Tab_Games.reset = function () {
    if (!$tab) {
      init();
    }

    clearGames();
    clearVotes();
    stage(stage.PREPARING);
  };

  /**
   * reset an original game state, respecting the current state of Swiss
   */
  Tab_Games.update = function () {

    if (Swiss.getRound() === 0) {
      // preparing
      stage(stage.PREPARING);
    } else {
      showRunning();
      showRound();
      showVotes();

      if (games.length === 0 || $games.length === 0) {
        stage(stage.FINISHED);
      } else {
        stage(stage.RUNNING);
      }
    }
  };

  return Tab_Games;
});
<|endoftext|>


<commit_before>/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */

import fs from 'fs';
import path from 'path';
import got from 'got';
import through2 from 'through2';

const ERROR_LIST_URL = 'https://cs.chromium.org/codesearch/f/chromium/src/net/base/net_error_list.h';
const OUTPUT_FILE = './net_error_list.h';

const outputPath = path.resolve(process.cwd(), OUTPUT_FILE);

const check = process.argv.includes('--check');

function getCurrentFile() {
  let stat;
  try {
    stat = fs.statSync(outputPath);
    if (!stat.isFile()) {
      return '';
    }
  } catch (err) {
    return '';
  }
  return fs.readFileSync(outputPath, 'utf-8');
}

const combined = (() => {
  const stream = through2((chunk, enc, callback) => {
    stream.combined += chunk;
    callback();
  });
  stream.combined = '';
  return stream;
})();

got.stream(ERROR_LIST_URL)
  .pipe(combined)
  .on('finish', () => {
    if (check) {
      if (combined.combined !== getCurrentFile()) {
        console.error(`${OUTPUT_FILE} is not up-to-date. Run 'npm run update-errors'`);
        process.exit(1);
        return;
      }
      console.log(`${OUTPUT_FILE} is up-to-date`);
      return;
    }

    fs.writeFileSync(outputPath, combined.combined, 'utf-8');
    console.log(`Errors updated: ${OUTPUT_FILE}`);
  });
<commit_msg>Fix fetching net_error_list.sh after Chromium Code Search update<commit_after>/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable no-console */

import fs from 'fs';
import path from 'path';
import got from 'got';
import through2 from 'through2';

const ERROR_LIST_URL = 'https://cs.chromium.org/codesearch/f/chromium/src/net/base/net_error_list.h';
const OUTPUT_FILE = './net_error_list.h';

const outputPath = path.resolve(process.cwd(), OUTPUT_FILE);

const check = process.argv.includes('--check');

function getCurrentFile() {
  let stat;
  try {
    stat = fs.statSync(outputPath);
    if (!stat.isFile()) {
      return '';
    }
  } catch (err) {
    return '';
  }
  return fs.readFileSync(outputPath, 'utf-8');
}

const combined = (() => {
  const stream = through2((chunk, enc, callback) => {
    stream.combined += chunk;
    callback();
  });
  stream.combined = '';
  return stream;
})();

got.stream(ERROR_LIST_URL, {
  headers: {
    'Cookie': 'REDIRECT_STATUS=optout',
  },
})
  .pipe(combined)
  .on('finish', () => {
    if (check) {
      if (combined.combined !== getCurrentFile()) {
        console.error(`${OUTPUT_FILE} is not up-to-date. Run 'npm run update-errors'`);
        process.exit(1);
        return;
      }
      console.log(`${OUTPUT_FILE} is up-to-date`);
      return;
    }

    fs.writeFileSync(outputPath, combined.combined, 'utf-8');
    console.log(`Errors updated: ${OUTPUT_FILE}`);
  });
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, View, StyleSheet } from 'react-native';

import { parsePositiveInteger } from '../utilities';

import { CircleButton } from './CircleButton';
import { AddIcon, MinusIcon } from './icons';

import { APP_FONT_FAMILY } from '../globalStyles';

/**
 * Input component which will update a numerical value. The `value` passed will be
 * cast, so should return a valid number when cast to `Number`.
 *
 * Has increment and decrement buttons which increase the passed value by one.
 * The buttons can also be held, increase by one every 50ms. The increment amount
 * then grows exponentially every 500ms, reseting to one after releasing.
 *
 * Component uses state to update itself, and a ref for storing the current value as
 * this is far more responsive than updating the redux store and re-rendering every 50ms.
 *
 * @prop {String|Number} value          The current numerical value.
 * @prop {Func}          onChangeText   Callback for updating the value.
 * @prop {Number}        lowerLimit     The lower limit of the numerical range.
 * @prop {Number}        upperLimit     The upper limit of the numerical range.
 * @prop {Object}        textInputStyle Style object for the underlying TextInput.
 * @prop {Bool}          isDisabled     Indicator if this component should be editable.
 *
 */
export const StepperInput = ({
  value,
  onChangeText,
  lowerLimit,
  upperLimit,
  textInputStyle,
  isDisabled,
}) => {
  const [, setCurrentValueState] = React.useState(Number(value));
  const currentValue = React.useRef(Number(value));
  const currentAdjustmentAmount = React.useRef(1);
  const valueAdjustmentInterval = React.useRef();
  const adjustmentIncreaseInterval = React.useRef();

  const parseNumber = number =>
    Math.max(Math.min(parsePositiveInteger(number), upperLimit), lowerLimit);

  const onUpdate = newValue => {
    const updateValue = parseNumber(newValue);
    currentValue.current = updateValue;
    setCurrentValueState(updateValue);
    onChangeText(updateValue);
  };

  const decrementValue = () => onUpdate(currentValue.current - currentAdjustmentAmount.current);
  const incrementValue = () => onUpdate(currentValue.current + currentAdjustmentAmount.current);

  const inreaseIncrement = () => {
    currentAdjustmentAmount.current *= 2;
  };

  const onStartingLongPress = isIncrement => {
    if (!valueAdjustmentInterval.current) {
      valueAdjustmentInterval.current = setInterval(
        isIncrement ? incrementValue : decrementValue,
        50
      );
    }

    if (!adjustmentIncreaseInterval.current) {
      adjustmentIncreaseInterval.current = setInterval(inreaseIncrement, 500);
    }
  };

  const onEndLongPress = () => {
    clearInterval(valueAdjustmentInterval.current);
    clearInterval(adjustmentIncreaseInterval.current);

    valueAdjustmentInterval.current = null;
    adjustmentIncreaseInterval.current = null;
    currentAdjustmentAmount.current = 1;
  };

  const onStartDecrementPress = () => onStartingLongPress(false);
  const onStartIncrementPress = () => onStartingLongPress(true);

  return (
    <View style={localStyles.row}>
      <CircleButton
        IconComponent={MinusIcon}
        onPressIn={isDisabled ? null : onStartDecrementPress}
        onPressOut={isDisabled ? null : onEndLongPress}
      />
      <TextInput
        editable={!isDisabled}
        onChangeText={onUpdate}
        value={String(currentValue.current)}
        style={textInputStyle}
      />
      <CircleButton
        IconComponent={AddIcon}
        onPressIn={isDisabled ? null : onStartIncrementPress}
        onPressOut={isDisabled ? null : onEndLongPress}
      />
    </View>
  );
};

const localStyles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
  textInput: {
    marginHorizontal: 25,
    width: 100,
    textAlign: 'center',
    fontSize: 24,

    fontFamily: APP_FONT_FAMILY,
  },
});

StepperInput.defaultProps = {
  lowerLimit: 0,
  upperLimit: 99999,
  textInputStyle: localStyles.textInput,
  isDisabled: false,
};

StepperInput.propTypes = {
  lowerLimit: PropTypes.number,
  upperLimit: PropTypes.number,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  onChangeText: PropTypes.func.isRequired,
  textInputStyle: PropTypes.object,
  isDisabled: PropTypes.bool,
};
<commit_msg>Add slight stepper update to trigger the callback at the end of a long press<commit_after>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { TextInput, View, StyleSheet } from 'react-native';

import { parsePositiveInteger } from '../utilities';

import { CircleButton } from './CircleButton';
import { AddIcon, MinusIcon } from './icons';

import { APP_FONT_FAMILY } from '../globalStyles';

/**
 * Input component which will update a numerical value. The `value` passed will be
 * cast, so should return a valid number when cast to `Number`.
 *
 * Has increment and decrement buttons which increase the passed value by one.
 * The buttons can also be held, increase by one every 50ms. The increment amount
 * then grows exponentially every 500ms, reseting to one after releasing.
 *
 * Component uses state to update itself, and a ref for storing the current value as
 * this is far more responsive than updating the redux store and re-rendering every 50ms.
 *
 * @prop {String|Number} value          The current numerical value.
 * @prop {Func}          onChangeText   Callback for updating the value.
 * @prop {Number}        lowerLimit     The lower limit of the numerical range.
 * @prop {Number}        upperLimit     The upper limit of the numerical range.
 * @prop {Object}        textInputStyle Style object for the underlying TextInput.
 * @prop {Bool}          isDisabled     Indicator if this component should be editable.
 *
 */
export const StepperInput = React.memo(
  ({ value, onChangeText, lowerLimit, upperLimit, textInputStyle, isDisabled }) => {
    const [, setCurrentValueState] = React.useState(Number(value));
    const currentValue = React.useRef(Number(value));
    const currentAdjustmentAmount = React.useRef(1);
    const valueAdjustmentInterval = React.useRef();
    const adjustmentIncreaseInterval = React.useRef();

    const parseNumber = number =>
      Math.max(Math.min(parsePositiveInteger(number), upperLimit), lowerLimit);

    const onUpdate = newValue => {
      const updateValue = parseNumber(newValue);
      currentValue.current = updateValue;
      setCurrentValueState(updateValue);
    };

    const decrementValue = () => onUpdate(currentValue.current - currentAdjustmentAmount.current);
    const incrementValue = () => onUpdate(currentValue.current + currentAdjustmentAmount.current);

    const inreaseIncrement = () => {
      currentAdjustmentAmount.current *= 2;
    };

    const onStartingLongPress = isIncrement => {
      if (isIncrement) incrementValue();
      else decrementValue();

      if (!valueAdjustmentInterval.current) {
        valueAdjustmentInterval.current = setInterval(
          isIncrement ? incrementValue : decrementValue,
          50
        );
      }

      if (!adjustmentIncreaseInterval.current) {
        adjustmentIncreaseInterval.current = setInterval(inreaseIncrement, 500);
      }
    };

    const onEndLongPress = () => {
      clearInterval(valueAdjustmentInterval.current);
      clearInterval(adjustmentIncreaseInterval.current);

      valueAdjustmentInterval.current = null;
      adjustmentIncreaseInterval.current = null;
      currentAdjustmentAmount.current = 1;

      onChangeText(currentValue.current);
    };

    const onStartDecrementPress = () => onStartingLongPress(false);
    const onStartIncrementPress = () => onStartingLongPress(true);

    return (
      <View style={localStyles.row}>
        <CircleButton
          IconComponent={MinusIcon}
          onPressIn={isDisabled ? null : onStartDecrementPress}
          onPressOut={isDisabled ? null : onEndLongPress}
        />
        <TextInput
          editable={!isDisabled}
          onChangeText={onUpdate}
          value={String(currentValue.current)}
          style={textInputStyle}
        />
        <CircleButton
          IconComponent={AddIcon}
          onPressIn={isDisabled ? null : onStartIncrementPress}
          onPressOut={isDisabled ? null : onEndLongPress}
        />
      </View>
    );
  }
);

const localStyles = StyleSheet.create({
  row: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
  textInput: {
    marginHorizontal: 25,
    width: 100,
    textAlign: 'center',
    fontSize: 24,
    fontFamily: APP_FONT_FAMILY,
  },
});

StepperInput.defaultProps = {
  lowerLimit: 0,
  upperLimit: 99999,
  textInputStyle: localStyles.textInput,
  isDisabled: false,
};

StepperInput.propTypes = {
  lowerLimit: PropTypes.number,
  upperLimit: PropTypes.number,
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  onChangeText: PropTypes.func.isRequired,
  textInputStyle: PropTypes.object,
  isDisabled: PropTypes.bool,
};
<|endoftext|>