<commit_before>var drag = require('./drag');
var util = require('./util');
var m = require('mithril');

function savePrevData(ctrl) {
  var cloned = {
    pieces: {},
    fadings: {},
    anims: {},
    orientation: ''
  };
  var currAnim = ctrl.data.animation.current;
  var pK = ctrl.data.pieces ? Object.keys(ctrl.data.pieces) : [];
  var aK = currAnim.anims ? Object.keys(currAnim.anims) : [];
  var fK = currAnim.fadings ? Object.keys(currAnim.fadings) : [];
  for (var i = 0, ilen = pK.length; i < ilen; i++) {
    cloned.pieces[pK[i]] = ctrl.data.pieces[pK[i]];
  }
  for (var j = 0, jlen = aK.length; j < jlen; j++) {
    cloned.anims[aK[j]] = ctrl.data.animation.current.anims[aK[j]];
  }
  for (var k = 0, klen = fK.length; k < klen; k++) {
    cloned.fadings[fK[k]] = ctrl.data.animation.current.fadings[fK[k]];
  }
  cloned.orientation = ctrl.data.orientation;

  return cloned;
}

function diffAndRenderBoard(ctrl, prevState) {
  var pieces = ctrl.data.pieces;
  var fadings = ctrl.data.animation.current.fadings;
  var key, piece, prevPiece, fading, prevFading, pieceEl, squareEl, sqClass, anim, prevAnim;
  var anims = ctrl.data.animation.current.anims;
  for (var i = 0, len = util.allKeys.length; i < len; i++) {
    key = util.allKeys[i];
    piece = pieces[key];
    prevPiece = prevState.pieces[key];
    anim = anims && anims[key];
    prevAnim = prevState.anims[key];
    fading = fadings && fadings[key];
    prevFading = prevState.fadings[key];
    squareEl = ctrl.data.squareEls[key];
    sqClass = squareClass(ctrl, key, piece);

    // should not happen
    if (squareEl === undefined || squareEl === null) {
      console.log('Chessground: attempt to diff against unexisting square element ' + key);
      return;
    }

    // update highlights
    if (squareEl.className !== sqClass) squareEl.className = sqClass;

    // remove previous fading if any when animation is finished
    if (prevFading && !fading) {
      var fadingPieceEls = squareEl.getElementsByClassName('cg-piece fading');
      while (fadingPieceEls[0]) squareEl.removeChild(fadingPieceEls[0]);
    }
    // there is a now piece at this square
    if (piece) {
      if (anim) {
        var animP = squareEl.getElementsByClassName('cg-piece').item(0);
        if (animP) animP.style[util.transformProp()] = util.translate(anim[1]);
      } else if (prevAnim) {
        var prevAnimP = squareEl.getElementsByClassName('cg-piece').item(0);
        if (prevAnimP) prevAnimP.removeAttribute('style');
      }
      // a piece was already there
      if (prevPiece) {
        // same piece same square: do nothing
        if (piece.role === prevPiece.role && piece.color === prevPiece.color) {
          continue;
        } else {
          // different pieces: remove old piece and put new one
          pieceEl = renderPieceDom(renderPiece(ctrl, key, piece));
          if (squareEl.firstChild && squareEl.firstChild.nodeType) {
            squareEl.replaceChild(pieceEl, squareEl.firstChild);
          } else {
            squareEl.appendChild(pieceEl);
            console.log('Chessground: failed attempt to replace child on square ' + key);
          }
          // during an animation we render a temporary 'fading' piece (the name
          // is wrong because it's not fading, it's juste here)
          // make sure there is no fading piece already (may happen with promotion)
          if (fading && !prevFading) {
            if (squareEl.firstChild && squareEl.firstChild.nodeType) {
              squareEl.replaceChild(renderCapturedDom(fading), squareEl.firstChild);
              console.log('Chessground: this square should not have a child ' + key);
            } else {
              squareEl.appendChild(renderCapturedDom(fading));
            }
          }
        }
      } // empty square before: just put the piece
      else {
        pieceEl = renderPieceDom(renderPiece(ctrl, key, piece));
        if (squareEl.firstChild && squareEl.firstChild.nodeType) {
          squareEl.replaceChild(pieceEl, squareEl.firstChild);
          console.log('Chessground: this square should not have a child ' + key);
        } else {
          squareEl.appendChild(pieceEl);
        }
      }
    } // no piece at this square
    else {
      // remove any piece that was here
      if (prevPiece) {
        while (squareEl.firstChild) squareEl.removeChild(squareEl.firstChild);
      }
    }
  }
}

function pieceClass(p) {
  return ['cg-piece', p.role, p.color].join(' ');
}

function renderPiece(ctrl, key, p) {
  var attrs = {
    style: {},
    className: pieceClass(p)
  };
  var draggable = ctrl.data.draggable.current;
  if (draggable.orig === key && (draggable.pos[0] !== 0 || draggable.pos[1] !== 0)) {
    attrs.style[util.transformProp()] = util.translate([
      draggable.pos[0] + draggable.dec[0],
      draggable.pos[1] + draggable.dec[1]
    ]);
    attrs.className += ' dragging';
  } else if (ctrl.data.animation.current.anims) {
    var animation = ctrl.data.animation.current.anims[key];
    if (animation) attrs.style[util.transformProp()] = util.translate(animation[1]);
  }
  return {
    tag: 'div',
    attrs: attrs
  };
}

function renderPieceDom(vdom) {
  var p = document.createElement('div');
  p.className = vdom.attrs.className;
  p.style[util.transformProp()] = vdom.attrs.style[util.transformProp()];
  return p;
}

function renderCapturedDom(p) {
  var cap = document.createElement('div');
  cap.className = pieceClass(p) + ' fading';
  return cap;
}

function squareClass(ctrl, key, piece) {
  return 'cg-square ' + key + ' ' + util.classSet({
    'selected': ctrl.data.selected === key,
    'check': ctrl.data.highlight.check && ctrl.data.check === key,
    'last-move': ctrl.data.highlight.lastMove && util.contains2(ctrl.data.lastMove, key),
    'move-dest': ctrl.data.movable.showDests && util.containsX(ctrl.data.movable.dests[ctrl.data.selected], key),
    'premove-dest': ctrl.data.premovable.showDests && util.containsX(ctrl.data.premovable.dests, key),
    'current-premove': util.contains2(ctrl.data.premovable.current, key),
    'occupied': !!piece,
    'exploding': ctrl.vm.exploding && ctrl.vm.exploding.indexOf(key) !== -1
  });
}

function renderSquare(ctrl, pos, asWhite) {
  var file = util.files[pos[0] - 1];
  var rank = pos[1];
  var key = file + rank;
  var piece = ctrl.data.pieces[key];
  var bpos = util.boardpos(pos, asWhite);
  var attrs = {
    className: squareClass(ctrl, key, piece),
    style: {
      left: bpos.left + '%',
      bottom: bpos.bottom + '%'
    },
    config: function(el, isUpdate) {
      if (!isUpdate) {
        ctrl.data.squareEls[key] = el;
      }
    }
  };
  var children = [];
  if (piece) {
    children.push(renderPiece(ctrl, key, piece));
  }
  if (ctrl.data.coordinates) {
    if (pos[1] === (asWhite ? 1 : 8)) attrs['data-coord-x'] = file;
    if (pos[0] === (asWhite ? 8 : 1)) attrs['data-coord-y'] = rank;
  }
  return {
    tag: 'div',
    attrs: attrs,
    children: children
  };
}

function renderMinimalDom(ctrl, asWhite) {
  var children = [];
  if (ctrl.data.lastMove) ctrl.data.lastMove.forEach(function(key) {
    var pos = util.key2pos(key);
    var bpos = util.boardpos(pos, asWhite);
    children.push({
      tag: 'div',
      attrs: {
        className: 'cg-square last-move',
        style: {
          left: bpos.left + '%',
          bottom: bpos.bottom + '%'
        }
      }
    });
  });
  var piecesKeys = Object.keys(ctrl.data.pieces);
  for (var i = 0, len = piecesKeys.length; i < len; i++) {
    var key = piecesKeys[i];
    var pos = util.key2pos(key);
    var bpos = util.boardpos(pos, asWhite);
    var attrs = {
      style: {
        left: bpos.left + '%',
        bottom: bpos.bottom + '%'
      },
      className: pieceClass(ctrl.data.pieces[key])
    };
    children.push({
      tag: 'div',
      attrs: attrs
    });
  }

  return children;
}

function renderContent(ctrl) {
  var asWhite = ctrl.data.orientation === 'white';
  if (ctrl.data.minimalDom) return renderMinimalDom(ctrl, asWhite);
  var positions = asWhite ? util.allPos : util.invPos;
  var children = [];
  for (var i = 0, len = positions.length; i < len; i++) {
    children.push(renderSquare(ctrl, positions[i], asWhite));
  }
  return children;
}

function bindEvents(ctrl, el) {
  var onstart = drag.start.bind(undefined, ctrl.data);
  var onmove = drag.move.bind(undefined, ctrl.data);
  var onend = drag.end.bind(undefined, ctrl.data);
  var oncancel = drag.cancel.bind(undefined, ctrl.data);
  if (!ctrl.data.viewOnly) {
    el.addEventListener('touchstart', onstart);
    el.addEventListener('touchmove', onmove);
    el.addEventListener('touchend', onend);
    el.addEventListener('touchcancel', oncancel);
  }
}

