<commit_before>/**
 * Module dependencies.
 */

var after = require('after');
var keys = require('./keys');

/**
 * Current protocol version.
 */
exports.protocol = 3;

/**
 * Packet types.
 */

var packets = exports.packets = {
    open:     0    // non-ws
  , close:    1    // non-ws
  , ping:     2
  , pong:     3
  , message:  4
  , upgrade:  5
  , noop:     6
};

var packetslist = keys(packets);

/**
 * Premade error packet.
 */

var err = { type: 'error', data: 'parser error' };

/**
 * Encodes a packet.
 *
 *     <packet type id> [ <data> ]
 *
 * Example:
 *
 *     5hello world
 *     3
 *     4
 *
 * Binary is encoded in an identical principle
 *
 * @api private
 */

exports.encodePacket = function (packet, supportsBinary, callback) {
  if (typeof supportsBinary == 'function') {
    callback = supportsBinary;
    supportsBinary = null;
  }

  var data = (packet.data === undefined)
    ? undefined
    : packet.data.buffer || packet.data;

  if (Buffer.isBuffer(data)) {
    return encodeBuffer(packet, supportsBinary, callback);
  } else if (data instanceof ArrayBuffer) {
    return encodeArrayBuffer(packet, supportsBinary, callback);
  }

  // Sending data as a utf-8 string
  var encoded = packets[packet.type];

  // data fragment is optional
  if (undefined !== packet.data) {
    encoded += String(packet.data);
  }

  return callback('' + encoded);
};

/**
 * Encode Buffer data
 */

function encodeBuffer(packet, supportsBinary, callback) {
  var data = packet.data;
  if (!supportsBinary) {
    return exports.encodeBase64Packet(packet, callback);
  }

  var typeBuffer = new Buffer(1);
  typeBuffer[0] = packets[packet.type];
  return callback(Buffer.concat([typeBuffer, data]));
}

function encodeArrayBuffer(packet, supportsBinary, callback) {
  var data = (packet.data === undefined)
    ? undefined
    : packet.data.buffer || packet.data;

  if (!supportsBinary) {
    return exports.encodeBase64Packet(packet, callback);
  }

  var contentArray = new Uint8Array(data);
  var resultBuffer = new Buffer(1 + data.byteLength);

  resultBuffer[0] = packets[packet.type];
  for (var i = 0; i < contentArray.length; i++){
    resultBuffer[i+1] = contentArray[i];
  }
  return callback(resultBuffer);
}

/**
 * Encodes a packet with binary data in a base64 string
 *
 * @param {Object} packet, has `type` and `data`
 * @return {String} base64 encoded message
 */

exports.encodeBase64Packet = function(packet, callback){
  var data = packet.data.buffer || packet.data;
  if (data instanceof ArrayBuffer) {
    var buf = new Buffer(data.byteLength);
    for (var i = 0; i < buf.length; i++) {
      buf[i] = data[i];
    }
    packet.data = buf;
  }

  var message = 'b' + packets[packet.type];
  message += packet.data.toString('base64');
  return callback(message);
};

/**
 * Decodes a packet. Data also available as an ArrayBuffer if requested.
 *
 * @return {Object} with `type` and `data` (if any)
 * @api private
 */

exports.decodePacket = function (data, binaryType) {
  // String data
  if (typeof data == 'string' || data === undefined) {
    if (data.charAt(0) == 'b') {
      return exports.decodeBase64Packet(data.substr(1), binaryType);
    }

    var type = data.charAt(0);

    if (Number(type) != type || !packetslist[type]) {
      return err;
    }

    if (data.length > 1) {
      return { type: packetslist[type], data: data.substring(1) };
    } else {
      return { type: packetslist[type] };
    }
  }

  // Binary data
  if (binaryType === 'arraybuffer') {
    var type = data[0];
    var intArray = new Uint8Array(data.length - 1);
    for (var i = 1; i < data.length; i++) {
      intArray[i - 1] = data[i];
    }
    return { type: packetslist[type], data: intArray.buffer };
  }
  var type = data[0];
  return { type: packetslist[type], data: data.slice(1) };
};

/**
 * Decodes a packet encoded in a base64 string.
 *
 * @param {String} base64 encoded message
 * @return {Object} with `type` and `data` (if any)
 */

exports.decodeBase64Packet = function(msg, binaryType) {
  var type = packetslist[msg.charAt(0)];
  var data = new Buffer(msg.substr(1), 'base64');
  if (binaryType === 'arraybuffer') {
    var abv = new Uint8Array(data.length);
    for (var i = 0; i < abv.length; i++){
      abv[i] = data[i];
    }
    data = abv.buffer;
  }
  return { type: type, data: data };
};

/**
 * Encodes multiple messages (payload).
 *
 *     <length>:data
 *
 * Example:
 *
 *     11:hello world2:hi
 *
 * If any contents are binary, they will be encoded as base64 strings. Base64
 * encoded strings are marked with a b before the length specifier
 *
 * @param {Array} packets
 * @api private
 */

exports.encodePayload = function (packets, supportsBinary, callback) {
  if (typeof supportsBinary == 'function') {
    callback = supportsBinary;
    supportsBinary = null;
  }

  if (supportsBinary) {
    return exports.encodePayloadAsBinary(packets, callback);
  }

  if (!packets.length) {
    return callback('0:');
  }

  function setLengthHeader(message) {
    return message.length + ':' + message;
  }

  function encodeOne(packet, doneCallback) {
    exports.encodePacket(packet, supportsBinary, function(message) {
      doneCallback(null, setLengthHeader(message));
    });
  }

  map(packets, encodeOne, function(err, results) {
    return callback(results.join(''));
  });
};

/**
 * Async array map using after
 */

function map(ary, each, done) {
  var result = new Array(ary.length);
  var next = after(ary.length, done);

  var eachWithIndex = function(i, el, cb) {
    each(el, function(error, msg) {
      result[i] = msg;
      cb(error, result);
    });
  };

  for (var i = 0; i < ary.length; i++) {
    eachWithIndex(i, ary[i], next);
  }
}

/*
 * Decodes data when a payload is maybe expected. Possible binary contents are
 * decoded from their base64 representation
 *
 * @param {String} data, callback method
 * @api public
 */

exports.decodePayload = function (data, binaryType, callback) {
  if ('string' != typeof data) {
    return exports.decodePayloadAsBinary(data, binaryType, callback);
  }

  if (typeof binaryType === 'function') {
    callback = binaryType;
    binaryType = null;
  }

  var packet;
  if (data == '') {
    // parser error - ignoring payload
    return callback(err, 0, 1);
  }

  var length = ''
    , n, msg;

  for (var i = 0, l = data.length; i < l; i++) {
    var chr = data.charAt(i);

    if (':' != chr) {
      length += chr;
    } else {
      if ('' == length || (length != (n = Number(length)))) {
        // parser error - ignoring payload
        return callback(err, 0, 1);
      }

      msg = data.substr(i + 1, n);

      if (length != msg.length) {
        // parser error - ignoring payload
        return callback(err, 0, 1);
      }

      if (msg.length) {
        packet = exports.decodePacket(msg, binaryType);

        if (err.type == packet.type && err.data == packet.data) {
          // parser error in individual packet - ignoring payload
          return callback(err, 0, 1);
        }

        var ret = callback(packet, i + n, l);
        if (false === ret) return;
      }

      // advance cursor
      i += n;
      length = '';
    }
  }

  if (length != '') {
    // parser error - ignoring payload
    return callback(err, 0, 1);
  }

};

/**
 * Encodes multiple messages (payload) as binary.
 *
 * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
 * 255><data>
 *
 * Example:
 * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
 *
 * @param {Array} packets
 * @return {Buffer} encoded payload
 * @api private
 */

exports.encodePayloadAsBinary = function (packets, callback) {
  if (!packets.length) {
    return callback(new Buffer(0));
  }

  function encodeOne(p, doneCallback) {
    exports.encodePacket(p, function(packet) {
      var encodingLength = '' + packet.length;
      var sizeBuffer = new Buffer(encodingLength.length + 2);

      if (typeof packet === 'string') {
        sizeBuffer[0] = 0; // is a string (not true binary = 0)
        for (var i = 0; i < encodingLength.length; i++) {
          sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
        }
        sizeBuffer[sizeBuffer.length - 1] = 255;
        return doneCallback(null, Buffer.concat([sizeBuffer, new Buffer(packet, 'utf8')]));
      }

      sizeBuffer[0] = 1; // is binary (true binary = 1)
      for (var i = 0; i < encodingLength.length; i++) {
        sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
      }
      sizeBuffer[sizeBuffer.length - 1] = 255;
      doneCallback(null, Buffer.concat([sizeBuffer, packet]));
    });
  }

  map(packets, encodeOne, function(err, results) {
    return callback(Buffer.concat(results));
  });
};

/*
 * Decodes data when a payload is maybe expected. Strings are decoded by
 * interpreting each byte as a key code for entries marked to start with 0. See
 * description of encodePayloadAsBinary

 * @param {Buffer} data, callback method
 * @api public
 */

exports.decodePayloadAsBinary = function (data, binaryType, callback) {
  if (typeof binaryType === 'function') {
    callback = binaryType;
    binaryType = null;
  }

  var bufferTail = data;
  var buffers = [];

  while (bufferTail.length > 0) {
    var strLen = '';
    var isString = bufferTail[0] === 0;
    for (var i = 1; ; i++) {
      if (bufferTail[i] == 255)  break;
      strLen += '' + bufferTail[i];
    }
    bufferTail = bufferTail.slice(strLen.length + 1);

    var msgLength = parseInt(strLen, 10);

    var msg = bufferTail.slice(1, msgLength + 1);
    if (isString) msg = msg.toString('utf8');
    buffers.push(msg);
    bufferTail = bufferTail.slice(msgLength + 1);
  }

  var total = buffers.length;
  buffers.forEach(function(buffer, i) {
    callback(exports.decodePacket(buffer, binaryType), i, total);
  });
};
<commit_msg>Fix length calculation when encoding as binary<commit_after>/**
 * Module dependencies.
 */

var after = require('after');
var keys = require('./keys');

/**
 * Current protocol version.
 */
exports.protocol = 3;

/**
 * Packet types.
 */

var packets = exports.packets = {
    open:     0    // non-ws
  , close:    1    // non-ws
  , ping:     2
  , pong:     3
  , message:  4
  , upgrade:  5
  , noop:     6
};

var packetslist = keys(packets);

/**
 * Premade error packet.
 */

var err = { type: 'error', data: 'parser error' };

/**
 * Encodes a packet.
 *
 *     <packet type id> [ <data> ]
 *
 * Example:
 *
 *     5hello world
 *     3
 *     4
 *
 * Binary is encoded in an identical principle
 *
 * @api private
 */

exports.encodePacket = function (packet, supportsBinary, callback) {
  if (typeof supportsBinary == 'function') {
    callback = supportsBinary;
    supportsBinary = null;
  }

  var data = (packet.data === undefined)
    ? undefined
    : packet.data.buffer || packet.data;

  if (Buffer.isBuffer(data)) {
    return encodeBuffer(packet, supportsBinary, callback);
  } else if (data instanceof ArrayBuffer) {
    return encodeArrayBuffer(packet, supportsBinary, callback);
  }

  // Sending data as a utf-8 string
  var encoded = packets[packet.type];

  // data fragment is optional
  if (undefined !== packet.data) {
    encoded += String(packet.data);
  }

  return callback('' + encoded);
};

/**
 * Encode Buffer data
 */

function encodeBuffer(packet, supportsBinary, callback) {
  var data = packet.data;
  if (!supportsBinary) {
    return exports.encodeBase64Packet(packet, callback);
  }

  var typeBuffer = new Buffer(1);
  typeBuffer[0] = packets[packet.type];
  return callback(Buffer.concat([typeBuffer, data]));
}

function encodeArrayBuffer(packet, supportsBinary, callback) {
  var data = (packet.data === undefined)
    ? undefined
    : packet.data.buffer || packet.data;

  if (!supportsBinary) {
    return exports.encodeBase64Packet(packet, callback);
  }

  var contentArray = new Uint8Array(data);
  var resultBuffer = new Buffer(1 + data.byteLength);

  resultBuffer[0] = packets[packet.type];
  for (var i = 0; i < contentArray.length; i++){
    resultBuffer[i+1] = contentArray[i];
  }
  return callback(resultBuffer);
}