function renderBoard(ctrl) {
  return {
    tag: 'div',
    attrs: {
      id: 'cg-board',
      className: [
        'cg-board orientation-' + ctrl.data.orientation,
        ctrl.data.viewOnly ? 'view-only' : 'manipulable',
        ctrl.data.minimalDom ? 'minimal-dom' : 'full-dom'
      ].join(' '),
      config: function(el, isUpdate) {
        if (isUpdate) return;

        var scheduledAnimationFrame;

        ctrl.data.bounds = el.getBoundingClientRect();
        ctrl.data.element = el;
        ctrl.data.render = function() {
          scheduledAnimationFrame = false;
          if (ctrl.data.minimalDom) {
            m.render(el, renderContent(ctrl));
          } else {
            if (ctrl.data.prevState.orientation !== ctrl.data.orientation) {
              m.render(el, renderContent(ctrl), true);
              ctrl.data.prevState = savePrevData(ctrl);
              diffAndRenderBoard(ctrl, ctrl.data.prevState);
            } else {
              diffAndRenderBoard(ctrl, ctrl.data.prevState);
              ctrl.data.prevState = savePrevData(ctrl);
            }
          }
        };
        ctrl.data.renderRAF = function() {
          if (scheduledAnimationFrame) return;
          scheduledAnimationFrame = true;
          requestAnimationFrame(ctrl.data.render);
        };

        // set initial ui state
        ctrl.data.prevState = {
          pieces: ctrl.data.pieces,
          fadings: {},
          anims: {},
          orientation: ctrl.data.orientation
        };

        ctrl.data.render();

        bindEvents(ctrl, el);
      }
    },
    children: renderContent(ctrl)
  };
}

module.exports = renderBoard;
<commit_msg>Fix fading bug<commit_after>var drag = require('./drag');
var util = require('./util');
var m = require('mithril');

function savePrevData(ctrl) {
  var cloned = {
    pieces: {},
    fadings: {},
    anims: {},
    orientation: ''
  };
  var currAnim = ctrl.data.animation.current;
  var pK = ctrl.data.pieces ? Object.keys(ctrl.data.pieces) : [];
  var aK = currAnim.anims ? Object.keys(currAnim.anims) : [];
  var fK = currAnim.fadings ? Object.keys(currAnim.fadings) : [];
  for (var i = 0, ilen = pK.length; i < ilen; i++) {
    cloned.pieces[pK[i]] = ctrl.data.pieces[pK[i]];
  }
  for (var j = 0, jlen = aK.length; j < jlen; j++) {
    cloned.anims[aK[j]] = ctrl.data.animation.current.anims[aK[j]];
  }
  for (var k = 0, klen = fK.length; k < klen; k++) {
    cloned.fadings[fK[k]] = ctrl.data.animation.current.fadings[fK[k]];
  }
  cloned.orientation = ctrl.data.orientation;

  return cloned;
}

function diffAndRenderBoard(ctrl, prevState) {
  var pieces = ctrl.data.pieces;
  var fadings = ctrl.data.animation.current.fadings;
  var key, piece, prevPiece, fading, prevFading, pieceEl, squareEl, sqClass, anim, prevAnim;
  var anims = ctrl.data.animation.current.anims;
  for (var i = 0, len = util.allKeys.length; i < len; i++) {
    key = util.allKeys[i];
    piece = pieces[key];
    prevPiece = prevState.pieces[key];
    anim = anims && anims[key];
    prevAnim = prevState.anims[key];
    fading = fadings && fadings[key];
    prevFading = prevState.fadings[key];
    squareEl = ctrl.data.squareEls[key];
    sqClass = squareClass(ctrl, key, piece);

    // should not happen
    if (squareEl === undefined || squareEl === null) {
      console.log('Chessground: attempt to diff against unexisting square element ' + key);
      return;
    }

    // update highlights
    if (squareEl.className !== sqClass) squareEl.className = sqClass;

    // remove previous fading if any when animation is finished
    if (prevFading && !fading) {
      var fadingPieceEls = squareEl.getElementsByClassName('cg-piece fading');
      while (fadingPieceEls[0]) squareEl.removeChild(fadingPieceEls[0]);
    }
    // there is a now piece at this square
    if (piece) {
      if (anim) {
        var animP = squareEl.getElementsByClassName('cg-piece').item(0);
        if (animP) animP.style[util.transformProp()] = util.translate(anim[1]);
      } else if (prevAnim) {
        var prevAnimP = squareEl.getElementsByClassName('cg-piece').item(0);
        if (prevAnimP) prevAnimP.removeAttribute('style');
      }
      // a piece was already there
      if (prevPiece) {
        // same piece same square: do nothing
        if (piece.role === prevPiece.role && piece.color === prevPiece.color) {
          continue;
        } else {
          // different pieces: remove old piece and put new one
          pieceEl = renderPieceDom(renderPiece(ctrl, key, piece));
          if (squareEl.firstChild && squareEl.firstChild.nodeType) {
            squareEl.replaceChild(pieceEl, squareEl.firstChild);
          } else {
            squareEl.appendChild(pieceEl);
            console.log('Chessground: failed attempt to replace child on square ' + key);
          }
          // during an animation we render a temporary 'fading' piece (the name
          // is wrong because it's not fading, it's juste here)
          // make sure there is no fading piece already (may happen with promotion)
          if (fading && !prevFading) {
            squareEl.appendChild(renderCapturedDom(fading));
          }
        }
      } // empty square before: just put the piece
      else {
        pieceEl = renderPieceDom(renderPiece(ctrl, key, piece));
        if (squareEl.firstChild && squareEl.firstChild.nodeType) {
          squareEl.replaceChild(pieceEl, squareEl.firstChild);
          console.log('Chessground: this square should not have a child ' + key);
        } else {
          squareEl.appendChild(pieceEl);
        }
      }
    } // no piece at this square
    else {
      // remove any piece that was here
      if (prevPiece) {
        while (squareEl.firstChild) squareEl.removeChild(squareEl.firstChild);
      }
    }
  }
}

function pieceClass(p) {
  return ['cg-piece', p.role, p.color].join(' ');
}

function renderPiece(ctrl, key, p) {
  var attrs = {
    style: {},
    className: pieceClass(p)
  };
  var draggable = ctrl.data.draggable.current;
  if (draggable.orig === key && (draggable.pos[0] !== 0 || draggable.pos[1] !== 0)) {
    attrs.style[util.transformProp()] = util.translate([
      draggable.pos[0] + draggable.dec[0],
      draggable.pos[1] + draggable.dec[1]
    ]);
    attrs.className += ' dragging';
  } else if (ctrl.data.animation.current.anims) {
    var animation = ctrl.data.animation.current.anims[key];
    if (animation) attrs.style[util.transformProp()] = util.translate(animation[1]);
  }
  return {
    tag: 'div',
    attrs: attrs
  };
}

function renderPieceDom(vdom) {
  var p = document.createElement('div');
  p.className = vdom.attrs.className;
  p.style[util.transformProp()] = vdom.attrs.style[util.transformProp()];
  return p;
}

function renderCapturedDom(p) {
  var cap = document.createElement('div');
  cap.className = pieceClass(p) + ' fading';
  return cap;
}

function squareClass(ctrl, key, piece) {
  return 'cg-square ' + key + ' ' + util.classSet({
    'selected': ctrl.data.selected === key,
    'check': ctrl.data.highlight.check && ctrl.data.check === key,
    'last-move': ctrl.data.highlight.lastMove && util.contains2(ctrl.data.lastMove, key),
    'move-dest': ctrl.data.movable.showDests && util.containsX(ctrl.data.movable.dests[ctrl.data.selected], key),
    'premove-dest': ctrl.data.premovable.showDests && util.containsX(ctrl.data.premovable.dests, key),
    'current-premove': util.contains2(ctrl.data.premovable.current, key),
    'occupied': !!piece,
    'exploding': ctrl.vm.exploding && ctrl.vm.exploding.indexOf(key) !== -1
  });
}

function renderSquare(ctrl, pos, asWhite) {
  var file = util.files[pos[0] - 1];
  var rank = pos[1];
  var key = file + rank;
  var piece = ctrl.data.pieces[key];
  var bpos = util.boardpos(pos, asWhite);
  var attrs = {
    className: squareClass(ctrl, key, piece),
    style: {
      left: bpos.left + '%',
      bottom: bpos.bottom + '%'
    },
    config: function(el, isUpdate) {
      if (!isUpdate) {
        ctrl.data.squareEls[key] = el;
      }
    }
  };
  var children = [];
  if (piece) {
    children.push(renderPiece(ctrl, key, piece));
  }
  if (ctrl.data.coordinates) {
    if (pos[1] === (asWhite ? 1 : 8)) attrs['data-coord-x'] = file;
    if (pos[0] === (asWhite ? 8 : 1)) attrs['data-coord-y'] = rank;
  }
  return {
    tag: 'div',
    attrs: attrs,
    children: children
  };
}

function renderMinimalDom(ctrl, asWhite) {
  var children = [];
  if (ctrl.data.lastMove) ctrl.data.lastMove.forEach(function(key) {
    var pos = util.key2pos(key);
    var bpos = util.boardpos(pos, asWhite);
    children.push({
      tag: 'div',
      attrs: {
        className: 'cg-square last-move',
        style: {
          left: bpos.left + '%',
          bottom: bpos.bottom + '%'
        }
      }
    });
  });
  var piecesKeys = Object.keys(ctrl.data.pieces);
  for (var i = 0, len = piecesKeys.length; i < len; i++) {
    var key = piecesKeys[i];
    var pos = util.key2pos(key);
    var bpos = util.boardpos(pos, asWhite);
    var attrs = {
      style: {
        left: bpos.left + '%',
        bottom: bpos.bottom + '%'
      },
      className: pieceClass(ctrl.data.pieces[key])
    };
    children.push({
      tag: 'div',
      attrs: attrs
    });
  }

  return children;
}

function renderContent(ctrl) {
  var asWhite = ctrl.data.orientation === 'white';
  if (ctrl.data.minimalDom) return renderMinimalDom(ctrl, asWhite);
  var positions = asWhite ? util.allPos : util.invPos;
  var children = [];
  for (var i = 0, len = positions.length; i < len; i++) {
    children.push(renderSquare(ctrl, positions[i], asWhite));
  }
  return children;
}

function bindEvents(ctrl, el) {
  var onstart = drag.start.bind(undefined, ctrl.data);
  var onmove = drag.move.bind(undefined, ctrl.data);
  var onend = drag.end.bind(undefined, ctrl.data);
  var oncancel = drag.cancel.bind(undefined, ctrl.data);
  if (!ctrl.data.viewOnly) {
    el.addEventListener('touchstart', onstart);
    el.addEventListener('touchmove', onmove);
    el.addEventListener('touchend', onend);
    el.addEventListener('touchcancel', oncancel);
  }
}