/**
 * Encodes a packet with binary data in a base64 string
 *
 * @param {Object} packet, has `type` and `data`
 * @return {String} base64 encoded message
 */

exports.encodeBase64Packet = function(packet, callback){
  var data = packet.data.buffer || packet.data;
  if (data instanceof ArrayBuffer) {
    var buf = new Buffer(data.byteLength);
    for (var i = 0; i < buf.length; i++) {
      buf[i] = data[i];
    }
    packet.data = buf;
  }

  var message = 'b' + packets[packet.type];
  message += packet.data.toString('base64');
  return callback(message);
};

/**
 * Decodes a packet. Data also available as an ArrayBuffer if requested.
 *
 * @return {Object} with `type` and `data` (if any)
 * @api private
 */

exports.decodePacket = function (data, binaryType) {
  // String data
  if (typeof data == 'string' || data === undefined) {
    if (data.charAt(0) == 'b') {
      return exports.decodeBase64Packet(data.substr(1), binaryType);
    }

    var type = data.charAt(0);

    if (Number(type) != type || !packetslist[type]) {
      return err;
    }

    if (data.length > 1) {
      return { type: packetslist[type], data: data.substring(1) };
    } else {
      return { type: packetslist[type] };
    }
  }

  // Binary data
  if (binaryType === 'arraybuffer') {
    var type = data[0];
    var intArray = new Uint8Array(data.length - 1);
    for (var i = 1; i < data.length; i++) {
      intArray[i - 1] = data[i];
    }
    return { type: packetslist[type], data: intArray.buffer };
  }
  var type = data[0];
  return { type: packetslist[type], data: data.slice(1) };
};

/**
 * Decodes a packet encoded in a base64 string.
 *
 * @param {String} base64 encoded message
 * @return {Object} with `type` and `data` (if any)
 */

exports.decodeBase64Packet = function(msg, binaryType) {
  var type = packetslist[msg.charAt(0)];
  var data = new Buffer(msg.substr(1), 'base64');
  if (binaryType === 'arraybuffer') {
    var abv = new Uint8Array(data.length);
    for (var i = 0; i < abv.length; i++){
      abv[i] = data[i];
    }
    data = abv.buffer;
  }
  return { type: type, data: data };
};

/**
 * Encodes multiple messages (payload).
 *
 *     <length>:data
 *
 * Example:
 *
 *     11:hello world2:hi
 *
 * If any contents are binary, they will be encoded as base64 strings. Base64
 * encoded strings are marked with a b before the length specifier
 *
 * @param {Array} packets
 * @api private
 */

exports.encodePayload = function (packets, supportsBinary, callback) {
  if (typeof supportsBinary == 'function') {
    callback = supportsBinary;
    supportsBinary = null;
  }

  if (supportsBinary) {
    return exports.encodePayloadAsBinary(packets, callback);
  }

  if (!packets.length) {
    return callback('0:');
  }

  function setLengthHeader(message) {
    return message.length + ':' + message;
  }

  function encodeOne(packet, doneCallback) {
    exports.encodePacket(packet, supportsBinary, function(message) {
      doneCallback(null, setLengthHeader(message));
    });
  }

  map(packets, encodeOne, function(err, results) {
    return callback(results.join(''));
  });
};

/**
 * Async array map using after
 */

function map(ary, each, done) {
  var result = new Array(ary.length);
  var next = after(ary.length, done);

  var eachWithIndex = function(i, el, cb) {
    each(el, function(error, msg) {
      result[i] = msg;
      cb(error, result);
    });
  };

  for (var i = 0; i < ary.length; i++) {
    eachWithIndex(i, ary[i], next);
  }
}

/*
 * Decodes data when a payload is maybe expected. Possible binary contents are
 * decoded from their base64 representation
 *
 * @param {String} data, callback method
 * @api public
 */

exports.decodePayload = function (data, binaryType, callback) {
  if ('string' != typeof data) {
    return exports.decodePayloadAsBinary(data, binaryType, callback);
  }

  if (typeof binaryType === 'function') {
    callback = binaryType;
    binaryType = null;
  }

  var packet;
  if (data == '') {
    // parser error - ignoring payload
    return callback(err, 0, 1);
  }

  var length = ''
    , n, msg;

  for (var i = 0, l = data.length; i < l; i++) {
    var chr = data.charAt(i);

    if (':' != chr) {
      length += chr;
    } else {
      if ('' == length || (length != (n = Number(length)))) {
        // parser error - ignoring payload
        return callback(err, 0, 1);
      }

      msg = data.substr(i + 1, n);

      if (length != msg.length) {
        // parser error - ignoring payload
        return callback(err, 0, 1);
      }

      if (msg.length) {
        packet = exports.decodePacket(msg, binaryType);

        if (err.type == packet.type && err.data == packet.data) {
          // parser error in individual packet - ignoring payload
          return callback(err, 0, 1);
        }

        var ret = callback(packet, i + n, l);
        if (false === ret) return;
      }

      // advance cursor
      i += n;
      length = '';
    }
  }

  if (length != '') {
    // parser error - ignoring payload
    return callback(err, 0, 1);
  }

};

/**
 * Encodes multiple messages (payload) as binary.
 *
 * <1 = binary, 0 = string><number from 0-9><number from 0-9>[...]<number
 * 255><data>
 *
 * Example:
 * 1 3 255 1 2 3, if the binary contents are interpreted as 8 bit integers
 *
 * @param {Array} packets
 * @return {Buffer} encoded payload
 * @api private
 */

exports.encodePayloadAsBinary = function (packets, callback) {
  if (!packets.length) {
    return callback(new Buffer(0));
  }

  function encodeOne(p, doneCallback) {
    exports.encodePacket(p, function(packet) {

      if (typeof packet === 'string') {
        var encodingLength = '' + Buffer.byteLength(packet, "utf8");
        var sizeBuffer = new Buffer(encodingLength.length + 2);
        sizeBuffer[0] = 0; // is a string (not true binary = 0)
        for (var i = 0; i < encodingLength.length; i++) {
          sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
        }
        sizeBuffer[sizeBuffer.length - 1] = 255;
        return doneCallback(null, Buffer.concat([sizeBuffer, new Buffer(packet, 'utf8')]));
      }

      var encodingLength = '' + packet.length;
      var sizeBuffer = new Buffer(encodingLength.length + 2);
      sizeBuffer[0] = 1; // is binary (true binary = 1)
      for (var i = 0; i < encodingLength.length; i++) {
        sizeBuffer[i + 1] = parseInt(encodingLength[i], 10);
      }
      sizeBuffer[sizeBuffer.length - 1] = 255;
      doneCallback(null, Buffer.concat([sizeBuffer, packet]));
    });
  }

  map(packets, encodeOne, function(err, results) {
    return callback(Buffer.concat(results));
  });
};

/*
 * Decodes data when a payload is maybe expected. Strings are decoded by
 * interpreting each byte as a key code for entries marked to start with 0. See
 * description of encodePayloadAsBinary

 * @param {Buffer} data, callback method
 * @api public
 */

exports.decodePayloadAsBinary = function (data, binaryType, callback) {
  if (typeof binaryType === 'function') {
    callback = binaryType;
    binaryType = null;
  }

  var bufferTail = data;
  var buffers = [];

  while (bufferTail.length > 0) {
    var strLen = '';
    var isString = bufferTail[0] === 0;
    for (var i = 1; ; i++) {
      if (bufferTail[i] == 255)  break;
      strLen += '' + bufferTail[i];
    }
    bufferTail = bufferTail.slice(strLen.length + 1);

    var msgLength = parseInt(strLen, 10);

    var msg = bufferTail.slice(1, msgLength + 1);
    if (isString) msg = msg.toString('utf8');
    buffers.push(msg);
    bufferTail = bufferTail.slice(msgLength + 1);
  }

  var total = buffers.length;
  buffers.forEach(function(buffer, i) {
    callback(exports.decodePacket(buffer, binaryType), i, total);
  });
};
<|endoftext|>


<commit_before>'use strict'
require('./style.less')
var Element = require('element')
var Observable = require('vjs/lib/observable')

Element.prototype.inject(
  require('element/lib/property/text'),
  require('element/lib/property/transform'),
  require('element/lib/property/css'),
  require('element/lib/property/attributes'),
  require('element/lib/events/render')
)

exports.Header = {}

for (var i = 1; i <= 6; i += 1) {
  exports.Header[i] = new Element({
    node: 'h' + i,
    css: 'ui-header'
  }).Constructor
}

exports.Input = new Element({
  label: { text: {} },
  css: 'ui-input',
  input: {
    node: 'textarea',
    on: {
      keyup: function () {
        this.text.origin.val = this.node.value
      }
    }
  },
  on: {
    parent: {
      label () {
        this.label.text.val = this.key
      }
    }
  }
}).Constructor

exports.Topbar = new Element({
  css: 'topbar'
}).Constructor

exports.Button = new Element({
  css: 'ui-button',
  text: 'this is a button'
}).Constructor

exports.Label = new Element({
  node: 'span',
  css: 'ui-label',
  text: 'this is a label'
}).Constructor

exports.Dl = new Element({
  node: 'dl',
  css: 'ui-dl',
  term: {
    node: 'dt'
  },
  def: {
    node: 'dd',
    text: 'boom!'
  }
}).Constructor

exports.Badge = new Element({
  css: 'ui-badge',
  label: {},
  message: {}
}).Constructor

exports.InputBadge = new Element({
  css: 'ui-badge input',
  label: {},
  message: {
    css: 'message animation',
    node: 'textarea',
    attributes: {
      wrap: 'off'
    },
    text: {
      on: {
        data: {
          resize (data) {
            // use property on observable timer (store on obs remove on remove)
            var node = this.parent.node
            node.style.width = '0px'
            var sw = node.scrollWidth
            node.style.width = (sw) + 'px'
            if (data && sw < 10) {
              setTimeout(() => {
                this._on.data.fn.resize.call(this)
              }, 100)
            }
          }
        }
      }
    },
    on: {
      keyup () {
        this.text.origin.val = this.node.value
      }
    }
  }
}).Constructor

exports.Stat = new Element({
  properties: {
    status: new Observable({
      on: {
        data () {
          this.parent.oval.inner.set({
            css: 'inner ' + this.val
          })
        }
      }
    })
  },
  css: 'ui-stat',
  counter: {},
  title: {
    text: 'Counter'
  },
  oval: {
    inner: {}
  }
}).Constructor

// this.title.text.val = this.key
<commit_msg>badges get key as a default label<commit_after>'use strict'
require('./style.less')
var Element = require('element')
var Observable = require('vjs/lib/observable')

Element.prototype.inject(
  require('element/lib/property/text'),
  require('element/lib/property/transform'),
  require('element/lib/property/css'),
  require('element/lib/property/attributes'),
  require('element/lib/events/render')
)

exports.Header = {}

for (var i = 1; i <= 6; i += 1) {
  exports.Header[i] = new Element({
    node: 'h' + i,
    css: 'ui-header'
  }).Constructor
}

exports.Input = new Element({
  label: { text: {} },
  css: 'ui-input',
  input: {
    node: 'textarea',
    on: {
      keyup: function () {
        this.text.origin.val = this.node.value
      }
    }
  },
  on: {
    parent: {
      label () {
        this.label.text.val = this.key
      }
    }
  }
}).Constructor

exports.Topbar = new Element({
  css: 'topbar'
}).Constructor

exports.Button = new Element({
  css: 'ui-button',
  text: 'this is a button'
}).Constructor

exports.Label = new Element({
  node: 'span',
  css: 'ui-label',
  text: 'this is a label'
}).Constructor

exports.Dl = new Element({
  node: 'dl',
  css: 'ui-dl',
  term: {
    node: 'dt'
  },
  def: {
    node: 'dd',
    text: 'boom!'
  }
}).Constructor

function setLabel () {
  if (!this.label.text) {
    this.label.set({
      text: this.key
    })
  }
}