function renderBoard(ctrl) {
  return {
    tag: 'div',
    attrs: {
      id: 'cg-board',
      className: [
        'cg-board orientation-' + ctrl.data.orientation,
        ctrl.data.viewOnly ? 'view-only' : 'manipulable',
        ctrl.data.minimalDom ? 'minimal-dom' : 'full-dom'
      ].join(' '),
      config: function(el, isUpdate) {
        if (isUpdate) return;

        var scheduledAnimationFrame;

        ctrl.data.bounds = el.getBoundingClientRect();
        ctrl.data.element = el;
        ctrl.data.render = function() {
          scheduledAnimationFrame = false;
          if (ctrl.data.minimalDom) {
            m.render(el, renderContent(ctrl));
          } else {
            if (ctrl.data.prevState.orientation !== ctrl.data.orientation) {
              m.render(el, renderContent(ctrl), true);
              ctrl.data.prevState = savePrevData(ctrl);
              diffAndRenderBoard(ctrl, ctrl.data.prevState);
            } else {
              diffAndRenderBoard(ctrl, ctrl.data.prevState);
              ctrl.data.prevState = savePrevData(ctrl);
            }
          }
        };
        ctrl.data.renderRAF = function() {
          if (scheduledAnimationFrame) return;
          scheduledAnimationFrame = true;
          requestAnimationFrame(ctrl.data.render);
        };

        // set initial ui state
        ctrl.data.prevState = {
          pieces: ctrl.data.pieces,
          fadings: {},
          anims: {},
          orientation: ctrl.data.orientation
        };

        ctrl.data.render();

        bindEvents(ctrl, el);
      }
    },
    children: renderContent(ctrl)
  };
}

module.exports = renderBoard;
<|endoftext|>


<commit_before>import HubSensor from 'sensors/hub-sensor';

// the amount of ounces required to flow to consider a pour occurred
const pourThreshold = 0.15;

// may require calibration
const pulsesPerLiter = 450;
const ouncesPerLiter = 33.814;
const pulsesPerOunce = 13.308;

/*

*/
export default class FlowMeter extends HubSensor {

  constructor(firebase, id, fiveSensor, display = null) {
    super();
    this._firebase = firebase;
    this._id = id;
    this._sensor = fiveSensor;
    this._display = display;

    // pulses per session - gets reset
    let sessionPulses = 0;

    // state of flow meter
    let isOpen = false;

    this._lastPulse = Date.now();
    this._hertz = 0;
    this._flow = 0;
    this._thisPour = 0;
    this._totalPour = 0;

    this._sensor.on('change', (value) => {
      if (!value) {
        return;
      }

      sessionPulses++;
      isOpen = true;

      let currentTime = Date.now();
      this._clickDelta = Math.max([currentTime - this._lastPulse], 1);
      if (this._clickDelta < 1000) {
          this._hertz = 1000 / this._clickDelta;
          this._flow = this._hertz / (60 * 7.5);
          let p = this._flow * (this._clickDelta / 1000);
          this._thisPour += p;
          this._totalPour += p;
          console.log(`${id} poured: ${this._thisPour}`);
      }

      this._lastPulse = currentTime;

      // let currentSession = sessionPulses;
      // setTimeout(() => {
      //   if (currentSession === sessionPulses) {
      //     const ounces = Math.round((sessionPulses / pulsesPerOunce) * 100) / 100;
      //     if (ounces > pourThreshold) {
      //       const message = `${id} poured: ${ounces} oz`;
      //       super.report(message);
      //
      //       // report to firebase
      //       this.logPour(ounces);
      //
      //       // write to display
      //       if (this._display && this._display.getIsOn()) {
      //         this._display.write(message);
      //         setTimeout(() => {
      //           this._display.clear();
      //         }, 500);
      //       }
      //
      //       // reset session
      //       sessionPulses = 0;
      //       isOpen = false;
      //     }
      //   }
      // }, 1000);
    });
  }

  // save the last pour
  logPour(ounces) {
    // get beer id for relationship
    this._firebase.database().ref(`taps/${this._id}`).once('value').then((snapshot) => {
      const beer = snapshot.val().beer;
      const pourData = {
        beer: beer,
        ounces: ounces,
        created: (new Date()).getTime()
      };

      // record pour for beer
      const pour = this._firebase.database().ref(`pours`).push(pourData).key;
      this._firebase.database().ref(`beers/${beer}/pours/${pour}`).set(true);
    });
  }
}
<commit_msg>calculate instantaneous speed, more debugging<commit_after>import HubSensor from 'sensors/hub-sensor';

// the amount of ounces required to flow to consider a pour occurred
const pourThreshold = 0.15;

// may require calibration
const pulsesPerLiter = 450;
const ouncesPerLiter = 33.814;
const pulsesPerOunce = 13.308;

/*

*/
export default class FlowMeter extends HubSensor {

  constructor(firebase, id, fiveSensor, display = null) {
    super();
    this._firebase = firebase;
    this._id = id;
    this._sensor = fiveSensor;
    this._display = display;

    // pulses per session - gets reset
    let sessionPulses = 0;

    // state of flow meter
    let isOpen = false;

    this._lastPulse = Date.now();
    this._hertz = 0;
    this._flow = 0;
    this._thisPour = 0;
    this._totalPour = 0;

    this._sensor.on('change', (value) => {
      if (!value) {
        return;
      }

      // sessionPulses++;
      // isOpen = true;

      let currentTime = Date.now();
      this._clickDelta = Math.max([currentTime - this._lastPulse], 1);
      if (this._clickDelta < 1000) {
          this._hertz = 1000 / this._clickDelta;
          this._flow = this._hertz / (60 * 7.5);
          let p = (this._flow * (this._clickDelta / 1000) * 25.11338);
          this._thisPour += p;
          this._totalPour += p;
          console.log(`${id} poured: ${this._thisPour}`);
          setTimeout(() => {
            if (this._lastPulse - Date.now() > 1000) {
              console.log(`the pour was: ${this._thisPour}`);
            }
          }, 1000);
      }

      this._lastPulse = currentTime;

      // let currentSession = sessionPulses;
      // setTimeout(() => {
      //   if (currentSession === sessionPulses) {
      //     const ounces = Math.round((sessionPulses / pulsesPerOunce) * 100) / 100;
      //     if (ounces > pourThreshold) {
      //       const message = `${id} poured: ${ounces} oz`;
      //       super.report(message);
      //
      //       // report to firebase
      //       this.logPour(ounces);
      //
      //       // write to display
      //       if (this._display && this._display.getIsOn()) {
      //         this._display.write(message);
      //         setTimeout(() => {
      //           this._display.clear();
      //         }, 500);
      //       }
      //
      //       // reset session
      //       sessionPulses = 0;
      //       isOpen = false;
      //     }
      //   }
      // }, 1000);
    });
  }

  // save the last pour
  logPour(ounces) {
    // get beer id for relationship
    this._firebase.database().ref(`taps/${this._id}`).once('value').then((snapshot) => {
      const beer = snapshot.val().beer;
      const pourData = {
        beer: beer,
        ounces: ounces,
        created: (new Date()).getTime()
      };

      // record pour for beer
      const pour = this._firebase.database().ref(`pours`).push(pourData).key;
      this._firebase.database().ref(`beers/${beer}/pours/${pour}`).set(true);
    });
  }
}
<|endoftext|>


<commit_before>var _ = require('./util');
var Node = require('./node');

var Text = function() {
  Node.apply(this, arguments);
};

Text.prototype = _.extend(Text.prototype, Node.prototype, {
  draw: function(ctx) {
    if (!this.visible) {
      return;
    }

    var x = this.x || 0;
    var y = this.y || 0;

    ctx.font = this.font || (this.fontSize + 'px ' + this.fontFamily) || '10px sans-serif';
    ctx.textAlign = this.textAlign || 'start';
    ctx.textBaseline = this.textBaseline || 'alphabetic';

    if (this.fillStyle) {
      ctx.fillStyle = this.fillStyle;
      ctx.fillText(this.text, x, y);
    }
    if (this.strokeStyle) {
      ctx.strokeStyle = this.strokeStyle;
      ctx.lineWidth = this.lineWidth || 1;
      ctx.lineCap = this.lineCap || 'butt';
      ctx.lineJoin = this.lineJoin || 'miter';
      ctx.strokeText(this.text, x, y);
    }
  },

  pick: function(ctx, x, y, lx, ly) {
    if (!this.visible) {
      return;
    }

    var x = this.x || 0;
    var y = this.y || 0;
    var width = ctx.measureText(this.text);
    // XXX Height
    if (lx >= x && lx < x+width && ly >= y && ly < y+10) {
      return this;
    }
  }
});

module.exports = Text;<commit_msg>Support rotation of text (and apply transition first)<commit_after>var _ = require('./util');
var Node = require('./node');

var Text = function() {
  Node.apply(this, arguments);
};

Text.prototype = _.extend(Text.prototype, Node.prototype, {
  draw: function(ctx) {
    if (!this.visible) {
      return;
    }

    var x = this.x || 0;
    var y = this.y || 0;

    if (this.rotation) {
      ctx.save();
      ctx.translate(x,y);
      x = 0;
      y = 0;
      ctx.rotate(this.rotation);
    }

    ctx.font = this.font || (this.fontSize + 'px ' + this.fontFamily) || '10px sans-serif';
    ctx.textAlign = this.textAlign || 'start';
    ctx.textBaseline = this.textBaseline || 'alphabetic';

    if (this.fillStyle) {
      ctx.fillStyle = this.fillStyle;
      ctx.fillText(this.text, x, y);
    }
    if (this.strokeStyle) {
      ctx.strokeStyle = this.strokeStyle;
      ctx.lineWidth = this.lineWidth || 1;
      ctx.lineCap = this.lineCap || 'butt';
      ctx.lineJoin = this.lineJoin || 'miter';
      ctx.strokeText(this.text, x, y);
    }

    if (this.rotation) {
      ctx.restore();
    }
  },

  pick: function(ctx, x, y, lx, ly) {
    if (!this.visible) {
      return;
    }

    var x = this.x || 0;
    var y = this.y || 0;
    var width = ctx.measureText(this.text);
    // XXX Height
    if (lx >= x && lx < x+width && ly >= y && ly < y+10) {
      return this;
    }
  }
});

module.exports = Text;<|endoftext|>


<commit_before>var cameraLoc;

// From: https://en.wikipedia.org/wiki/Aircraft_principal_axes
var cameraPitch = 0;
var cameraRoll = 0;
var cameraYaw = 0;
var generatedTerrain;

function setup(){
  createCanvas(windowWidth, windowHeight, WEBGL);

  terrain = new Terrain();
  cameraLoc = createVector(0, 100, 0);
  cameraPitch = - PI / 6;

  stroke(255);
  // draw();
  // noLoop();
  // frameRate(0.005);
  frameRate(5);
}

function draw(){
  background(45);

  let baseColor = color(30, 90, 30);
  let maxColor = color(230, 240, 230);

  let cellSize = 80;
  let renderDepth = 30;
  let renderWidth = 4 * (width / cellSize);
  console.log("Render Width: "+ renderWidth);

  generatedTerrain = [];

  // Build Terrain
  for (var z = 0; z < renderDepth; z++){
    generatedTerrain.push([]);
    for (var x = 0; x < renderWidth; x++){
      let xScaled = ((x - renderWidth/2) * cellSize) + cameraLoc.x;
      let zScaled = (z * cellSize) + cameraLoc.z;
      let heightAtLoc = terrain.elevationAt(xScaled, zScaled);
      generatedTerrain[z][x] = heightAtLoc;
    }
  }

  // translate(width/2, height/2);
  translate(cameraLoc.x, cameraLoc.y, cameraLoc.z);
  rotateX(cameraPitch);

  // Render mesh for Terrain
  noFill();
  for (var z = 0; z < renderDepth-1; z++){
    beginShape(TRIANGLE_STRIP);
    for (var x = 0; x < renderWidth; x++){
      let xScaled = ((x - renderWidth/2)* cellSize) + cameraLoc.x;
      let zScaled = (z * cellSize) + cameraLoc.z;

      let nextScaledZ = zScaled + cellSize;
      vertex(xScaled, generatedTerrain[z][x], zScaled);
      vertex(xScaled, generatedTerrain[z+1][x], nextScaledZ);
    }
    endShape();
  }
  cameraLoc.z -= cellSize;
}

class Terrain {
  constructor(){
    this.noiseScale = 0.005;
  }

  elevationAt(x, z){
    return 100 * noise(x * this.noiseScale, z * this.noiseScale);
  }
}
<commit_msg>p5js/terrain-3d - Fix fly over effect<commit_after>var cameraLoc;

// From: https://en.wikipedia.org/wiki/Aircraft_principal_axes
var cameraPitch = 0;
var cameraRoll = 0;
var cameraYaw = 0;
var generatedTerrain;
var terrain;

function setup(){
  createCanvas(windowWidth, windowHeight, WEBGL);

  terrain = new Terrain();
  cameraLoc = createVector(0, 0, 0);
  cameraPitch = PI / 3;

  stroke(255);
  // draw();
  // noLoop();
  // frameRate(0.005);
  frameRate(5);
}

function draw(){
  background(45);

  let baseColor = color(30, 90, 30);
  let maxColor = color(230, 240, 230);

  let cellSize = 20;
  let renderDepth = 120;
  let renderWidth = 2 * (width / cellSize);
  console.log("Render Width: "+ renderWidth);

  generatedTerrain = [];

  // Build Terrain
  for (var y = 0; y < renderDepth; y++){
    generatedTerrain.push([]);
    for (var x = 0; x < renderWidth; x++){
      let xScaled = ((x - renderWidth/2) * cellSize) + cameraLoc.x;
      let yScaled = (y * cellSize) + cameraLoc.y;
      let heightAtLoc = terrain.elevationAt(xScaled, yScaled);
      generatedTerrain[y][x] = heightAtLoc;
    }
  }

  // translate(width/2, height/2);
  // translate(cameraLoc.x, cameraLoc.y, cameraLoc.z);
  rotateX(cameraPitch);

  // x => left-right
  // y => forward-back
  // z => up-down
  translate(0, -2000, 0);

  // Render mesh for Terrain
  noFill();
  for (var y = 0; y < renderDepth-1; y++){
    beginShape(TRIANGLE_STRIP);
    for (var x = 0; x < renderWidth; x++){
      let xScaled = ((x - renderWidth/2)* cellSize) + cameraLoc.x;
      let yScaled = (y * cellSize) + 0;

      let nextScaledY = yScaled + cellSize;
      vertex(xScaled, yScaled, generatedTerrain[y][x]);
      vertex(xScaled, nextScaledY, generatedTerrain[y+1][x]);
    }
    endShape();
  }
  cameraLoc.y -= cellSize;
}

class Terrain {
  constructor(){
    this.noiseScale = 0.005;
  }

  elevationAt(x, z){
    return 100 * noise(x * this.noiseScale, z * this.noiseScale);
  }
}

<|endoftext|>