exports.Badge = new Element({
  css: 'ui-badge',
  on: {
    parent: {
      label: setLabel
    }
  },
  label: {},
  message: {}
}).Constructor

exports.InputBadge = new Element({
  css: 'ui-badge input',
  on: {
    parent: {
      label: setLabel
    }
  },
  label: {},
  message: {
    css: 'message animation',
    node: 'textarea',
    attributes: {
      wrap: 'off'
    },
    text: {
      on: {
        data: {
          resize (data) {
            // use property on observable timer (store on obs remove on remove)
            var node = this.parent.node
            node.style.width = '0px'
            var sw = node.scrollWidth
            node.style.width = (sw) + 'px'
            if (data && sw < 10) {
              setTimeout(() => {
                this._on.data.fn.resize.call(this)
              }, 100)
            }
          }
        }
      }
    },
    on: {
      keyup () {
        this.text.origin.val = this.node.value
      }
    }
  }
}).Constructor

exports.Stat = new Element({
  properties: {
    status: new Observable({
      on: {
        data () {
          this.parent.oval.inner.set({
            css: 'inner ' + this.val
          })
        }
      }
    })
  },
  css: 'ui-stat',
  counter: {},
  title: {
    text: 'Counter'
  },
  oval: {
    inner: {}
  }
}).Constructor

// this.title.text.val = this.key
<|endoftext|>


<commit_before>// JavaScript Document

/*
 * FileSender www.filesender.org
 *
 * Copyright (c) 2009-2012, AARNet, Belnet, HEAnet, SURFnet, UNINETT
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *	Redistributions of source code must retain the above copyright
 * 	notice, this list of conditions and the following disclaimer.
 * *	Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * *	Neither the name of AARNet, Belnet, HEAnet, SURFnet and UNINETT nor the
 * 	names of its contributors may be used to endorse or promote products
 * 	derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

$(function() {
    var section = $('#page.admin_page .transfers_section');
    if(!section.length) return;

    var idbutton   = section.find('.search [name="idbutton"]').prop('disabled', true);
    var idmin      = section.find('.search [name="idmin"]');
    var idmax      = section.find('.search [name="idmax"]');

    var idbuttonse  = section.find('.search [name="idbuttonse"]').prop('disabled', false);
    var senderemail = section.find('.search [name="senderemail"]');
    var senderemail_full_match = section.find('.search [name="senderemail_full_match"]');

    var idsearch = function() {
        //alert(idmin.val());
        filesender.ui.redirect( filesender.config.base_path
                                + '?s=admin&as=transfers'
                                + '&idmin=' + idmin.val()
                                + '&idmax=' + idmax.val()
                              );

    };

    var ideval = function() {
        idbutton.prop('disabled',
                      idmin.val().length < 1
                      || idmax.val().length < 1
                      || Number(idmax.val()) < Number(idmin.val()));
    };

    idmin.on('change, input', function() {
        ideval();
    }).on('keyup', function(e) {
        if(e.keyCode === 13 && $(this).val().length >= 3)
            idsearch();
    });
    idmax.on('change, input', function() {
        ideval();
    }).on('keyup', function(e) {
        if(e.keyCode === 13 && $(this).val().length >= 3)
            idsearch();
    });

    idbutton.on('click',   function() { idsearch(); });

    var search_senderemail = function() {
        var full_match = senderemail_full_match.is(':checked');

        filesender.ui.redirect( filesender.config.base_path
                                + '?s=admin&as=transfers'
                                + '&senderemail=' + senderemail.val()
                                + '&senderemail_full_match=' + full_match
                              );
    }

    idbuttonse.on('click', function() { search_senderemail(); } );
    senderemail.keypress(function (e) {
        if (e.which == 13) {
            search_senderemail();
            return false;
        }
    });
    ideval();
});
<commit_msg>url-encode email address in admin_transfers (#1196)<commit_after>// JavaScript Document

/*
 * FileSender www.filesender.org
 *
 * Copyright (c) 2009-2012, AARNet, Belnet, HEAnet, SURFnet, UNINETT
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *	Redistributions of source code must retain the above copyright
 * 	notice, this list of conditions and the following disclaimer.
 * *	Redistributions in binary form must reproduce the above copyright
 * 	notice, this list of conditions and the following disclaimer in the
 * 	documentation and/or other materials provided with the distribution.
 * *	Neither the name of AARNet, Belnet, HEAnet, SURFnet and UNINETT nor the
 * 	names of its contributors may be used to endorse or promote products
 * 	derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

$(function() {
    var section = $('#page.admin_page .transfers_section');
    if(!section.length) return;

    var idbutton   = section.find('.search [name="idbutton"]').prop('disabled', true);
    var idmin      = section.find('.search [name="idmin"]');
    var idmax      = section.find('.search [name="idmax"]');

    var idbuttonse  = section.find('.search [name="idbuttonse"]').prop('disabled', false);
    var senderemail = section.find('.search [name="senderemail"]');
    var senderemail_full_match = section.find('.search [name="senderemail_full_match"]');

    var idsearch = function() {
        //alert(idmin.val());
        filesender.ui.redirect( filesender.config.base_path
                                + '?s=admin&as=transfers'
                                + '&idmin=' + idmin.val()
                                + '&idmax=' + idmax.val()
                              );

    };

    var ideval = function() {
        idbutton.prop('disabled',
                      idmin.val().length < 1
                      || idmax.val().length < 1
                      || Number(idmax.val()) < Number(idmin.val()));
    };

    idmin.on('change, input', function() {
        ideval();
    }).on('keyup', function(e) {
        if(e.keyCode === 13 && $(this).val().length >= 3)
            idsearch();
    });
    idmax.on('change, input', function() {
        ideval();
    }).on('keyup', function(e) {
        if(e.keyCode === 13 && $(this).val().length >= 3)
            idsearch();
    });

    idbutton.on('click',   function() { idsearch(); });

    var search_senderemail = function() {
        var full_match = senderemail_full_match.is(':checked');

        filesender.ui.redirect( filesender.config.base_path
                                + '?s=admin&as=transfers'
                                + '&senderemail=' + encodeURIComponent(senderemail.val())
                                + '&senderemail_full_match=' + full_match
                              );
    }

    idbuttonse.on('click', function() { search_senderemail(); } );
    senderemail.keypress(function (e) {
        if (e.which == 13) {
            search_senderemail();
            return false;
        }
    });
    ideval();
});
<|endoftext|>


<commit_before>/*
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

'use strict';

// Include Gulp & tools we'll use
var gulp = require('gulp');
/*jshint ignore:start */
var $ = require('gulp-load-plugins')();
/*jshint ignore:end */
var del = require('del');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync').create();
var reload = browserSync.reload;
var merge = require('merge-stream');
var path = require('path');
var fs = require('fs');
var glob = require('glob-all');
// var historyApiFallback = require('connect-history-api-fallback');
var packageJson = require('./package.json');
var crypto = require('crypto');
// var ghPages = require('gulp-gh-pages');
// For hoodie_js task
var http = require('http');
// var connect = require('gulp-connect');
var spawn = require('child_process').spawn;
// For browserSync proxy middleware
var proxyMiddleware = require('http-proxy-middleware');

var AUTOPREFIXER_BROWSERS = [
  'ie >= 10',
  'ie_mob >= 10',
  'ff >= 30',
  'chrome >= 34',
  'safari >= 7',
  'opera >= 23',
  'ios >= 7',
  'android >= 4.4',
  'bb >= 10'
];

var DIST = 'dist';

var dist = function(subpath) {
  return !subpath ? DIST : path.join(DIST, subpath);
};

var styleTask = function(stylesPath, srcs) {
  return gulp.src(srcs.map(function(src) {
      return path.join('app', stylesPath, src);
    }))
    .pipe($.changed(stylesPath, {extension: '.css'}))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe(gulp.dest('.tmp/' + stylesPath))
    .pipe($.minifyCss())
    .pipe(gulp.dest(dist(stylesPath)))
    .pipe($.size({title: stylesPath}));
};