<commit_before>Glow.provide(function(glow) {
	var ResponseProto;

	/**
	@name glow.net.Response
	@class
	@extends glow.event.Event
	@description Provided in callbacks to {@link glow.net.post glow.net.post}, {@link glow.net.get glow.net.get}, {@link glow.net.put glow.net.put} and {@link glow.net.delete glow.net.delete} and glow.net.Requests load and error events.

	@glowPrivateConstructor There is no direct constructor.
	*/

	/*
	These params are hidden as we don't want users to try and create instances of this...

	@param {XMLHttpRequest} nativeResponse
	@param {Boolean} [timedOut=false] Set to true if the response timed out
	@param {glow.net.Request} [request] Original request object
	*/
	function Response(nativeResponse, timedOut, request) {

		/**
		@name glow.net.Response#_request
		@private
		@description Original request object
		@type glow.net.Request
		*/

		this._request = request;

		/**
		@name glow.net.Response#nativeResponse
		@description The response object from the browser.
			This may not have the same properties and methods across user agents.
		@type Object
		*/

		this.nativeResponse = nativeResponse;

		/**
		@name glow.net.Response#status
		@description HTTP status code of the response
		@type Number
		*/

		//IE reports status as 1223 rather than 204, for laffs
		this.status = timedOut ? 408 :
			nativeResponse.status == 1223 ? 204 : nativeResponse.status;

		/**
		@name glow.net.Response#timedOut
		@description Boolean indicating if the requests time out was reached.
		@type boolean
		*/

		this.timedOut = !!timedOut;

		/**
		@name glow.net.Response#wasSuccessful
		@description  Boolean indicating if the request returned successfully.
		@type boolean
		*/

		this.wasSuccessful = (this.status >= 200 && this.status < 300) ||
		//from cache
		this.status == 304 ||
		//watch our for requests from file://
		(this.status == 0 && nativeResponse.responseText);

	}

	glow.util.extend(Response, glow.events.Event);
	ResponseProto = Response.prototype;

		/**
			@name glow.net.Response#text
			@function
			@description Gets the body of the response as plain text
			@returns {string} Response as text
		*/

		ResponseProto.text = function() {
			return this.nativeResponse.responseText;
		};

		/**
		@name glow.net.Response#xml
		@function
		@description Gets the body of the response as xml
		@returns {xml} Response as XML
		*/

		ResponseProto.xml = function() {
			var nativeResponse = this.nativeResponse;

			if (
				// IE fails to recognise the doc as XML in some cases
				( glow.env.ie && shouldParseAsXml.call(this) )
				// If the _forceXml option is set, we need to turn the response text into xml
				|| ( this._request._forceXml && !this._request.nativeRequest.overrideMimeType && window.ActiveXObject )
			) {
				var doc = new ActiveXObject("Microsoft.XMLDOM");
				doc.loadXML( nativeResponse.responseText );
				return doc;
			}
			else {
				// check property exists
				if (!nativeResponse.responseXML) {
					throw new Error('Cannot get response as XML, check the mime type of the data');
				}
				return nativeResponse.responseXML;
			}
		};
		/**
		@private
		@function
		@description Should the response be treated as xml? This function is used by IE only
			'this' is the response object
		@returns {Boolean}
		*/
		function shouldParseAsXml() {
			var contentType = this.header("Content-Type"),
				endsPlusXml = str.slice(-4);
			// IE 6 & 7 fail to recognise Content-Types ending +xml (eg application/rss+xml)
			// Files from the filesystem don't have a content type, but could be xml files, parse them to be safe
			return endsPlusXml.test(contentType) || contentType === '';
		}
		/**
		@name glow.net.Response#json
		@function
		@description Gets the body of the response as a json object

		@param {Boolean} [safeMode=false]
			If true, the response will be parsed using a string parser which
			will filter out non-JSON javascript, this will be slower but
			recommended if you do not trust the data source.

		@returns {Object}
		*/

		ResponseProto.json = function(safe) {
			return glow.util.decodeJson(this.text(), {safeMode:safe});
		};

		/**
		@name glow.net.Response#header
		@function
		@description Gets a header from the response

		@param {String} name
			Header name

		@returns {String}
			Header value

		@example var contentType = myResponse.header("Content-Type");
		*/

		ResponseProto.header = function(name) {
			return this.nativeResponse.getResponseHeader(name);
		};

		/**
		@name glow.net.Response#statusText
		@function
		@description Gets the meaning of {@link glow.net.Response#status myResponse.status}

		@returns {String}
		*/
		ResponseProto.statusText = function() {
			return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText;
		};

	glow.net.Response = Response;
});<commit_msg>correction to make docs build right<commit_after>Glow.provide(function(glow) {
	var ResponseProto;

	/**
	@name glow.net.Response
	@class
	@extends glow.events
	@description Provided in callbacks to {@link glow.net.post glow.net.post}, {@link glow.net.get glow.net.get}, {@link glow.net.put glow.net.put} and {@link glow.net.delete glow.net.delete} and glow.net.Requests load and error events.

	@glowPrivateConstructor There is no direct constructor.
	*/

	/*
	These params are hidden as we don't want users to try and create instances of this...

	@param {XMLHttpRequest} nativeResponse
	@param {Boolean} [timedOut=false] Set to true if the response timed out
	@param {glow.net.Request} [request] Original request object
	*/
	function Response(nativeResponse, timedOut, request) {

		/**
		@name glow.net.Response#_request
		@private
		@description Original request object
		@type glow.net.Request
		*/

		this._request = request;

		/**
		@name glow.net.Response#nativeResponse
		@description The response object from the browser.
			This may not have the same properties and methods across user agents.
		@type Object
		*/

		this.nativeResponse = nativeResponse;

		/**
		@name glow.net.Response#status
		@description HTTP status code of the response
		@type Number
		*/

		//IE reports status as 1223 rather than 204, for laffs
		this.status = timedOut ? 408 :
			nativeResponse.status == 1223 ? 204 : nativeResponse.status;

		/**
		@name glow.net.Response#timedOut
		@description Boolean indicating if the requests time out was reached.
		@type boolean
		*/

		this.timedOut = !!timedOut;

		/**
		@name glow.net.Response#wasSuccessful
		@description  Boolean indicating if the request returned successfully.
		@type boolean
		*/

		this.wasSuccessful = (this.status >= 200 && this.status < 300) ||
		//from cache
		this.status == 304 ||
		//watch our for requests from file://
		(this.status == 0 && nativeResponse.responseText);

	}

	glow.util.extend(Response, glow.events.Event);
	ResponseProto = Response.prototype;

		/**
			@name glow.net.Response#text
			@function
			@description Gets the body of the response as plain text
			@returns {string} Response as text
		*/

		ResponseProto.text = function() {
			return this.nativeResponse.responseText;
		};

		/**
		@name glow.net.Response#xml
		@function
		@description Gets the body of the response as xml
		@returns {xml} Response as XML
		*/

		ResponseProto.xml = function() {
			var nativeResponse = this.nativeResponse;

			if (
				// IE fails to recognise the doc as XML in some cases
				( glow.env.ie && shouldParseAsXml.call(this) )
				// If the _forceXml option is set, we need to turn the response text into xml
				|| ( this._request._forceXml && !this._request.nativeRequest.overrideMimeType && window.ActiveXObject )
			) {
				var doc = new ActiveXObject("Microsoft.XMLDOM");
				doc.loadXML( nativeResponse.responseText );
				return doc;
			}
			else {
				// check property exists
				if (!nativeResponse.responseXML) {
					throw new Error('Cannot get response as XML, check the mime type of the data');
				}
				return nativeResponse.responseXML;
			}
		};
		/**
		@private
		@function
		@description Should the response be treated as xml? This function is used by IE only
			'this' is the response object
		@returns {Boolean}
		*/
		function shouldParseAsXml() {
			var contentType = this.header("Content-Type"),
				endsPlusXml = str.slice(-4);
			// IE 6 & 7 fail to recognise Content-Types ending +xml (eg application/rss+xml)
			// Files from the filesystem don't have a content type, but could be xml files, parse them to be safe
			return endsPlusXml.test(contentType) || contentType === '';
		}
		/**
		@name glow.net.Response#json
		@function
		@description Gets the body of the response as a json object

		@param {Boolean} [safeMode=false]
			If true, the response will be parsed using a string parser which
			will filter out non-JSON javascript, this will be slower but
			recommended if you do not trust the data source.

		@returns {Object}
		*/

		ResponseProto.json = function(safe) {
			return glow.util.decodeJson(this.text(), {safeMode:safe});
		};

		/**
		@name glow.net.Response#header
		@function
		@description Gets a header from the response

		@param {String} name
			Header name

		@returns {String}
			Header value

		@example var contentType = myResponse.header("Content-Type");
		*/

		ResponseProto.header = function(name) {
			return this.nativeResponse.getResponseHeader(name);
		};

		/**
		@name glow.net.Response#statusText
		@function
		@description Gets the meaning of {@link glow.net.Response#status myResponse.status}

		@returns {String}
		*/
		ResponseProto.statusText = function() {
			return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText;
		};

	glow.net.Response = Response;
});<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name ng.directive:ngSwitch
 * @restrict EA
 *
 * @description
 * Conditionally change the DOM structure. Elements within ngSwitch but without
 * ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
 * as specified in the template
 *
 * @usageContent
 * <ANY ng-switch-when="matchValue1">...</ANY>
 *   <ANY ng-switch-when="matchValue2">...</ANY>
 *   ...
 *   <ANY ng-switch-default>...</ANY>
 *   <ANY>...</ANY>
 *
 * @scope
 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
 * @paramDescription
 * On child elements add:
 *
 * * `ngSwitchWhen`: the case statement to match against. If match then this
 *   case will be displayed. If the same match appears multiple times, all the
 *   elements will be displayed.
 * * `ngSwitchDefault`: the default case when no other case match. If there
 *   are multiple default cases, all of them will be displayed when no other
 *   case match.
 *
 *
 * @example
    <doc:example>
      <doc:source>

          <select ng-model="selection" ng-options="item for item in items">
          </select>
          <tt>selection={{selection}}</tt>

Settings Div

            Home Span
            default

      </doc:source>
      <doc:scenario>
        it('should start in settings', function() {
         expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
        });
        it('should change to home', function() {
         select('selection').option('home');
         expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
        });
        it('should select default', function() {
         select('selection').option('other');
         expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
        });
      </doc:scenario>
    </doc:example>
 */
var NG_SWITCH = 'ng-switch';
var ngSwitchDirective = valueFn({
  restrict: 'EA',
  require: 'ngSwitch',
  // asks for $scope to fool the BC controller module
  controller: ['$scope', function ngSwitchController() {
    this.cases = {};
  }],
  link: function(scope, element, attr, ctrl) {
    var watchExpr = attr.ngSwitch || attr.on,
        selectedTranscludes,
        selectedElements,
        selectedScopes = [];

    scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
      for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
        selectedScopes[i].$destroy();
        selectedElements[i].remove();
      }

      selectedElements = [];
      selectedScopes = [];

      if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
        scope.$eval(attr.change);
        forEach(selectedTranscludes, function(selectedTransclude) {
          var selectedScope = scope.$new();
          selectedScopes.push(selectedScope);
          selectedTransclude.transclude(selectedScope, function(caseElement) {
            selectedElements.push(caseElement);
            selectedTransclude.element.after(caseElement);
          });
        });
      }
    });
  }
});

var ngSwitchWhenDirective = ngDirective({
  transclude: 'element',
  priority: 500,
  require: '^ngSwitch',
  compile: function(element, attrs, transclude) {
    return function(scope, element, attr, ctrl) {
      ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
      ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element });
    };
  }
});

var ngSwitchDefaultDirective = ngDirective({
  transclude: 'element',
  priority: 500,
  require: '^ngSwitch',
  compile: function(element, attrs, transclude) {
    return function(scope, element, attr, ctrl) {
      ctrl.cases['?'] = (ctrl.cases['?'] || []);
      ctrl.cases['?'].push({ transclude: transclude, element: element });
    };
  }
});
<commit_msg>docs(ngSwitch): improve the @usage example<commit_after>'use strict';

/**
 * @ngdoc directive
 * @name ng.directive:ngSwitch
 * @restrict EA
 *
 * @description
 * Conditionally change the DOM structure. Elements within ngSwitch but without
 * ngSwitchWhen or ngSwitchDefault directives will be preserved at the location
 * as specified in the template
 *
 * @usage
 * <ANY ng-switch="expression">
 *   <ANY ng-switch-when="matchValue1">...</ANY>
 *   <ANY ng-switch-when="matchValue2">...</ANY>
 *   ...
 *   <ANY ng-switch-default>...</ANY>
 * </ANY>
 *
 * @scope
 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
 * @paramDescription
 * On child elements add:
 *
 * * `ngSwitchWhen`: the case statement to match against. If match then this
 *   case will be displayed. If the same match appears multiple times, all the
 *   elements will be displayed.
 * * `ngSwitchDefault`: the default case when no other case match. If there
 *   are multiple default cases, all of them will be displayed when no other
 *   case match.
 *
 *
 * @example
    <doc:example>
      <doc:source>

          <select ng-model="selection" ng-options="item for item in items">
          </select>
          <tt>selection={{selection}}</tt>

Settings Div

            Home Span
            default

      </doc:source>
      <doc:scenario>
        it('should start in settings', function() {
         expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/);
        });
        it('should change to home', function() {
         select('selection').option('home');
         expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/);
        });
        it('should select default', function() {
         select('selection').option('other');
         expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/);
        });
      </doc:scenario>
    </doc:example>
 */
var NG_SWITCH = 'ng-switch';
var ngSwitchDirective = valueFn({
  restrict: 'EA',
  require: 'ngSwitch',
  // asks for $scope to fool the BC controller module
  controller: ['$scope', function ngSwitchController() {
    this.cases = {};
  }],
  link: function(scope, element, attr, ctrl) {
    var watchExpr = attr.ngSwitch || attr.on,
        selectedTranscludes,
        selectedElements,
        selectedScopes = [];

    scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
      for (var i= 0, ii=selectedScopes.length; i<ii; i++) {
        selectedScopes[i].$destroy();
        selectedElements[i].remove();
      }

      selectedElements = [];
      selectedScopes = [];

      if ((selectedTranscludes = ctrl.cases['!' + value] || ctrl.cases['?'])) {
        scope.$eval(attr.change);
        forEach(selectedTranscludes, function(selectedTransclude) {
          var selectedScope = scope.$new();
          selectedScopes.push(selectedScope);
          selectedTransclude.transclude(selectedScope, function(caseElement) {
            selectedElements.push(caseElement);
            selectedTransclude.element.after(caseElement);
          });
        });
      }
    });
  }
});