var imageOptimizeTask = function(src, dest) {
  return gulp.src(src)
    .pipe($.imagemin({
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest(dest))
    .pipe($.size({title: 'images'}));
};

var optimizeHtmlTask = function(src, dest) {
  var assets = $.useref.assets({
    searchPath: ['.tmp', 'app', dist()]
  });

  return gulp.src(src)
    // Replace path for vulcanized assets
    .pipe($.if('*.html', $.replace('elements/elements.html', 'elements/elements.vulcanized.html')))
    .pipe(assets)
    // Concatenate and minify JavaScript
    .pipe($.if('*.js', $.uglify({
      preserveComments: 'some'
    })))
    // Concatenate and minify styles
    // In case you are still using useref build blocks
    .pipe($.if('*.css', $.minifyCss()))
    .pipe(assets.restore())
    .pipe($.useref())
    // Minify any HTML
    .pipe($.if('*.html', $.minifyHtml({
      quotes: true,
      empty: true,
      spare: true
    })))
    // Output files
    .pipe(gulp.dest(dest))
    .pipe($.size({
      title: 'html'
    }));
};

// Compile and automatically prefix stylesheets
gulp.task('styles', function() {
  return styleTask('styles', ['**/*.css']);
});

gulp.task('elements', function() {
  return styleTask('elements', ['**/*.css']);
});

// Lint JavaScript
gulp.task('lint', function() {
  return gulp.src([
      'app/scripts/**/*.js',
      'app/elements/**/*.js',
      'app/elements/**/*.html',
      'gulpfile.js'
    ])
    .pipe(reload({
      stream: true,
      once: true
    }))

  // JSCS has not yet a extract option
  .pipe($.if('*.html', $.htmlExtract()))
  .pipe($.jshint())
  .pipe($.jscs())
  .pipe($.jscsStylish.combineWithHintResults())
  .pipe($.jshint.reporter('jshint-stylish'));
  // .pipe($.if(!browserSync.active, $.jshint.reporter('fail')));
});

// Optimize images
gulp.task('images', function() {
  return imageOptimizeTask('app/images/**/*', dist('images'));
});

// Copy all files at the root level (app)
gulp.task('copy', function() {
  var app = gulp.src([
    'app/*',
    '!app/test',
    '!app/cache-config.json'
  ], {
    dot: true
  }).pipe(gulp.dest(dist()));

  var bower = gulp.src([
    'bower_components/**/*'
  ]).pipe(gulp.dest(dist('bower_components')));

  var elements = gulp.src(['app/elements/**/*.html',
      'app/elements/**/*.css',
      'app/elements/**/*.js'
    ])
    .pipe(gulp.dest(dist('elements')));

  var swBootstrap = gulp.src(['bower_components/platinum-sw/bootstrap/*.js'])
    .pipe(gulp.dest(dist('elements/bootstrap')));

  var swToolbox = gulp.src(['bower_components/sw-toolbox/*.js'])
    .pipe(gulp.dest(dist('sw-toolbox')));

  var vulcanized = gulp.src(['app/elements/elements.html'])
    .pipe($.rename('elements.vulcanized.html'))
    .pipe(gulp.dest(dist('elements')));

  return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox)
    .pipe($.size({
      title: 'copy'
    }));
});

// Copy top-level docs to app directory to serve as in-app help files
gulp.task('docs', function () {
  return gulp.src(['docs/**'])
    .pipe(gulp.dest('app/docs'))
    .pipe($.size({title: 'docs'}));
});

// Copy web fonts to dist
gulp.task('fonts', function() {
  return gulp.src(['app/fonts/**'])
    .pipe(gulp.dest(dist('fonts')))
    .pipe($.size({
      title: 'fonts'
    }));
});

// Scan your HTML for assets & optimize them
gulp.task('html', function() {
  return optimizeHtmlTask(
    ['app/**/*.html', '!app/{elements,test}/**/*.html'],
    dist());
});

// Vulcanize granular configuration
gulp.task('vulcanize', function() {
  var DEST_DIR = dist('elements');
  return gulp.src(dist('elements/elements.vulcanized.html'))
    .pipe($.vulcanize({
      stripComments: true,
      inlineCss: true,
      inlineScripts: true
    }))
    .pipe(gulp.dest(DEST_DIR))
    .pipe($.size({title: 'vulcanize'}));
});

// Generate config data for the <sw-precache-cache> element.
// This includes a list of files that should be precached, as well as a (hopefully unique) cache
// id that ensure that multiple PSK projects don't share the same Cache Storage.
// This task does not run by default, but if you are interested in using service worker caching
// in your project, please enable it within the 'default' task.
// See https://github.com/PolymerElements/polymer-starter-kit#enable-service-worker-support
// for more context.
gulp.task('cache-config', function(callback) {
  var dir = dist();
  var config = {
    cacheId: packageJson.name || path.basename(__dirname),
    disabled: false
  };

  glob([
    'index.html',
    './',
    'bower_components/webcomponentsjs/webcomponents-lite.min.js',
    '{elements,scripts,styles}/**/*.*'],
    {cwd: dir}, function(error, files) {
    if (error) {
      callback(error);
    } else {
      config.precache = files;

      var md5 = crypto.createHash('md5');
      md5.update(JSON.stringify(config.precache));
      config.precacheFingerprint = md5.digest('hex');

      var configPath = path.join(dir, 'cache-config.json');
      fs.writeFile(configPath, JSON.stringify(config), callback);
    }
  });
});

// Clean output directory
gulp.task('clean', function() {
  return del(['.tmp', dist()]);
});

gulp.task('hoodie_start', function(done){
  // Start Hoodie
  spawn('node_modules/hoodie-server/bin/start', ['--custom-ports', '3002,3003,3004']);
  done();
});

gulp.task('hoodie_start_production', function(done){
  // Start Hoodie
  spawn('node_modules/hoodie-server/bin/start', ['--custom-ports', '19500,11504,30837']);
  done();
});

gulp.task('hoodie', ['serve', 'hoodie_start'], function(){
  /*
  Copy dynamically-generated
    /_api/_files/hoodie.js
  to
    bower_components/hoodie-service/hoodie.js
  */
  setTimeout(function(){
    var file = fs.createWriteStream('bower_components/hoodie-service/hoodie.js');
    // Note:  Use `var request = http.get(...)` if you need it
    http.get('http://localhost:3002/_api/_files/hoodie.js', function(response) {
      response.pipe(file);
    });
  }, 8000);
});

// Configure proxy for 'serve' and 'serve:dist' tasks
var proxy = proxyMiddleware('/_api', {
//   target: 'http://localhost:3002/_api'
  target: 'http://localhost:3002'
//   target: {
//     port: 3002,
//     host: 'localhost'
//   }
});

// Watch files for changes & reload
gulp.task('serve', ['hoodie_start', 'lint', 'styles', 'elements', 'images', 'docs'], function() {
  browserSync.init({
    port: 5000,
    notify: false,
    ghostMode: false,
    logPrefix: 'PSK',
    snippetOptions: {
      rule: {
        match: '',
        fn: function(snippet) {
          return snippet;
        }
      }
    },
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: {
      baseDir: ['.tmp', 'app'],
      // middleware: [historyApiFallback()],
      // Set up proxy for Hoodie's /_api
      middleware: [proxy],
      routes: {
        '/bower_components': 'bower_components'
      }
    }
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.css'], ['styles', reload]);
  gulp.watch(['app/elements/**/*.css'], ['elements', reload]);
  gulp.watch(['app/{scripts,elements}/**/{*.js,*.html}'], ['lint']);
  gulp.watch(['app/images/**/*'], reload);
  gulp.watch(['docs/**/*'], ['docs', reload]);
});

// Build and serve the output from the dist build
gulp.task('serve:dist', ['hoodie_start', 'docs', 'default'], function() {
  browserSync.init({
    port: 5001,
    notify: false,
    ghostMode: false,
    logPrefix: 'PSK',
    snippetOptions: {
      rule: {
        match: '',
        fn: function(snippet) {
          return snippet;
        }
      }
    },
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    // server: dist(), // This was the original config provided in the Polymer Starter Kit
    // middleware: [historyApiFallback()], // This was the original config provided in the Polymer Starter Kit
    server: {
      baseDir: 'dist',
      middleware: [proxy]
    }
  });
});

// Build and serve the output from the dist build
gulp.task('serve:production', ['hoodie_start_production', 'docs', 'default'], function () {

  // Configure proxy for 'serve:production' task
  var proxy_production = proxyMiddleware('/_api', {
    // target: 'http://localhost:19500/_api'
    target: {
      port: 19500,
      host: 'localhost'
    }
  });

  browserSync.init({
    port: 19500,
    notify: false,
    ghostMode: false,
    browser: false,
    logPrefix: 'PSK',
    snippetOptions: {
      rule: {
        match: '',
        fn: function (snippet) {
          return snippet;
        }
      }
    },
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    // server: dist(), // This was the original config provided in the Polymer Starter Kit
    // middleware: [historyApiFallback()], // This was the original config provided in the Polymer Starter Kit
    server: {
      baseDir: 'dist',
      middleware: [proxy_production]
    }
  });
});

// Build production files, the default task
gulp.task('default', ['clean', 'docs'], function (cb) {
  // Uncomment 'cache-config' if you are going to use service workers.
  runSequence(
    ['copy', 'styles'],
    'elements',
    ['lint', 'images', 'fonts', 'html'],
    'vulcanize', 'cache-config',
    cb);
});

// Load tasks for web-component-tester
// Adds tasks for `gulp test:local` and `gulp test:remote`
require('web-component-tester').gulp.init(gulp);

// Load custom tasks from the `tasks` directory
try {
  require('require-dir')('tasks');
} catch (err) {}
<commit_msg>Corrected custom ports<commit_after>/*
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

'use strict';

// Include Gulp & tools we'll use
var gulp = require('gulp');
/*jshint ignore:start */
var $ = require('gulp-load-plugins')();
/*jshint ignore:end */
var del = require('del');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync').create();
var reload = browserSync.reload;
var merge = require('merge-stream');
var path = require('path');
var fs = require('fs');
var glob = require('glob-all');
// var historyApiFallback = require('connect-history-api-fallback');
var packageJson = require('./package.json');
var crypto = require('crypto');
// var ghPages = require('gulp-gh-pages');
// For hoodie_js task
var http = require('http');
// var connect = require('gulp-connect');
var spawn = require('child_process').spawn;
// For browserSync proxy middleware
var proxyMiddleware = require('http-proxy-middleware');

var AUTOPREFIXER_BROWSERS = [
  'ie >= 10',
  'ie_mob >= 10',
  'ff >= 30',
  'chrome >= 34',
  'safari >= 7',
  'opera >= 23',
  'ios >= 7',
  'android >= 4.4',
  'bb >= 10'
];

var DIST = 'dist';

var dist = function(subpath) {
  return !subpath ? DIST : path.join(DIST, subpath);
};

var styleTask = function(stylesPath, srcs) {
  return gulp.src(srcs.map(function(src) {
      return path.join('app', stylesPath, src);
    }))
    .pipe($.changed(stylesPath, {extension: '.css'}))
    .pipe($.autoprefixer(AUTOPREFIXER_BROWSERS))
    .pipe(gulp.dest('.tmp/' + stylesPath))
    .pipe($.minifyCss())
    .pipe(gulp.dest(dist(stylesPath)))
    .pipe($.size({title: stylesPath}));
};

var imageOptimizeTask = function(src, dest) {
  return gulp.src(src)
    .pipe($.imagemin({
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest(dest))
    .pipe($.size({title: 'images'}));
};

var optimizeHtmlTask = function(src, dest) {
  var assets = $.useref.assets({
    searchPath: ['.tmp', 'app', dist()]
  });

  return gulp.src(src)
    // Replace path for vulcanized assets
    .pipe($.if('*.html', $.replace('elements/elements.html', 'elements/elements.vulcanized.html')))
    .pipe(assets)
    // Concatenate and minify JavaScript
    .pipe($.if('*.js', $.uglify({
      preserveComments: 'some'
    })))
    // Concatenate and minify styles
    // In case you are still using useref build blocks
    .pipe($.if('*.css', $.minifyCss()))
    .pipe(assets.restore())
    .pipe($.useref())
    // Minify any HTML
    .pipe($.if('*.html', $.minifyHtml({
      quotes: true,
      empty: true,
      spare: true
    })))
    // Output files
    .pipe(gulp.dest(dest))
    .pipe($.size({
      title: 'html'
    }));
};

// Compile and automatically prefix stylesheets
gulp.task('styles', function() {
  return styleTask('styles', ['**/*.css']);
});

gulp.task('elements', function() {
  return styleTask('elements', ['**/*.css']);
});

// Lint JavaScript
gulp.task('lint', function() {
  return gulp.src([
      'app/scripts/**/*.js',
      'app/elements/**/*.js',
      'app/elements/**/*.html',
      'gulpfile.js'
    ])
    .pipe(reload({
      stream: true,
      once: true
    }))

  // JSCS has not yet a extract option
  .pipe($.if('*.html', $.htmlExtract()))
  .pipe($.jshint())
  .pipe($.jscs())
  .pipe($.jscsStylish.combineWithHintResults())
  .pipe($.jshint.reporter('jshint-stylish'));
  // .pipe($.if(!browserSync.active, $.jshint.reporter('fail')));
});

// Optimize images
gulp.task('images', function() {
  return imageOptimizeTask('app/images/**/*', dist('images'));
});

// Copy all files at the root level (app)
gulp.task('copy', function() {
  var app = gulp.src([
    'app/*',
    '!app/test',
    '!app/cache-config.json'
  ], {
    dot: true
  }).pipe(gulp.dest(dist()));

  var bower = gulp.src([
    'bower_components/**/*'
  ]).pipe(gulp.dest(dist('bower_components')));

  var elements = gulp.src(['app/elements/**/*.html',
      'app/elements/**/*.css',
      'app/elements/**/*.js'
    ])
    .pipe(gulp.dest(dist('elements')));

  var swBootstrap = gulp.src(['bower_components/platinum-sw/bootstrap/*.js'])
    .pipe(gulp.dest(dist('elements/bootstrap')));

  var swToolbox = gulp.src(['bower_components/sw-toolbox/*.js'])
    .pipe(gulp.dest(dist('sw-toolbox')));

  var vulcanized = gulp.src(['app/elements/elements.html'])
    .pipe($.rename('elements.vulcanized.html'))
    .pipe(gulp.dest(dist('elements')));

  return merge(app, bower, elements, vulcanized, swBootstrap, swToolbox)
    .pipe($.size({
      title: 'copy'
    }));
});

// Copy top-level docs to app directory to serve as in-app help files
gulp.task('docs', function () {
  return gulp.src(['docs/**'])
    .pipe(gulp.dest('app/docs'))
    .pipe($.size({title: 'docs'}));
});

// Copy web fonts to dist
gulp.task('fonts', function() {
  return gulp.src(['app/fonts/**'])
    .pipe(gulp.dest(dist('fonts')))
    .pipe($.size({
      title: 'fonts'
    }));
});

// Scan your HTML for assets & optimize them
gulp.task('html', function() {
  return optimizeHtmlTask(
    ['app/**/*.html', '!app/{elements,test}/**/*.html'],
    dist());
});

// Vulcanize granular configuration
gulp.task('vulcanize', function() {
  var DEST_DIR = dist('elements');
  return gulp.src(dist('elements/elements.vulcanized.html'))
    .pipe($.vulcanize({
      stripComments: true,
      inlineCss: true,
      inlineScripts: true
    }))
    .pipe(gulp.dest(DEST_DIR))
    .pipe($.size({title: 'vulcanize'}));
});

// Generate config data for the <sw-precache-cache> element.
// This includes a list of files that should be precached, as well as a (hopefully unique) cache
// id that ensure that multiple PSK projects don't share the same Cache Storage.
// This task does not run by default, but if you are interested in using service worker caching
// in your project, please enable it within the 'default' task.
// See https://github.com/PolymerElements/polymer-starter-kit#enable-service-worker-support
// for more context.
gulp.task('cache-config', function(callback) {
  var dir = dist();
  var config = {
    cacheId: packageJson.name || path.basename(__dirname),
    disabled: false
  };

  glob([
    'index.html',
    './',
    'bower_components/webcomponentsjs/webcomponents-lite.min.js',
    '{elements,scripts,styles}/**/*.*'],
    {cwd: dir}, function(error, files) {
    if (error) {
      callback(error);
    } else {
      config.precache = files;

      var md5 = crypto.createHash('md5');
      md5.update(JSON.stringify(config.precache));
      config.precacheFingerprint = md5.digest('hex');

      var configPath = path.join(dir, 'cache-config.json');
      fs.writeFile(configPath, JSON.stringify(config), callback);
    }
  });
});

// Clean output directory
gulp.task('clean', function() {
  return del(['.tmp', dist()]);
});

gulp.task('hoodie_start', function(done){
  // Start Hoodie
  spawn('node_modules/hoodie-server/bin/start', ['--custom-ports', '3002,3003,3004']);
  done();
});

gulp.task('hoodie_start_production', function(done){
  // Start Hoodie
  spawn('node_modules/hoodie-server/bin/start', ['--custom-ports', '20188,19911,27069']);
  done();
});

gulp.task('hoodie', ['serve', 'hoodie_start'], function(){
  /*
  Copy dynamically-generated
    /_api/_files/hoodie.js
  to
    bower_components/hoodie-service/hoodie.js
  */
  setTimeout(function(){
    var file = fs.createWriteStream('bower_components/hoodie-service/hoodie.js');
    // Note:  Use `var request = http.get(...)` if you need it
    http.get('http://localhost:3002/_api/_files/hoodie.js', function(response) {
      response.pipe(file);
    });
  }, 8000);
});

// Configure proxy for 'serve' and 'serve:dist' tasks
var proxy = proxyMiddleware('/_api', {
//   target: 'http://localhost:3002/_api'
  target: 'http://localhost:3002'
//   target: {
//     port: 3002,
//     host: 'localhost'
//   }
});

// Watch files for changes & reload
gulp.task('serve', ['hoodie_start', 'lint', 'styles', 'elements', 'images', 'docs'], function() {
  browserSync.init({
    port: 5000,
    notify: false,
    ghostMode: false,
    logPrefix: 'PSK',
    snippetOptions: {
      rule: {
        match: '',
        fn: function(snippet) {
          return snippet;
        }
      }
    },
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    server: {
      baseDir: ['.tmp', 'app'],
      // middleware: [historyApiFallback()],
      // Set up proxy for Hoodie's /_api
      middleware: [proxy],
      routes: {
        '/bower_components': 'bower_components'
      }
    }
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.css'], ['styles', reload]);
  gulp.watch(['app/elements/**/*.css'], ['elements', reload]);
  gulp.watch(['app/{scripts,elements}/**/{*.js,*.html}'], ['lint']);
  gulp.watch(['app/images/**/*'], reload);
  gulp.watch(['docs/**/*'], ['docs', reload]);
});

// Build and serve the output from the dist build
gulp.task('serve:dist', ['hoodie_start', 'docs', 'default'], function() {
  browserSync.init({
    port: 5001,
    notify: false,
    ghostMode: false,
    logPrefix: 'PSK',
    snippetOptions: {
      rule: {
        match: '',
        fn: function(snippet) {
          return snippet;
        }
      }
    },
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    // server: dist(), // This was the original config provided in the Polymer Starter Kit
    // middleware: [historyApiFallback()], // This was the original config provided in the Polymer Starter Kit
    server: {
      baseDir: 'dist',
      middleware: [proxy]
    }
  });
});

// Build and serve the output from the dist build
gulp.task('serve:production', ['hoodie_start_production', 'docs', 'default'], function () {

  // Configure proxy for 'serve:production' task
  var proxy_production = proxyMiddleware('/_api', {
    // target: 'http://localhost:19500/_api'
    target: {
      port: 20188
      host: 'localhost'
    }
  });

  browserSync.init({
    port: 20188,
    notify: false,
    ghostMode: false,
    browser: false,
    logPrefix: 'PSK',
    snippetOptions: {
      rule: {
        match: '',
        fn: function (snippet) {
          return snippet;
        }
      }
    },
    // Run as an https by uncommenting 'https: true'
    // Note: this uses an unsigned certificate which on first access
    //       will present a certificate warning in the browser.
    // https: true,
    // server: dist(), // This was the original config provided in the Polymer Starter Kit
    // middleware: [historyApiFallback()], // This was the original config provided in the Polymer Starter Kit
    server: {
      baseDir: 'dist',
      middleware: [proxy_production]
    }
  });
});

// Build production files, the default task
gulp.task('default', ['clean', 'docs'], function (cb) {
  // Uncomment 'cache-config' if you are going to use service workers.
  runSequence(
    ['copy', 'styles'],
    'elements',
    ['lint', 'images', 'fonts', 'html'],
    'vulcanize', 'cache-config',
    cb);
});

// Load tasks for web-component-tester
// Adds tasks for `gulp test:local` and `gulp test:remote`
require('web-component-tester').gulp.init(gulp);

// Load custom tasks from the `tasks` directory
try {
  require('require-dir')('tasks');
} catch (err) {}
<|endoftext|>


<commit_before>var util = require('util');
var events = require('events');
var common = require('./common');
var strtok = require('strtok2');
var readStream = require('filereader-stream');
var through = require('through')
var fs = require('fs')

function wrapFileWithStream (file) {
  if (file instanceof FileList) {
    throw new Error('You have passed a FileList object but we expected a File');
  }
  if (!(file instanceof File || file instanceof Blob)) {
    throw new Error('You must provide a valid File or Blob object');
  }
  var stream = through(null, null, {autoDestroy: false});
  stream.fileSize = function (cb) {
    process.nextTick(function () {
      cb(file.size);
    })
  }
  return readStream(file).pipe(stream);
}

var MusicMetadata = module.exports = function (stream, opts) {
  if (!(this instanceof MusicMetadata)) return new MusicMetadata(stream, opts);
  opts = opts || {};
  var self = this;

  if (process.browser) {
    this.stream = wrapFileWithStream(stream);
  } else {
    stream.fileSize = function (cb) {
      if (stream.hasOwnProperty('path')) {
        fs.stat(stream.path, function (err, stats) {
          if (err) throw err;
          cb(stats.size);
        });
      } else if (opts.fileSize) {
        process.nextTick(function() {
          cb(opts.fileSize);
        });
      } else if (opts.duration) {
        self.emit(
          'done',
          Error('for non file streams, specify the size of the stream with a fileSize option'));
      }
    }
    this.stream = stream;
  }

  events.EventEmitter.call(this);
  this.parse(opts);
};

util.inherits(MusicMetadata, events.EventEmitter);

MusicMetadata.prototype.parse = function (opts) {
  this.metadata = {
    title: '',
    artist: [],
    albumartist: [],
    album: '',
    year: "",
    track: { no: 0, of: 0 },
    genre: [],
    disk: { no: 0, of: 0 },
    picture: [],
    duration: 0
  }

  this.aliased = {};

  var self = this;
  this.stream.once('data', function (result) {
    var parser = common.getParserForMediaType(headerTypes, result);
    parser(self.stream, self.readEvent.bind(self), done,
      opts.hasOwnProperty('duration'));
    // re-emitting the first data chunk so the
    // parser picks the stream up from the start
    self.stream.emit('data', result);
  });

  this.stream.on('close', onClose);

  function onClose () {
    done(new Error('Unexpected end of stream'));
  }

  function done (exception) {
    self.stream.removeListener('close', onClose);
    self.readEvent('done', exception);
    return strtok.DONE;
  }
};

MusicMetadata.prototype.readEvent = function (event, value) {
  // We only emit aliased events once the 'done' event has been raised,
  // this is because an alias like 'artist' could have values split
  // over many data chunks.
  if (event === 'done') {
    for (var _alias in this.aliased) {
      if (this.aliased.hasOwnProperty(_alias)) {
        var val;
        if (_alias === 'title' || _alias === 'album' ||
            _alias === 'year' || _alias === 'duration') {
          val = this.aliased[_alias][0];
        } else {
          val = this.aliased[_alias];
        }

        this.emit(_alias, val);

        if (this.metadata.hasOwnProperty(_alias)) {
          this.metadata[_alias] = val;
        }
      }
    }

    // don't emit the metadata event if nothing
    // ever gets added to the metadata object
    if (Object.keys(this.aliased).length > 0) {
      this.emit('metadata', this.metadata);
    }

    this.emit('done', value);
    return;
  }

  var alias = lookupAlias(event);

  // emit original event & value
  if (event !== alias) {
    this.emit(event, value);
  }
  if (value === null) return;

  // we need to do something special for these events
  var cleaned;
  if (event === 'TRACKTOTAL' || event === 'DISCTOTAL') {
    var evt;
    if (event === 'TRACKTOTAL') evt = 'track';
    if (event === 'DISCTOTAL') evt = 'disk';

    cleaned = parseInt(value, 10);
    if (isNaN(cleaned)) cleaned = 0;
    if (!this.aliased.hasOwnProperty(evt)) {
      this.aliased[evt] = { no: 0, of: cleaned };
    } else {
      this.aliased[evt].of = cleaned;
    }
  }

  // if the event has been aliased then we need to clean it before
  // it is emitted to the user. e.g. genre (20) -> Electronic
  if (alias) {
    cleaned = value;
    if (alias === 'genre') cleaned = common.parseGenre(value);
    if (alias === 'picture') cleaned = cleanupPicture(value);

    if (alias === 'track' || alias === 'disk') {
      cleaned = cleanupTrack(value);

      if (this.aliased[alias]) {
        this.aliased[alias].no = cleaned.no;
        return;
      } else {
        this.aliased[alias] = cleaned;
        return;
      }
    }

    // many tagging libraries use forward slashes to separate artists etc
    // within a string, this code separates those strings into an array
    if (cleaned.constructor === String) {
      // limit to these three aliases, we don't want to be splitting anything else
      if (alias === 'artist' || alias === 'albumartist' || alias === 'genre') {
        cleaned = cleaned.split('/');
        if (cleaned.length === 1) cleaned = cleaned[0];
      }
    }

    // if we haven't previously seen this tag then
    // initialize it to an array, ready for values to be entered
    if (!this.aliased.hasOwnProperty(alias)) {
      this.aliased[alias] = [];
    }

    if (cleaned.constructor === Array) {
      this.aliased[alias] = cleaned;
    } else {
      this.aliased[alias].push(cleaned);
    }
  }
}

function lookupAlias (event) {
  var alias;
  for (var i = 0; i < MAPPINGS.length; i++) {
    for (var j = 0; j < MAPPINGS[i].length; j++) {
      var cur = MAPPINGS[i][j];
      if (cur.toUpperCase() === event.toUpperCase()) {
        alias = MAPPINGS[i][0];
        break;
      }
    }
  }
  return alias;
}

function cleanupArtist (origVal) {
  return origVal.split('/');
}

// TODO: a string of 1of1 would fail to be converted
// converts 1/10 to no : 1, of : 10
// or 1 to no : 1, of : 0
function cleanupTrack (origVal) {
  var split = origVal.toString().split('/');
  var number = parseInt(split[0], 10) || 0;
  var total = parseInt(split[1], 10) || 0;
  return { no: number, of: total }
}

function cleanupPicture (picture) {
  var newFormat;
  if (picture.format) {
    var split = picture.format.toLowerCase().split('/');
    newFormat = (split.length > 1) ? split[1] : split[0];
    if (newFormat === 'jpeg') newFormat = 'jpg';
  } else {
    newFormat = 'jpg';
  }
  return { format: newFormat, data: picture.data }
}

var headerTypes = [
  {
    buf: common.asfGuidBuf,
    tag: require('./asf'),
  },
  {
    buf: new Buffer('ID3'),
    tag: require('./id3v2'),
  },
  {
    buf: new Buffer('ftypM4A'),
    tag: require('./id4'),
    offset: 4,
  },
  {
    buf: new Buffer('ftypmp42'),
    tag: require('./id4'),
    offset: 4,
  },
  {
    buf: new Buffer('OggS'),
    tag: require('./ogg'),
  },
  {
    buf: new Buffer('fLaC'),
    tag: require('./flac'),
  },
  {
    buf: new Buffer('MAC'),
    tag: require('./monkeysaudio'),
  },
];

// mappings for common metadata types(id3v2.3, id3v2.2, id4, vorbis, APEv2)
var MAPPINGS = [
  ['title', 'TIT2', 'TT2', '©nam', 'TITLE', 'Title'],
  ['artist', 'TPE1', 'TP1', '©ART', 'ARTIST', 'Author'],
  ['albumartist', 'TPE2', 'TP2', 'aART', 'ALBUMARTIST', 'ENSEMBLE', 'WM/AlbumArtist'],
  ['album', 'TALB', 'TAL', '©alb', 'ALBUM', 'WM/AlbumTitle'],
  ['year', 'TDRC', 'TYER', 'TYE', '©day', 'DATE', 'Year', 'WM/Year'],
  ['comment', 'COMM', 'COM', '©cmt', 'COMMENT'],
  ['track', 'TRCK', 'TRK', 'trkn', 'TRACKNUMBER', 'Track', 'WM/TrackNumber'],
  ['disk', 'TPOS', 'TPA', 'disk', 'DISCNUMBER', 'Disk'],
  ['genre', 'TCON', 'TCO', '©gen', 'gnre', 'GENRE', 'WM/Genre'],
  ['picture', 'APIC', 'PIC', 'covr', 'METADATA_BLOCK_PICTURE',
    'Cover Art (Front)', 'Cover Art (Back)'],
  ['composer', 'TCOM', 'TCM', '©wrt', 'COMPOSER'],
  ['duration']
];
<commit_msg>refactorings<commit_after>var util = require('util');
var events = require('events');
var common = require('./common');
var strtok = require('strtok2');
var readStream = require('filereader-stream');
var through = require('through')
var fs = require('fs')

function wrapFileWithStream (file) {
  if (file instanceof FileList) {
    throw new Error('You have passed a FileList object but we expected a File');
  }
  if (!(file instanceof File || file instanceof Blob)) {
    throw new Error('You must provide a valid File or Blob object');
  }
  var stream = through(null, null, {autoDestroy: false});
  stream.fileSize = function (cb) {
    process.nextTick(function () {
      cb(file.size);
    })
  }
  return readStream(file).pipe(stream);
}

var MusicMetadata = module.exports = function (stream, opts) {
  if (!(this instanceof MusicMetadata)) return new MusicMetadata(stream, opts);
  opts = opts || {};
  var self = this;

  if (process.browser) {
    this.stream = wrapFileWithStream(stream);
  } else {
    stream.fileSize = function (cb) {
      if (stream.hasOwnProperty('path')) {
        fs.stat(stream.path, function (err, stats) {
          if (err) throw err;
          cb(stats.size);
        });
      } else if (opts.fileSize) {
        process.nextTick(function() {
          cb(opts.fileSize);
        });
      } else if (opts.duration) {
        self.emit(
          'done',
          Error('for non file streams, specify the size of the stream with a fileSize option'));
      }
    }
    this.stream = stream;
  }

  this.metadata = {
    title: '',
    artist: [],
    albumartist: [],
    album: '',
    year: '',
    track: { no: 0, of: 0 },
    genre: [],
    disk: { no: 0, of: 0 },
    picture: [],
    duration: 0
  }

  this.aliased = {};

  this.stream.once('data', function (result) {
    var parser = common.getParserForMediaType(headerTypes, result);
    parser(self.stream, self.readEvent.bind(self), done,
      opts.hasOwnProperty('duration'));
    // re-emitting the first data chunk so the
    // parser picks the stream up from the start
    self.stream.emit('data', result);
  });

  this.stream.on('close', onClose);

  function onClose () {
    done(new Error('Unexpected end of stream'));
  }

  function done (exception) {
    self.stream.removeListener('close', onClose);
    self.readEvent('done', exception);
    return strtok.DONE;
  }

  events.EventEmitter.call(this);
}

util.inherits(MusicMetadata, events.EventEmitter);

MusicMetadata.prototype.readEvent = function (event, value) {
  // We only emit aliased events once the 'done' event has been raised,
  // this is because an alias like 'artist' could have values split
  // over many data chunks.
  if (event === 'done') {
    for (var _alias in this.aliased) {
      if (this.aliased.hasOwnProperty(_alias)) {
        var val;
        if (_alias === 'title' || _alias === 'album' ||
            _alias === 'year' || _alias === 'duration') {
          val = this.aliased[_alias][0];
        } else {
          val = this.aliased[_alias];
        }

        this.emit(_alias, val);

        if (this.metadata.hasOwnProperty(_alias)) {
          this.metadata[_alias] = val;
        }
      }
    }

    // don't emit the metadata event if nothing
    // ever gets added to the metadata object
    if (Object.keys(this.aliased).length > 0) {
      this.emit('metadata', this.metadata);
    }

    this.emit('done', value);
    return;
  }

  var alias = lookupAlias(event);

  // emit original event & value
  if (event !== alias) {
    this.emit(event, value);
  }
  if (value === null) return;

  // we need to do something special for these events
  var cleaned;
  if (event === 'TRACKTOTAL' || event === 'DISCTOTAL') {
    var evt;
    if (event === 'TRACKTOTAL') evt = 'track';
    if (event === 'DISCTOTAL') evt = 'disk';

    cleaned = parseInt(value, 10);
    if (isNaN(cleaned)) cleaned = 0;
    if (!this.aliased.hasOwnProperty(evt)) {
      this.aliased[evt] = { no: 0, of: cleaned };
    } else {
      this.aliased[evt].of = cleaned;
    }
  }

  // if the event has been aliased then we need to clean it before
  // it is emitted to the user. e.g. genre (20) -> Electronic
  if (alias) {
    cleaned = value;
    if (alias === 'genre') cleaned = common.parseGenre(value);
    if (alias === 'picture') cleaned = cleanupPicture(value);

    if (alias === 'track' || alias === 'disk') {
      cleaned = cleanupTrack(value);

      if (this.aliased[alias]) {
        this.aliased[alias].no = cleaned.no;
        return;
      } else {
        this.aliased[alias] = cleaned;
        return;
      }
    }

    // many tagging libraries use forward slashes to separate artists etc
    // within a string, this code separates those strings into an array
    if (cleaned.constructor === String) {
      // limit to these three aliases, we don't want to be splitting anything else
      if (alias === 'artist' || alias === 'albumartist' || alias === 'genre') {
        cleaned = cleaned.split('/');
        if (cleaned.length === 1) cleaned = cleaned[0];
      }
    }

    // if we haven't previously seen this tag then
    // initialize it to an array, ready for values to be entered
    if (!this.aliased.hasOwnProperty(alias)) {
      this.aliased[alias] = [];
    }

    if (cleaned.constructor === Array) {
      this.aliased[alias] = cleaned;
    } else {
      this.aliased[alias].push(cleaned);
    }
  }
}

function lookupAlias (event) {
  var alias;
  for (var i = 0; i < MAPPINGS.length; i++) {
    for (var j = 0; j < MAPPINGS[i].length; j++) {
      var cur = MAPPINGS[i][j];
      if (cur.toUpperCase() === event.toUpperCase()) {
        alias = MAPPINGS[i][0];
        break;
      }
    }
  }
  return alias;
}

function cleanupArtist (origVal) {
  return origVal.split('/');
}

// TODO: a string of 1of1 would fail to be converted
// converts 1/10 to no : 1, of : 10
// or 1 to no : 1, of : 0
function cleanupTrack (origVal) {
  var split = origVal.toString().split('/');
  var number = parseInt(split[0], 10) || 0;
  var total = parseInt(split[1], 10) || 0;
  return { no: number, of: total }
}

function cleanupPicture (picture) {
  var newFormat;
  if (picture.format) {
    var split = picture.format.toLowerCase().split('/');
    newFormat = (split.length > 1) ? split[1] : split[0];
    if (newFormat === 'jpeg') newFormat = 'jpg';
  } else {
    newFormat = 'jpg';
  }
  return { format: newFormat, data: picture.data }
}

var headerTypes = [
  {
    buf: common.asfGuidBuf,
    tag: require('./asf'),
  },
  {
    buf: new Buffer('ID3'),
    tag: require('./id3v2'),
  },
  {
    buf: new Buffer('ftypM4A'),
    tag: require('./id4'),
    offset: 4,
  },
  {
    buf: new Buffer('ftypmp42'),
    tag: require('./id4'),
    offset: 4,
  },
  {
    buf: new Buffer('OggS'),
    tag: require('./ogg'),
  },
  {
    buf: new Buffer('fLaC'),
    tag: require('./flac'),
  },
  {
    buf: new Buffer('MAC'),
    tag: require('./monkeysaudio'),
  },
];

// mappings for common metadata types(id3v2.3, id3v2.2, id4, vorbis, APEv2)
var MAPPINGS = [
  ['title', 'TIT2', 'TT2', '©nam', 'TITLE', 'Title'],
  ['artist', 'TPE1', 'TP1', '©ART', 'ARTIST', 'Author'],
  ['albumartist', 'TPE2', 'TP2', 'aART', 'ALBUMARTIST', 'ENSEMBLE', 'WM/AlbumArtist'],
  ['album', 'TALB', 'TAL', '©alb', 'ALBUM', 'WM/AlbumTitle'],
  ['year', 'TDRC', 'TYER', 'TYE', '©day', 'DATE', 'Year', 'WM/Year'],
  ['comment', 'COMM', 'COM', '©cmt', 'COMMENT'],
  ['track', 'TRCK', 'TRK', 'trkn', 'TRACKNUMBER', 'Track', 'WM/TrackNumber'],
  ['disk', 'TPOS', 'TPA', 'disk', 'DISCNUMBER', 'Disk'],
  ['genre', 'TCON', 'TCO', '©gen', 'gnre', 'GENRE', 'WM/Genre'],
  ['picture', 'APIC', 'PIC', 'covr', 'METADATA_BLOCK_PICTURE',
    'Cover Art (Front)', 'Cover Art (Back)'],
  ['composer', 'TCOM', 'TCM', '©wrt', 'COMPOSER'],
  ['duration']
];
<|endoftext|>


<commit_before>
const { Writable } = require('stream')
const { createGzip } = require('zlib')
const tempPath = require('temp-path')
const assert = require('assert')
const moment = require('moment')
const { v4 } = require('uuid')
const AWS = require('aws-sdk')
const pg = require('pg-then')
const fs = require('fs')

exports = module.exports = options => {
  return new RedshiftLoader(options)
}

exports.s3format = prefix => prefix + moment.utc().format('YYYY/MM/DD/HH/mm/') + v4()

exports.onError = err => {
  if (!err) return

  console.error(err.stack || err)
}

class RedshiftLoader extends Writable {
  constructor (options) {
    super({
      objectMode: true
    })

    this.options = options
    // which stream to write to
    this.i = 0
    // whether data has been written
    this.written = false

    this._validateOptions()
  }

  _validateOptions () {
    const { options } = this
    assert(options, 'Options are required!')

    const { s3 } = options
    assert(s3, '.s3 options are required!')

    this.s3 = new AWS.S3(Object.assign({}, s3, {
      params: {
        Bucket: s3.bucket
      }
    }))
    this.s3format = typeof s3.format === 'function'
      ? s3.format
      : exports.s3format

    this.prefix = options.prefix || ''
    if (typeof options.transform === 'function') {
      this._transform = options.transform
    }

    const files = this.files = options.files || 1
    const filenames = this.filenames = []
    const keys = this.s3keys = []
    const name = this.s3format(this.prefix)
    for (let i = 0; i < files; i++) {
      filenames.push(tempPath())
      keys.push(`${name}/${i}.json.gz`)
    }

    this.s3manifestKey = `${name}/manifest.json`

    this.streams = filenames.map(filename => {
      const gzip = createGzip()
      const stream = fs.createWriteStream(filename)
      gzip.pipe(stream)
      return {
        gzip,
        stream
      }
    })

    this.onError = options.onError || exports.onError
  }

  transform (doc) {
    const transform = this._transform
    if (!transform) return Promise.resolve(doc)
    return new Promise(resolve => resolve(transform(doc)))
  }

  // select which write stream to write to
  selectWriteStream () {
    this.i = ++this.i % this.files
    return this.streams[this.i].gzip
  }

  // TODO: backpressure
  // TODO: write in parallel?
  _write (doc, NULL, cb) {
    this.written = true
    this.transform(doc).then(doc => {
      this.selectWriteStream().write(JSON.stringify(doc) + '\n')
    }).then(cb, cb)
  }

  uploadFile (Key, Body, gzipped) {
    const Params = {
      Key,
      Body,
      ContentType: `application/json`
    }
    if (gzipped) Params.ContentEncoding = `gzip`
    return this.s3.putObject(Params).promise()
  }

  uploadFiles () {
    return Promise.all(this.filenames.map((filename, i) => {
      const Key = this.s3keys[i]
      return this.uploadFile(Key, fs.createReadStream(filename), true)
    }))
  }

  // TODO: can I upload gzipped?
  uploadManifest () {
    const { bucket } = this.options.s3
    const manifest = {
      entries: this.s3keys.map(Key => ({
        url: `s3://${bucket}/${Key}`,
        mandatory: true
      }))
    }

    return this.uploadFile(this.s3manifestKey, JSON.stringify(manifest, null, 2))
  }

  copy () {
    const { options } = this
    const { redshift, s3 } = options
    const client = pg.Client(redshift.url)
    const TABLE = redshift.table
    const TEMP = `${TABLE}_temp_${v4().replace(/-/g, `_`)}`.replace(/.*\./, '')

    const rsQuery = `
      BEGIN TRANSACTION;
      CREATE TEMP TABLE ${TEMP} (LIKE ${TABLE});
      COPY ${TEMP}
      FROM 's3://${s3.bucket}/${this.s3manifestKey}'
      CREDENTIALS 'aws_access_key_id=${s3.accessKeyId};aws_secret_access_key=${s3.secretAccessKey}'
      MANIFEST
      FORMAT AS JSON 'auto'
      GZIP
      TRUNCATECOLUMNS
      MAXERROR 100000;
      DELETE FROM ${TABLE}
      WHERE id IN (SELECT id FROM ${TEMP});
      INSERT INTO ${TABLE}
      SELECT * FROM ${TEMP};
      DROP TABLE ${TEMP};
      END TRANSACTION;
    `

    return client.query(rsQuery)
    .then(() => client.end())
    .catch(err => (
      client.query(`ROLLBACK;`).catch(this.onError).then(() => {
        client.end()
        throw err
      })
    ))
  }

  _promise () {
    return new Promise((resolve, reject) => {
      // wait until the file write streams are finished
      resolve(Promise.all(this.streams.map(({ stream }) => (
        new Promise((resolve, reject) => {
          stream.once('error', reject)
          stream.once('finish', resolve)
        })
      ))))

      this.once('error', reject)
      this.once('finish', () => {
        // once this stream ended, end all the gzip streams
        this.streams.map(({ gzip }) => gzip.end())
      })
      // end this stream
      this.end()
    })
    .then(() => {
      if (!this.written) return

      return Promise.all([
        this.uploadFiles(),
        this.uploadManifest()
      ]).then(() => this.copy())
    })
  }

  then (resolve, reject) {
    this.promise = this.promise || this._promise()
    return this.promise.then(resolve, reject)
  }
}

exports.RedshiftLoader = RedshiftLoader
<commit_msg>Maxerrors (#3)<commit_after>
const { Writable } = require('stream')
const { createGzip } = require('zlib')
const tempPath = require('temp-path')
const assert = require('assert')
const moment = require('moment')
const { v4 } = require('uuid')
const AWS = require('aws-sdk')
const pg = require('pg-then')
const fs = require('fs')

exports = module.exports = options => {
  return new RedshiftLoader(options)
}

exports.s3format = prefix => prefix + moment.utc().format('YYYY/MM/DD/HH/mm/') + v4()

exports.onError = err => {
  if (!err) return

  console.error(err.stack || err)
}

class RedshiftLoader extends Writable {
  constructor (options) {
    super({
      objectMode: true
    })

    this.options = options
    // which stream to write to
    this.i = 0
    // whether data has been written
    this.written = false

    this._validateOptions()
  }

  _validateOptions () {
    const { options } = this
    assert(options, 'Options are required!')

    const { s3 } = options
    assert(s3, '.s3 options are required!')

    this.s3 = new AWS.S3(Object.assign({}, s3, {
      params: {
        Bucket: s3.bucket
      }
    }))
    this.s3format = typeof s3.format === 'function'
      ? s3.format
      : exports.s3format

    this.prefix = options.prefix || ''
    if (typeof options.transform === 'function') {
      this._transform = options.transform
    }

    const files = this.files = options.files || 1
    const filenames = this.filenames = []
    const keys = this.s3keys = []
    const name = this.s3format(this.prefix)
    for (let i = 0; i < files; i++) {
      filenames.push(tempPath())
      keys.push(`${name}/${i}.json.gz`)
    }

    this.s3manifestKey = `${name}/manifest.json`

    this.streams = filenames.map(filename => {
      const gzip = createGzip()
      const stream = fs.createWriteStream(filename)
      gzip.pipe(stream)
      return {
        gzip,
        stream
      }
    })

    this.onError = options.onError || exports.onError
    this.maxErrors = typeof options.maxErrors === 'number'
      ? Math.round(options.maxErrors)
      : 100000
  }

  transform (doc) {
    const transform = this._transform
    if (!transform) return Promise.resolve(doc)
    return new Promise(resolve => resolve(transform(doc)))
  }

  // select which write stream to write to
  selectWriteStream () {
    this.i = ++this.i % this.files
    return this.streams[this.i].gzip
  }

  // TODO: backpressure
  // TODO: write in parallel?
  _write (doc, NULL, cb) {
    this.written = true
    this.transform(doc).then(doc => {
      this.selectWriteStream().write(JSON.stringify(doc) + '\n')
    }).then(cb, cb)
  }

  uploadFile (Key, Body, gzipped) {
    const Params = {
      Key,
      Body,
      ContentType: `application/json`
    }
    if (gzipped) Params.ContentEncoding = `gzip`
    return this.s3.putObject(Params).promise()
  }

  uploadFiles () {
    return Promise.all(this.filenames.map((filename, i) => {
      const Key = this.s3keys[i]
      return this.uploadFile(Key, fs.createReadStream(filename), true)
    }))
  }

  // TODO: can I upload gzipped?
  uploadManifest () {
    const { bucket } = this.options.s3
    const manifest = {
      entries: this.s3keys.map(Key => ({
        url: `s3://${bucket}/${Key}`,
        mandatory: true
      }))
    }

    return this.uploadFile(this.s3manifestKey, JSON.stringify(manifest, null, 2))
  }

  copy () {
    const { options } = this
    const { redshift, s3 } = options
    const client = pg.Client(redshift.url)
    const TABLE = redshift.table
    const TEMP = `${TABLE}_temp_${v4().replace(/-/g, `_`)}`.replace(/.*\./, '')

    const rsQuery = `
      BEGIN TRANSACTION;
      CREATE TEMP TABLE ${TEMP} (LIKE ${TABLE});
      COPY ${TEMP}
      FROM 's3://${s3.bucket}/${this.s3manifestKey}'
      CREDENTIALS 'aws_access_key_id=${s3.accessKeyId};aws_secret_access_key=${s3.secretAccessKey}'
      MANIFEST
      FORMAT AS JSON 'auto'
      GZIP
      TRUNCATECOLUMNS
      ${this.maxErrors ? `MAXERROR ${this.maxErrors}` : ''};
      DELETE FROM ${TABLE}
      WHERE id IN (SELECT id FROM ${TEMP});
      INSERT INTO ${TABLE}
      SELECT * FROM ${TEMP};
      DROP TABLE ${TEMP};
      END TRANSACTION;
    `

    return client.query(rsQuery)
    .then(() => client.end())
    .catch(err => (
      client.query(`ROLLBACK;`).catch(this.onError).then(() => {
        client.end()
        throw err
      })
    ))
  }

  _promise () {
    return new Promise((resolve, reject) => {
      // wait until the file write streams are finished
      resolve(Promise.all(this.streams.map(({ stream }) => (
        new Promise((resolve, reject) => {
          stream.once('error', reject)
          stream.once('finish', resolve)
        })
      ))))

      this.once('error', reject)
      this.once('finish', () => {
        // once this stream ended, end all the gzip streams
        this.streams.map(({ gzip }) => gzip.end())
      })
      // end this stream
      this.end()
    })
    .then(() => {
      if (!this.written) return

      return Promise.all([
        this.uploadFiles(),
        this.uploadManifest()
      ]).then(() => this.copy())
    })
  }

  then (resolve, reject) {
    this.promise = this.promise || this._promise()
    return this.promise.then(resolve, reject)
  }
}

exports.RedshiftLoader = RedshiftLoader
<|endoftext|>


<commit_before>/**
 * Created by Petr Marek on 21.7.2015.
 */

var BASE_URL = 'https://docs.google.com/forms/d/1Vra6SgNnso6Nn45adZNd0mVWtTMe5_W4QABhlYxfP8I/formResponse?';
var FIELDS_IDS = ["entry.557679011", "entry.668964305", "entry.315539511", "entry.807998585", "entry.1331990072",
    "entry.540581579", "entry.799866241", "entry.1492550795", "entry.1983407746"];
var SUBMIT_REF = '&submit=Submit';

var CORRECT_A = true;
var INCORRECT_A = false;

var correctAnswerFieldNumber = 1;

var feedbackButtons;

$(document).on('pageshow', '#mainPage', function (e, data) {
    if (localStorage.getItem("previousQuestionActive")=="true") {
        $('#search').val(localStorage.getItem("previousQuestion"));
        localStorage.setItem("previousQuestionActive","false");
    }
});

//click on submit button
$(document).on('click', '#form-submit', function () {
    var email = $('#email').val();
    var previousQuestion = $('#search').val();
    if (supports_html5_storage()) {
        localStorage.setItem("email", email);
    }
    if (supports_html5_storage()) {
        localStorage.setItem("previousQuestion", previousQuestion);
        localStorage.setItem("previousQuestionActive", "true");
    }
    sendAndReload(email);
});

//click on more correct button
$(document).on('click', '#moreCorrectAnswers', function () {
    var toAppend = $('Correct answer:<input id="ea' +
        (correctAnswerFieldNumber + 1) + '" placeholder="Correct answer" name="ea' + (correctAnswerFieldNumber + 1) + '">');
    $("#ea" + correctAnswerFieldNumber).parent().after(toAppend);
    $('#ea' + (correctAnswerFieldNumber + 1)).textinput();
    $("#moreCorrectAnswerContainer").css("position", "relative");
    $("#moreCorrectAnswerContainer").css("top", 37 * correctAnswerFieldNumber);
    correctAnswerFieldNumber++;
});

//send if answer was correct
function sendAndReload(email) {
    var question = $('#search').val();
    var ea = getCorrectAnswers();
    ea = addFeedbackFromInputFields(ea);
    if (ea[0] != "") {
        sendFeedbackAndReload(email, question, ea[0], ea[1], ea[2], ea[3], ea[4], ea[5], ea[6]);
    } else {
        alert("Mark correct answers please.")
    }
}

function addFeedbackFromInputFields(ea) {
    var inputFieldPosition = 1;
    for (var i = 0; i < ea.length; i++) {
        if (ea[i] == null || ea[i] == "") {
            for (inputFieldPosition; ; inputFieldPosition++) {
                if ($("#ea" + inputFieldPosition).length) {
                    if ($("#ea" + inputFieldPosition).val() != "") {
                        ea[i] = $("#ea" + inputFieldPosition).val();
                        inputFieldPosition++;
                        break;
                    } else {
                        continue;
                    }
                }
                break;
            }
        }
        if (i == 6 && ea[i] != "") {
            for (inputFieldPosition; ; inputFieldPosition++) {
                if ($("#ea" + inputFieldPosition).length) {
                    if ($("#ea" + inputFieldPosition).val() != "") {
                        ea[i] += "|" + $("#ea" + inputFieldPosition).val();
                    } else {
                        continue;
                    }
                } else {
                    break;
                }
            }
        }
    }
    return ea;
}

//checks if browser supports html5 storage
function supports_html5_storage() {
    try {
        return 'localStorage' in window && window['localStorage'] !== null;
    } catch (e) {
        return false;
    }
}

//sends feedback to google form
function sendFeedbackAndReload(email, question, ea1, ea2, ea3, ea4, ea5, ea6, mca) {
    var LEmail = FIELDS_IDS[0];
    var LQuestion = FIELDS_IDS[1];
    var LEa = [];
    LEa[0] = FIELDS_IDS[2];
    LEa[1] = FIELDS_IDS[3];
    LEa[2] = FIELDS_IDS[4];
    LEa[3] = FIELDS_IDS[5];
    LEa[4] = FIELDS_IDS[6];
    LEa[5] = FIELDS_IDS[7];

    var LMca = FIELDS_IDS[8];

    var VEmail = encodeURIComponent(email);
    var VQuestion = encodeURIComponent(question);
    var VEa = [];
    VEa[0] = encodeURIComponent(ea1);
    VEa[1] = encodeURIComponent(ea2);
    VEa[2] = encodeURIComponent(ea3);
    VEa[3] = encodeURIComponent(ea4);
    VEa[4] = encodeURIComponent(ea5);
    VEa[5] = encodeURIComponent(ea6);

    var Vmca = encodeURIComponent(mca);

    var submitURL = (BASE_URL +
    LEmail + "=" + VEmail + "&" +
    LQuestion + "=" + VQuestion + "&" +
    LEa[0] + "=" + VEa[0] + "&" +
    LEa[1] + "=" + VEa[1] + "&" +
    LEa[2] + "=" + VEa[2] + "&" +
    LEa[3] + "=" + VEa[3] + "&" +
    LEa[4] + "=" + VEa[4] + "&" +
    LEa[5] + "=" + VEa[5] + "&" +
    LMca + "=" + Vmca +
    SUBMIT_REF);

    $.post(submitURL)
        .always(function () {
            window.location.href = createURL(null);
        });
}

//restores feedback to default state
function showFeedback(numberOfAnswers) {
    $('#feedback_area').css('display', 'inline');
    showAnswerFeedbackButton(numberOfAnswers);
    $('#askMeButton').parent().css("display", "none");
    $('#search').textinput('disable');
    $('#voice').prop('disabled', true).addClass('ui-disabled');
    $('#email').parent().css("width", "82%");
    $('#email').val(localStorage.getItem("email"));
}

//click function on correct button
function clickActionCorrect(i) {
    if (feedbackButtons[i] == INCORRECT_A) {
        feedbackButtons[i] = CORRECT_A;
        $('#feedbackButtonCorrect' + i).addClass("ui-icon-myapp-checked");
        $("#feedbackButtonCorrect" + i).removeClass('ui-icon-myapp-unchecked');
    } else {
        feedbackButtons[i] = INCORRECT_A;
        $('#feedbackButtonCorrect' + i).addClass("ui-icon-myapp-unchecked");
        $("#feedbackButtonCorrect" + i).removeClass('ui-icon-myapp-checked');
    }
}

// shows feedback buttons near answer
function showAnswerFeedbackButton(numberOfAnswers) {
    feedbackButtons = [];
    for (var i = 0; i < numberOfAnswers; i++) {
        createFeedbackButton(i);
    }
}

//creates feedback buttons
function createFeedbackButton(i) {
    feedbackButtons[i] = INCORRECT_A;

    var feedbackButtonCorrect = '<button class="ui-btn ui-mini ui-corner-all ui-icon-myapp-unchecked ui-btn-icon-left ui-btn-inline ui-nodisc-icon" id="feedbackButtonCorrect' + i + '">Correct</button>';
    $("#feedbackButtonArea" + i).append(feedbackButtonCorrect);
    $('#feedbackButtonCorrect' + i).on('click', function (e) {
        clickActionCorrect(i);
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
    });
}

//returns array of correct answers
function getCorrectAnswers() {
    var corrects = [];
    var position = 0;
    for (var i = 0; i < feedbackButtons.length; i++) {
        if (feedbackButtons[i] == true) {
            if (position < 6) {
                corrects[position] = getAnswer(i);
                position++;
            } else {
                if (corrects[position] == null) {
                    corrects[position] = getAnswer(i);
                } else {
                    corrects[position] += "|" + getAnswer(i);
                }
            }
        }
    }
    if (position < 6) {
        for (var i = position; i < 7; i++) {
            corrects[i] = "";
        }
    }
    return corrects;
}

//gets text of answer on i position
function getAnswer(i) {
    return $('#answerText' + i).text();
}<commit_msg>Search field automatically focuses after feedback<commit_after>/**
 * Created by Petr Marek on 21.7.2015.
 */

var BASE_URL = 'https://docs.google.com/forms/d/1Vra6SgNnso6Nn45adZNd0mVWtTMe5_W4QABhlYxfP8I/formResponse?';
var FIELDS_IDS = ["entry.557679011", "entry.668964305", "entry.315539511", "entry.807998585", "entry.1331990072",
    "entry.540581579", "entry.799866241", "entry.1492550795", "entry.1983407746"];
var SUBMIT_REF = '&submit=Submit';

var CORRECT_A = true;
var INCORRECT_A = false;

var correctAnswerFieldNumber = 1;

var feedbackButtons;

$(document).on('pageshow', '#mainPage', function (e, data) {
    if (localStorage.getItem("previousQuestionActive")=="true") {
        $('#search').val(localStorage.getItem("previousQuestion"));
        $('#search').focus();
        localStorage.setItem("previousQuestionActive","false");
    }
});

//click on submit button
$(document).on('click', '#form-submit', function () {
    var email = $('#email').val();
    var previousQuestion = $('#search').val();
    if (supports_html5_storage()) {
        localStorage.setItem("email", email);
    }
    if (supports_html5_storage()) {
        localStorage.setItem("previousQuestion", previousQuestion);
        localStorage.setItem("previousQuestionActive", "true");
    }
    sendAndReload(email);
});

//click on more correct button
$(document).on('click', '#moreCorrectAnswers', function () {
    var toAppend = $('Correct answer:<input id="ea' +
        (correctAnswerFieldNumber + 1) + '" placeholder="Correct answer" name="ea' + (correctAnswerFieldNumber + 1) + '">');
    $("#ea" + correctAnswerFieldNumber).parent().after(toAppend);
    $('#ea' + (correctAnswerFieldNumber + 1)).textinput();
    $("#moreCorrectAnswerContainer").css("position", "relative");
    $("#moreCorrectAnswerContainer").css("top", 37 * correctAnswerFieldNumber);
    correctAnswerFieldNumber++;
});

//send if answer was correct
function sendAndReload(email) {
    var question = $('#search').val();
    var ea = getCorrectAnswers();
    ea = addFeedbackFromInputFields(ea);
    if (ea[0] != "") {
        sendFeedbackAndReload(email, question, ea[0], ea[1], ea[2], ea[3], ea[4], ea[5], ea[6]);
    } else {
        alert("Mark correct answers please.")
    }
}

function addFeedbackFromInputFields(ea) {
    var inputFieldPosition = 1;
    for (var i = 0; i < ea.length; i++) {
        if (ea[i] == null || ea[i] == "") {
            for (inputFieldPosition; ; inputFieldPosition++) {
                if ($("#ea" + inputFieldPosition).length) {
                    if ($("#ea" + inputFieldPosition).val() != "") {
                        ea[i] = $("#ea" + inputFieldPosition).val();
                        inputFieldPosition++;
                        break;
                    } else {
                        continue;
                    }
                }
                break;
            }
        }
        if (i == 6 && ea[i] != "") {
            for (inputFieldPosition; ; inputFieldPosition++) {
                if ($("#ea" + inputFieldPosition).length) {
                    if ($("#ea" + inputFieldPosition).val() != "") {
                        ea[i] += "|" + $("#ea" + inputFieldPosition).val();
                    } else {
                        continue;
                    }
                } else {
                    break;
                }
            }
        }
    }
    return ea;
}

//checks if browser supports html5 storage
function supports_html5_storage() {
    try {
        return 'localStorage' in window && window['localStorage'] !== null;
    } catch (e) {
        return false;
    }
}

//sends feedback to google form
function sendFeedbackAndReload(email, question, ea1, ea2, ea3, ea4, ea5, ea6, mca) {
    var LEmail = FIELDS_IDS[0];
    var LQuestion = FIELDS_IDS[1];
    var LEa = [];
    LEa[0] = FIELDS_IDS[2];
    LEa[1] = FIELDS_IDS[3];
    LEa[2] = FIELDS_IDS[4];
    LEa[3] = FIELDS_IDS[5];
    LEa[4] = FIELDS_IDS[6];
    LEa[5] = FIELDS_IDS[7];

    var LMca = FIELDS_IDS[8];

    var VEmail = encodeURIComponent(email);
    var VQuestion = encodeURIComponent(question);
    var VEa = [];
    VEa[0] = encodeURIComponent(ea1);
    VEa[1] = encodeURIComponent(ea2);
    VEa[2] = encodeURIComponent(ea3);
    VEa[3] = encodeURIComponent(ea4);
    VEa[4] = encodeURIComponent(ea5);
    VEa[5] = encodeURIComponent(ea6);

    var Vmca = encodeURIComponent(mca);

    var submitURL = (BASE_URL +
    LEmail + "=" + VEmail + "&" +
    LQuestion + "=" + VQuestion + "&" +
    LEa[0] + "=" + VEa[0] + "&" +
    LEa[1] + "=" + VEa[1] + "&" +
    LEa[2] + "=" + VEa[2] + "&" +
    LEa[3] + "=" + VEa[3] + "&" +
    LEa[4] + "=" + VEa[4] + "&" +
    LEa[5] + "=" + VEa[5] + "&" +
    LMca + "=" + Vmca +
    SUBMIT_REF);

    $.post(submitURL)
        .always(function () {
            window.location.href = createURL(null);
        });
}

//restores feedback to default state
function showFeedback(numberOfAnswers) {
    $('#feedback_area').css('display', 'inline');
    showAnswerFeedbackButton(numberOfAnswers);
    $('#askMeButton').parent().css("display", "none");
    $('#search').textinput('disable');
    $('#voice').prop('disabled', true).addClass('ui-disabled');
    $('#email').parent().css("width", "82%");
    $('#email').val(localStorage.getItem("email"));
}

//click function on correct button
function clickActionCorrect(i) {
    if (feedbackButtons[i] == INCORRECT_A) {
        feedbackButtons[i] = CORRECT_A;
        $('#feedbackButtonCorrect' + i).addClass("ui-icon-myapp-checked");
        $("#feedbackButtonCorrect" + i).removeClass('ui-icon-myapp-unchecked');
    } else {
        feedbackButtons[i] = INCORRECT_A;
        $('#feedbackButtonCorrect' + i).addClass("ui-icon-myapp-unchecked");
        $("#feedbackButtonCorrect" + i).removeClass('ui-icon-myapp-checked');
    }
}

// shows feedback buttons near answer
function showAnswerFeedbackButton(numberOfAnswers) {
    feedbackButtons = [];
    for (var i = 0; i < numberOfAnswers; i++) {
        createFeedbackButton(i);
    }
}

//creates feedback buttons
function createFeedbackButton(i) {
    feedbackButtons[i] = INCORRECT_A;

    var feedbackButtonCorrect = '<button class="ui-btn ui-mini ui-corner-all ui-icon-myapp-unchecked ui-btn-icon-left ui-btn-inline ui-nodisc-icon" id="feedbackButtonCorrect' + i + '">Correct</button>';
    $("#feedbackButtonArea" + i).append(feedbackButtonCorrect);
    $('#feedbackButtonCorrect' + i).on('click', function (e) {
        clickActionCorrect(i);
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
    });
}

//returns array of correct answers
function getCorrectAnswers() {
    var corrects = [];
    var position = 0;
    for (var i = 0; i < feedbackButtons.length; i++) {
        if (feedbackButtons[i] == true) {
            if (position < 6) {
                corrects[position] = getAnswer(i);
                position++;
            } else {
                if (corrects[position] == null) {
                    corrects[position] = getAnswer(i);
                } else {
                    corrects[position] += "|" + getAnswer(i);
                }
            }
        }
    }
    if (position < 6) {
        for (var i = position; i < 7; i++) {
            corrects[i] = "";
        }
    }
    return corrects;
}

//gets text of answer on i position
function getAnswer(i) {
    return $('#answerText' + i).text();
}<|endoftext|>