var ngSwitchWhenDirective = ngDirective({
  transclude: 'element',
  priority: 500,
  require: '^ngSwitch',
  compile: function(element, attrs, transclude) {
    return function(scope, element, attr, ctrl) {
      ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
      ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: transclude, element: element });
    };
  }
});

var ngSwitchDefaultDirective = ngDirective({
  transclude: 'element',
  priority: 500,
  require: '^ngSwitch',
  compile: function(element, attrs, transclude) {
    return function(scope, element, attr, ctrl) {
      ctrl.cases['?'] = (ctrl.cases['?'] || []);
      ctrl.cases['?'].push({ transclude: transclude, element: element });
    };
  }
});
<|endoftext|>


<commit_before>const fs = require('fs')
const path = require('path')
const tus = require('tus-js-client')
const uuid = require('uuid')
const createTailReadStream = require('@uppy/fs-tail-stream')
const emitter = require('./emitter')
const request = require('request')
const serializeError = require('serialize-error')
const { jsonStringify, hasMatch } = require('./helpers/utils')
const logger = require('./logger')
const validator = require('validator')
const headerSanitize = require('./header-blacklist')

const PROTOCOLS = Object.freeze({
  multipart: 'multipart',
  s3Multipart: 's3-multipart',
  tus: 'tus'
})

class Uploader {
  /**
   * Uploads file to destination based on the supplied protocol (tus, s3-multipart, multipart)
   * For tus uploads, the deferredLength option is enabled, because file size value can be unreliable
   * for some providers (Instagram particularly)
   *
   * @typedef {object} UploaderOptions
   * @property {string} endpoint
   * @property {string=} uploadUrl
   * @property {string} protocol
   * @property {number} size
   * @property {string=} fieldname
   * @property {string} pathPrefix
   * @property {any=} s3
   * @property {any} metadata
   * @property {any} uppyOptions
   * @property {any=} storage
   * @property {any=} headers
   *
   * @param {UploaderOptions} options
   */
  constructor (options) {
    if (!this.validateOptions(options)) {
      logger.debug(this._errRespMessage, 'uploader.validator.fail')
      return
    }

    this.options = options
    this.token = uuid.v4()
    this.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
    this.metadata = Object.assign({}, this.options.metadata || {})
    this.metadata.filename = this.metadata.name || path.basename(this.path)
    this.metadata.filetype = this.metadata.type
    this.streamsEnded = false
    this.duplexStream = null
    // @TODO disabling parallel uploads and downloads for now
    // if (this.options.protocol === PROTOCOLS.tus) {
    //   this.duplexStream = new stream.PassThrough()
    //     .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
    // }
    this.writeStream = fs.createWriteStream(this.path, { mode: 0o666 }) // no executable files
      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
    /** @type {number} */
    this.emittedProgress = 0
    this.storage = options.storage
    this._paused = false

    if (this.options.protocol === PROTOCOLS.tus) {
      emitter().on(`pause:${this.token}`, () => {
        this._paused = true
        if (this.tus) {
          this.tus.abort()
        }
      })

      emitter().on(`resume:${this.token}`, () => {
        this._paused = false
        if (this.tus) {
          this.tus.start()
        }
      })
    }
  }

  /**
   * the number of bytes written into the streams
   */
  get bytesWritten () {
    return this.writeStream.bytesWritten
  }

  /**
   * Validate the options passed down to the uplaoder
   *
   * @param {UploaderOptions} options
   * @returns {boolean}
   */
  validateOptions (options) {
    // s3 uploads don't require upload destination
    // validation, because the destination is determined
    // by the server's s3 config
    if (options.protocol === PROTOCOLS.s3Multipart) {
      return true
    }

    if (!options.endpoint && !options.uploadUrl) {
      this._errRespMessage = 'No destination specified'
      return false
    }

    const validatorOpts = { require_protocol: true, require_tld: !options.uppyOptions.debug }
    return [options.endpoint, options.uploadUrl].every((url) => {
      if (url && !validator.isURL(url, validatorOpts)) {
        this._errRespMessage = 'Invalid destination url'
        return false
      }

      const allowedUrls = options.uppyOptions.uploadUrls
      if (allowedUrls && url && !hasMatch(url, allowedUrls)) {
        this._errRespMessage = 'upload destination does not match any allowed destinations'
        return false
      }

      return true
    })
  }

  hasError () {
    return this._errRespMessage != null
  }

  /**
   * returns a substring of the token
   */
  get shortToken () {
    return this.token.substring(0, 8)
  }

  /**
   *
   * @param {function} callback
   */
  onSocketReady (callback) {
    emitter().once(`connection:${this.token}`, () => callback())
    logger.debug(`${this.shortToken} waiting for connection`, 'uploader.socket.wait')
  }

  cleanUp () {
    fs.unlink(this.path, (err) => {
      if (err) {
        logger.error(`cleanup failed for: ${this.path} err: ${err}`, 'uploader.cleanup.error')
      }
    })
    emitter().removeAllListeners(`pause:${this.token}`)
    emitter().removeAllListeners(`resume:${this.token}`)
  }

  /**
   *
   * @param {Buffer | Buffer[]} chunk
   */
  handleChunk (chunk) {
    // @todo a default protocol should not be set. We should ensure that the user specifies her protocol.
    const protocol = this.options.protocol || PROTOCOLS.multipart

    // The download has completed; close the file and start an upload if necessary.
    if (chunk === null) {
      this.writeStream.on('finish', () => {
        this.streamsEnded = true
        if (this.options.endpoint && protocol === PROTOCOLS.multipart) {
          this.uploadMultipart()
        }

        if (protocol === PROTOCOLS.tus && !this.tus) {
          return this.uploadTus()
        }
      })

      return this.endStreams()
    }

    this.writeStream.write(chunk, () => {
      logger.debug(`${this.shortToken} ${this.bytesWritten} bytes`, 'uploader.download.progress')
      if (protocol === PROTOCOLS.multipart || protocol === PROTOCOLS.tus) {
        return this.emitIllusiveProgress()
      }

      if (protocol === PROTOCOLS.s3Multipart && !this.s3Upload) {
        return this.uploadS3()
      }
      // @TODO disabling parallel uploads and downloads for now
      // if (!this.options.endpoint) return

      // if (protocol === PROTOCOLS.tus && !this.tus) {
      //   return this.uploadTus()
      // }
    })
  }

  /**
   * @param {Buffer | Buffer[]} chunk
   * @param {function} cb
   */
  writeToStreams (chunk, cb) {
    const done = []
    const doneLength = this.duplexStream ? 2 : 1
    const onDone = () => {
      done.push(true)
      if (done.length >= doneLength) {
        cb()
      }
    }

    this.writeStream.write(chunk, onDone)
    if (this.duplexStream) {
      this.duplexStream.write(chunk, onDone)
    }
  }

  endStreams () {
    this.writeStream.end()
    if (this.duplexStream) {
      this.duplexStream.end()
    }
  }

  getResponse () {
    if (this._errRespMessage) {
      return { body: this._errRespMessage, status: 400 }
    }
    return { body: { token: this.token }, status: 200 }
  }

  /**
   * @typedef {{action: string, payload: object}} State
   * @param {State} state
   */
  saveState (state) {
    if (!this.storage) return
    this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
  }

  /**
   * This method emits upload progress but also creates an "upload progress" illusion
   * for the waiting period while only download is happening. Hence, it combines both
   * download and upload into an upload progress.
   * @see emitProgress
   * @param {number=} bytesUploaded the bytes actually Uploaded so far
   */
  emitIllusiveProgress (bytesUploaded) {
    if (this._paused) {
      return
    }

    let bytesTotal = this.streamsEnded ? this.bytesWritten : this.options.size
    if (!this.streamsEnded) {
      bytesTotal = Math.max(bytesTotal, this.bytesWritten)
    }
    bytesUploaded = bytesUploaded || 0
    // for a 10MB file, 10MB of download will account for 5MB upload progress
    // and 10MB of actual upload will account for the other 5MB upload progress.
    const illusiveBytesUploaded = (this.bytesWritten / 2) + (bytesUploaded / 2)

    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${illusiveBytesUploaded} ${bytesTotal}`,
      'uploader.illusive.progress'
    )
    this.emitProgress(illusiveBytesUploaded, bytesTotal)
  }

  /**
   *
   * @param {number} bytesUploaded
   * @param {number | null} bytesTotal
   */
  emitProgress (bytesUploaded, bytesTotal) {
    bytesTotal = bytesTotal || this.options.size
    if (this.tus && this.tus.options.uploadLengthDeferred && this.streamsEnded) {
      bytesTotal = this.bytesWritten
    }
    const percentage = (bytesUploaded / bytesTotal * 100)
    const formatPercentage = percentage.toFixed(2)
    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${bytesTotal} ${formatPercentage}%`,
      'uploader.upload.progress'
    )

    const dataToEmit = {
      action: 'progress',
      payload: { progress: formatPercentage, bytesUploaded, bytesTotal }
    }
    this.saveState(dataToEmit)

    // avoid flooding the client with progress events.
    const roundedPercentage = Math.floor(percentage)
    if (this.emittedProgress !== roundedPercentage) {
      this.emittedProgress = roundedPercentage
      emitter().emit(this.token, dataToEmit)
    }
  }

  /**
   *
   * @param {string} url
   * @param {object} extraData
   */
  emitSuccess (url, extraData = {}) {
    const emitData = {
      action: 'success',
      payload: Object.assign(extraData, { complete: true, url })
    }
    this.saveState(emitData)
    emitter().emit(this.token, emitData)
  }

  /**
   *
   * @param {Error} err
   * @param {object=} extraData
   */
  emitError (err, extraData = {}) {
    const dataToEmit = {
      action: 'error',
      // TODO: consider removing the stack property
      payload: Object.assign(extraData, { error: serializeError(err) })
    }
    this.saveState(dataToEmit)
    emitter().emit(this.token, dataToEmit)
  }

  /**
   * start the tus upload
   */
  uploadTus () {
    const file = fs.createReadStream(this.path)
    const uploader = this

    // @ts-ignore
    this.tus = new tus.Upload(file, {
      endpoint: this.options.endpoint,
      uploadUrl: this.options.uploadUrl,
      // @ts-ignore
      uploadLengthDeferred: false,
      resume: true,
      retryDelays: [0, 1000, 3000, 5000],
      uploadSize: this.bytesWritten,
      metadata: this.metadata,
      /**
       *
       * @param {Error} error
       */
      onError (error) {
        logger.error(error, 'uploader.tus.error')
        uploader.emitError(error)
      },
      /**
       *
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onProgress (bytesUploaded, bytesTotal) {
        uploader.emitIllusiveProgress(bytesUploaded)
      },
      onSuccess () {
        uploader.emitSuccess(uploader.tus.url)
        uploader.cleanUp()
      }
    })

    if (!this._paused) {
      this.tus.start()
    }
  }

  uploadMultipart () {
    const file = fs.createReadStream(this.path)

    // upload progress
    let bytesUploaded = 0
    file.on('data', (data) => {
      bytesUploaded += data.length
      this.emitIllusiveProgress(bytesUploaded)
    })

    const formData = Object.assign(
      {},
      this.options.metadata,
      {
        [this.options.fieldname]: {
          value: file,
          options: {
            filename: this.metadata.filename,
            contentType: this.metadata.filetype
          }
        }
      }
    )
    const headers = headerSanitize(this.options.headers)
    request.post({ url: this.options.endpoint, headers, formData, encoding: null }, (error, response, body) => {
      if (error) {
        logger.error(error, 'upload.multipart.error')
        this.emitError(error)
        return
      }
      const headers = response.headers
      // remove browser forbidden headers
      delete headers['set-cookie']
      delete headers['set-cookie2']

      const respObj = {
        responseText: body.toString(),
        status: response.statusCode,
        statusText: response.statusMessage,
        headers
      }

      if (response.statusCode >= 400) {
        logger.error(`upload failed with status: ${response.statusCode}`, 'upload.multipart.error')
        this.emitError(new Error(response.statusMessage), respObj)
      } else if (bytesUploaded !== this.bytesWritten && bytesUploaded !== this.options.size) {
        const errMsg = `uploaded only ${bytesUploaded} of ${this.bytesWritten} with status: ${response.statusCode}`
        logger.error(errMsg, 'upload.multipart.mismatch.error')
        this.emitError(new Error(errMsg))
      } else {
        this.emitSuccess(null, { response: respObj })
      }

      this.cleanUp()
    })
  }

  /**
   * Upload the file to S3 while it is still being downloaded.
   */
  uploadS3 () {
    const file = createTailReadStream(this.path, {
      tail: true
    })

    this.writeStream.on('finish', () => {
      file.close()
    })

    return this._uploadS3(file)
  }

  /**
   * Upload a stream to S3.
   */
  _uploadS3 (stream) {
    if (!this.options.s3) {
      this.emitError(new Error('The S3 client is not configured on this companion instance.'))
      return
    }

    const filename = this.options.metadata.filename || path.basename(this.path)
    const { client, options } = this.options.s3

    const upload = client.upload({
      Bucket: options.bucket,
      Key: options.getKey(null, filename),
      ACL: options.acl,
      ContentType: this.options.metadata.type,
      Body: stream
    })

    this.s3Upload = upload

    upload.on('httpUploadProgress', ({ loaded, total }) => {
      this.emitProgress(loaded, total)
    })

    upload.send((error, data) => {
      this.s3Upload = null
      if (error) {
        this.emitError(error)
      } else {
        this.emitSuccess(null, {
          response: {
            responseText: JSON.stringify(data),
            headers: {
              'content-type': 'application/json'
            }
          }
        })
      }
      this.cleanUp()
    })
  }
}

Uploader.FILE_NAME_PREFIX = 'uppy-file'
Uploader.STORAGE_PREFIX = 'companion'

module.exports = Uploader
<commit_msg>companion: refactor metadata handling<commit_after>const fs = require('fs')
const path = require('path')
const tus = require('tus-js-client')
const uuid = require('uuid')
const createTailReadStream = require('@uppy/fs-tail-stream')
const emitter = require('./emitter')
const request = require('request')
const serializeError = require('serialize-error')
const { jsonStringify, hasMatch } = require('./helpers/utils')
const logger = require('./logger')
const validator = require('validator')
const headerSanitize = require('./header-blacklist')

const PROTOCOLS = Object.freeze({
  multipart: 'multipart',
  s3Multipart: 's3-multipart',
  tus: 'tus'
})

class Uploader {
  /**
   * Uploads file to destination based on the supplied protocol (tus, s3-multipart, multipart)
   * For tus uploads, the deferredLength option is enabled, because file size value can be unreliable
   * for some providers (Instagram particularly)
   *
   * @typedef {object} UploaderOptions
   * @property {string} endpoint
   * @property {string=} uploadUrl
   * @property {string} protocol
   * @property {number} size
   * @property {string=} fieldname
   * @property {string} pathPrefix
   * @property {any=} s3
   * @property {any} metadata
   * @property {any} uppyOptions
   * @property {any=} storage
   * @property {any=} headers
   *
   * @param {UploaderOptions} options
   */
  constructor (options) {
    if (!this.validateOptions(options)) {
      logger.debug(this._errRespMessage, 'uploader.validator.fail')
      return
    }

    this.options = options
    this.token = uuid.v4()
    this.path = `${this.options.pathPrefix}/${Uploader.FILE_NAME_PREFIX}-${this.token}`
    this.options.metadata = this.options.metadata || {}
    this.uploadFileName = this.options.metadata.name || path.basename(this.path)
    this.streamsEnded = false
    this.duplexStream = null
    // @TODO disabling parallel uploads and downloads for now
    // if (this.options.protocol === PROTOCOLS.tus) {
    //   this.duplexStream = new stream.PassThrough()
    //     .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.duplex.error'))
    // }
    this.writeStream = fs.createWriteStream(this.path, { mode: 0o666 }) // no executable files
      .on('error', (err) => logger.error(`${this.shortToken} ${err}`, 'uploader.write.error'))
    /** @type {number} */
    this.emittedProgress = 0
    this.storage = options.storage
    this._paused = false

    if (this.options.protocol === PROTOCOLS.tus) {
      emitter().on(`pause:${this.token}`, () => {
        this._paused = true
        if (this.tus) {
          this.tus.abort()
        }
      })

      emitter().on(`resume:${this.token}`, () => {
        this._paused = false
        if (this.tus) {
          this.tus.start()
        }
      })
    }
  }

  /**
   * the number of bytes written into the streams
   */
  get bytesWritten () {
    return this.writeStream.bytesWritten
  }

  /**
   * Validate the options passed down to the uplaoder
   *
   * @param {UploaderOptions} options
   * @returns {boolean}
   */
  validateOptions (options) {
    // s3 uploads don't require upload destination
    // validation, because the destination is determined
    // by the server's s3 config
    if (options.protocol === PROTOCOLS.s3Multipart) {
      return true
    }

    if (!options.endpoint && !options.uploadUrl) {
      this._errRespMessage = 'No destination specified'
      return false
    }

    const validatorOpts = { require_protocol: true, require_tld: !options.uppyOptions.debug }
    return [options.endpoint, options.uploadUrl].every((url) => {
      if (url && !validator.isURL(url, validatorOpts)) {
        this._errRespMessage = 'Invalid destination url'
        return false
      }

      const allowedUrls = options.uppyOptions.uploadUrls
      if (allowedUrls && url && !hasMatch(url, allowedUrls)) {
        this._errRespMessage = 'upload destination does not match any allowed destinations'
        return false
      }

      return true
    })
  }

  hasError () {
    return this._errRespMessage != null
  }

  /**
   * returns a substring of the token
   */
  get shortToken () {
    return this.token.substring(0, 8)
  }

  /**
   *
   * @param {function} callback
   */
  onSocketReady (callback) {
    emitter().once(`connection:${this.token}`, () => callback())
    logger.debug(`${this.shortToken} waiting for connection`, 'uploader.socket.wait')
  }

  cleanUp () {
    fs.unlink(this.path, (err) => {
      if (err) {
        logger.error(`cleanup failed for: ${this.path} err: ${err}`, 'uploader.cleanup.error')
      }
    })
    emitter().removeAllListeners(`pause:${this.token}`)
    emitter().removeAllListeners(`resume:${this.token}`)
  }

  /**
   *
   * @param {Buffer | Buffer[]} chunk
   */
  handleChunk (chunk) {
    // @todo a default protocol should not be set. We should ensure that the user specifies her protocol.
    const protocol = this.options.protocol || PROTOCOLS.multipart

    // The download has completed; close the file and start an upload if necessary.
    if (chunk === null) {
      this.writeStream.on('finish', () => {
        this.streamsEnded = true
        if (this.options.endpoint && protocol === PROTOCOLS.multipart) {
          this.uploadMultipart()
        }

        if (protocol === PROTOCOLS.tus && !this.tus) {
          return this.uploadTus()
        }
      })

      return this.endStreams()
    }

    this.writeStream.write(chunk, () => {
      logger.debug(`${this.shortToken} ${this.bytesWritten} bytes`, 'uploader.download.progress')
      if (protocol === PROTOCOLS.multipart || protocol === PROTOCOLS.tus) {
        return this.emitIllusiveProgress()
      }

      if (protocol === PROTOCOLS.s3Multipart && !this.s3Upload) {
        return this.uploadS3()
      }
      // @TODO disabling parallel uploads and downloads for now
      // if (!this.options.endpoint) return

      // if (protocol === PROTOCOLS.tus && !this.tus) {
      //   return this.uploadTus()
      // }
    })
  }

  /**
   * @param {Buffer | Buffer[]} chunk
   * @param {function} cb
   */
  writeToStreams (chunk, cb) {
    const done = []
    const doneLength = this.duplexStream ? 2 : 1
    const onDone = () => {
      done.push(true)
      if (done.length >= doneLength) {
        cb()
      }
    }

    this.writeStream.write(chunk, onDone)
    if (this.duplexStream) {
      this.duplexStream.write(chunk, onDone)
    }
  }

  endStreams () {
    this.writeStream.end()
    if (this.duplexStream) {
      this.duplexStream.end()
    }
  }

  getResponse () {
    if (this._errRespMessage) {
      return { body: this._errRespMessage, status: 400 }
    }
    return { body: { token: this.token }, status: 200 }
  }

  /**
   * @typedef {{action: string, payload: object}} State
   * @param {State} state
   */
  saveState (state) {
    if (!this.storage) return
    this.storage.set(`${Uploader.STORAGE_PREFIX}:${this.token}`, jsonStringify(state))
  }

  /**
   * This method emits upload progress but also creates an "upload progress" illusion
   * for the waiting period while only download is happening. Hence, it combines both
   * download and upload into an upload progress.
   * @see emitProgress
   * @param {number=} bytesUploaded the bytes actually Uploaded so far
   */
  emitIllusiveProgress (bytesUploaded) {
    if (this._paused) {
      return
    }

    let bytesTotal = this.streamsEnded ? this.bytesWritten : this.options.size
    if (!this.streamsEnded) {
      bytesTotal = Math.max(bytesTotal, this.bytesWritten)
    }
    bytesUploaded = bytesUploaded || 0
    // for a 10MB file, 10MB of download will account for 5MB upload progress
    // and 10MB of actual upload will account for the other 5MB upload progress.
    const illusiveBytesUploaded = (this.bytesWritten / 2) + (bytesUploaded / 2)

    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${illusiveBytesUploaded} ${bytesTotal}`,
      'uploader.illusive.progress'
    )
    this.emitProgress(illusiveBytesUploaded, bytesTotal)
  }

  /**
   *
   * @param {number} bytesUploaded
   * @param {number | null} bytesTotal
   */
  emitProgress (bytesUploaded, bytesTotal) {
    bytesTotal = bytesTotal || this.options.size
    if (this.tus && this.tus.options.uploadLengthDeferred && this.streamsEnded) {
      bytesTotal = this.bytesWritten
    }
    const percentage = (bytesUploaded / bytesTotal * 100)
    const formatPercentage = percentage.toFixed(2)
    logger.debug(
      `${this.shortToken} ${bytesUploaded} ${bytesTotal} ${formatPercentage}%`,
      'uploader.upload.progress'
    )

    const dataToEmit = {
      action: 'progress',
      payload: { progress: formatPercentage, bytesUploaded, bytesTotal }
    }
    this.saveState(dataToEmit)

    // avoid flooding the client with progress events.
    const roundedPercentage = Math.floor(percentage)
    if (this.emittedProgress !== roundedPercentage) {
      this.emittedProgress = roundedPercentage
      emitter().emit(this.token, dataToEmit)
    }
  }

  /**
   *
   * @param {string} url
   * @param {object} extraData
   */
  emitSuccess (url, extraData = {}) {
    const emitData = {
      action: 'success',
      payload: Object.assign(extraData, { complete: true, url })
    }
    this.saveState(emitData)
    emitter().emit(this.token, emitData)
  }

  /**
   *
   * @param {Error} err
   * @param {object=} extraData
   */
  emitError (err, extraData = {}) {
    const dataToEmit = {
      action: 'error',
      // TODO: consider removing the stack property
      payload: Object.assign(extraData, { error: serializeError(err) })
    }
    this.saveState(dataToEmit)
    emitter().emit(this.token, dataToEmit)
  }

  /**
   * start the tus upload
   */
  uploadTus () {
    const file = fs.createReadStream(this.path)
    const uploader = this

    // @ts-ignore
    this.tus = new tus.Upload(file, {
      endpoint: this.options.endpoint,
      uploadUrl: this.options.uploadUrl,
      // @ts-ignore
      uploadLengthDeferred: false,
      resume: true,
      retryDelays: [0, 1000, 3000, 5000],
      uploadSize: this.bytesWritten,
      metadata: Object.assign(
        {
          // file name and type as specified by tus protocol
          filename: this.uploadFileName,
          filetype: this.options.metadata.type
        }, this.options.metadata
      ),
      /**
       *
       * @param {Error} error
       */
      onError (error) {
        logger.error(error, 'uploader.tus.error')
        uploader.emitError(error)
      },
      /**
       *
       * @param {number} bytesUploaded
       * @param {number} bytesTotal
       */
      onProgress (bytesUploaded, bytesTotal) {
        uploader.emitIllusiveProgress(bytesUploaded)
      },
      onSuccess () {
        uploader.emitSuccess(uploader.tus.url)
        uploader.cleanUp()
      }
    })

    if (!this._paused) {
      this.tus.start()
    }
  }

  uploadMultipart () {
    const file = fs.createReadStream(this.path)

    // upload progress
    let bytesUploaded = 0
    file.on('data', (data) => {
      bytesUploaded += data.length
      this.emitIllusiveProgress(bytesUploaded)
    })

    const formData = Object.assign(
      {},
      this.options.metadata,
      {
        [this.options.fieldname]: {
          value: file,
          options: {
            filename: this.uploadFileName,
            contentType: this.options.metadata.type
          }
        }
      }
    )
    const headers = headerSanitize(this.options.headers)
    request.post({ url: this.options.endpoint, headers, formData, encoding: null }, (error, response, body) => {
      if (error) {
        logger.error(error, 'upload.multipart.error')
        this.emitError(error)
        return
      }
      const headers = response.headers
      // remove browser forbidden headers
      delete headers['set-cookie']
      delete headers['set-cookie2']

      const respObj = {
        responseText: body.toString(),
        status: response.statusCode,
        statusText: response.statusMessage,
        headers
      }

      if (response.statusCode >= 400) {
        logger.error(`upload failed with status: ${response.statusCode}`, 'upload.multipart.error')
        this.emitError(new Error(response.statusMessage), respObj)
      } else if (bytesUploaded !== this.bytesWritten && bytesUploaded !== this.options.size) {
        const errMsg = `uploaded only ${bytesUploaded} of ${this.bytesWritten} with status: ${response.statusCode}`
        logger.error(errMsg, 'upload.multipart.mismatch.error')
        this.emitError(new Error(errMsg))
      } else {
        this.emitSuccess(null, { response: respObj })
      }

      this.cleanUp()
    })
  }

  /**
   * Upload the file to S3 while it is still being downloaded.
   */
  uploadS3 () {
    const file = createTailReadStream(this.path, {
      tail: true
    })

    this.writeStream.on('finish', () => {
      file.close()
    })

    return this._uploadS3(file)
  }

  /**
   * Upload a stream to S3.
   */
  _uploadS3 (stream) {
    if (!this.options.s3) {
      this.emitError(new Error('The S3 client is not configured on this companion instance.'))
      return
    }

    const filename = this.options.metadata.filename || path.basename(this.path)
    const { client, options } = this.options.s3

    const upload = client.upload({
      Bucket: options.bucket,
      Key: options.getKey(null, filename),
      ACL: options.acl,
      ContentType: this.options.metadata.type,
      Body: stream
    })

    this.s3Upload = upload

    upload.on('httpUploadProgress', ({ loaded, total }) => {
      this.emitProgress(loaded, total)
    })

    upload.send((error, data) => {
      this.s3Upload = null
      if (error) {
        this.emitError(error)
      } else {
        this.emitSuccess(null, {
          response: {
            responseText: JSON.stringify(data),
            headers: {
              'content-type': 'application/json'
            }
          }
        })
      }
      this.cleanUp()
    })
  }
}

Uploader.FILE_NAME_PREFIX = 'uppy-file'
Uploader.STORAGE_PREFIX = 'companion'

module.exports = Uploader
<|endoftext|>


<commit_before>const exec = require('child_process').exec;

const AUDIT_EXCEPTIONS = [
  'handlebars',
  'decompress',
  'http-proxy',
  'elliptic',
  'serialize-javascript',
  'bl',
  'cryptiles',
  'axios',
  'y18n',
  'xmlhttprequest-ssl',
  'lodash'
];

const execute = (command, callback) => {
  exec(
    command,
    {
      maxBuffer: 2000 * 4096
    },
    (error, stdout, stderr) => {
      callback(stdout);
    }
  );
};
execute('npm audit --json', json => {
  const advisories = JSON.parse(json).advisories;
  if (!advisories) {
    console.info('Most likely npm audit is unavailable', json);
    process.exit(0);
  }
  let auditPass = true;
  for (const id in advisories) {
    if (
      advisories[id].severity === 'high' &&
      !AUDIT_EXCEPTIONS.includes(advisories[id].module_name)
    ) {
      console.error('AUDIT Failed', advisories[id]);
      auditPass = false;
    }
  }
  if (!auditPass) process.exit(1);
});
<commit_msg>devop: add underscore to audit<commit_after>const exec = require('child_process').exec;

const AUDIT_EXCEPTIONS = [
  'handlebars',
  'decompress',
  'http-proxy',
  'elliptic',
  'serialize-javascript',
  'bl',
  'cryptiles',
  'axios',
  'y18n',
  'xmlhttprequest-ssl',
  'lodash',
  'underscore'
];

const execute = (command, callback) => {
  exec(
    command,
    {
      maxBuffer: 2000 * 4096
    },
    (error, stdout, stderr) => {
      callback(stdout);
    }
  );
};
execute('npm audit --json', json => {
  const advisories = JSON.parse(json).advisories;
  if (!advisories) {
    console.info('Most likely npm audit is unavailable', json);
    process.exit(0);
  }
  let auditPass = true;
  for (const id in advisories) {
    if (
      advisories[id].severity === 'high' &&
      !AUDIT_EXCEPTIONS.includes(advisories[id].module_name)
    ) {
      console.error('AUDIT Failed', advisories[id]);
      auditPass = false;
    }
  }
  if (!auditPass) process.exit(1);
});
<|endoftext|>