<commit_before>(function(d3) {

  // obtains element computed style
  // context is chart
  function _style(attr) {
    var style,
      element = this.base[0][0];
    if (window.getComputedStyle) {
      style = window.getComputedStyle(element);
    } else if (element.currentStyle) {
      style = element.currentStyle;
    }

    if (!attr) {
      return style;
    } else {
      return style[attr];
    }
  }

  // converts pixel values
  var _toNumFromPx = (function() {
    var rx = /px$/;
    return function(value) {
      if (rx.test(value)) {
        return +(value.replace(rx, ""));
      } else {
        return value;
      }
    };
  }());

  // helper attribute setter on chart base.
  // context is chart
  function _initAttr(internalName, d3Name, defaultValue) {
    var current = _toNumFromPx(_style.call(this, d3Name));

    if (current === null || current === 0 || current === "") {
      this[internalName] = defaultValue;
      this.base.attr(d3Name, defaultValue);
    } else {
      this[internalName] = _toNumFromPx(_style.call(this, d3Name));
    }
  }

  // Borrowed from Underscore.js 1.5.2
  //     http://underscorejs.org
  //     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  //     Underscore may be freely distributed under the MIT license.
  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  function _debounce(func, wait, immediate) {
    var timeout, args, context, timestamp, result;
    return function() {
      context = this;
      args = arguments;
      timestamp = new Date();
      var later = function() {
        var last = (new Date()) - timestamp;
        if (last < wait) {
          timeout = setTimeout(later, wait - last);
        } else {
          timeout = null;
          if (!immediate) result = func.apply(context, args);
        }
      };
      var callNow = immediate && !timeout;
      if (!timeout) {
        timeout = setTimeout(later, wait);
      }
      if (callNow) result = func.apply(context, args);
      return result;
    };
  }

  // go over existing modes and determine which we are in
  // returns true if a mode change occured, false otherwise.
  function _determineMode() {
    var oldMode = this._currentMode;
    this._currentMode = null;

    if ("modes" in this) {
      var result = false;
      for (var mode in this._modes) {
        result = this._modes[mode].call(this);
        if (result) {
          this._currentMode = mode;
          break;
        }
      }
    }
    return oldMode !== this._currentMode;
  }

  // takes care of removing/adding appropriate layers
  function _onModeChange() {

    var chart = this;

    for(var layerName in chart._layersArguments) {

      // is this chart in the current mode?
      var layerArgs = chart._layersArguments[layerName];

      // if this layer should not exist in the current mode
      // unlayer it and then save it so we can reattach it
      // later.
      if (layerArgs.options.modes.indexOf(chart.mode()) === -1) {

        // is it showing?
        if (layerArgs.showing === true) {

          // nope? remove it.
          var removedLayer = chart.unlayer(layerName);
          removedLayer.style("display","none");
          chart._layersArguments[layerName].showing = false;
          chart._layersArguments[layerName].layer = removedLayer;
        }

      } else {

        // this layer is not showing, we need to add it
        if (chart._layersArguments[layerName].showing === false) {

          // if the layer has already been created, just re-add it
          if (chart._layersArguments[layerName].layer !== null) {
            oldLayer.call(chart, layerName, chart._layersArguments[layerName].layer);
            chart._layersArguments[layerName].layer.style("display","inline");
          } else {

            // this layer must not have been drawn in the initial rendering
            // but we do have the arguments, so render it using the
            // old layering.
            oldLayer.call(chart,
              chart._layersArguments[layerName].name,
              chart._layersArguments[layerName].selection,
              chart._layersArguments[layerName].options);
          }

          chart._layersArguments[layerName].showing = true;
        }
      }
    }
  }

  var BaseChart = d3.chart("BaseChart", {
    initialize: function() {

      var chart = this;
      // layer structures container - for layers that are
      // created on initialize but do not actually need to
      // be rendered in the detected mode, we need to save the
      // actual arguments so that we can construct it later.
      this._layersArguments = {};

      // save mode functions
      this._modes = this.modes || {};
      delete this.modes;

      // store layers referenced per mode
      this._modeLayers = {};

      // determine current mode on initialization
      _determineMode.call(this);

      // setup some reasonable defaults
      chart._width  = _toNumFromPx(_style.call(chart, "width")) || 200;
      chart._height = _toNumFromPx(_style.call(chart, "height")) || 200;

      // make sure container height and width are set.
      _initAttr.call(this, "_width", "width", 200);
      _initAttr.call(this, "_height", "height", 200);

      // bind to winow resize end
      window.addEventListener("resize", _debounce(function() {

        chart._width  = _toNumFromPx(_style.call(chart, "width")) || 200;
        chart._height = _toNumFromPx(_style.call(chart, "height")) || 200;

        // update current mode
        var changed = _determineMode.call(chart);
        if (changed) {
          chart.trigger("mode:change", this._currentMode);
        }

        // only redraw if there is data
        if (chart.data) {
          chart.draw(chart.data);
        }

      }, 150));

      window.addEventListener("orientationchange", function() {
        // redraw on device rotation
        chart.trigger("mode:change", this._currentMode);

        // only redraw if there is data
        if (chart.data) {
          chart.draw(chart.data);
        }
      }, false);

      // on mode change, update height and width, and redraw
      // the chart
      chart.on("mode:change", function() {

        // sort out current mode
        _onModeChange.call(chart);

      });
    },

    // returns current mode
    mode : function() {
      return this._currentMode;
    },

    width: function(newWidth) {
      if (arguments.length === 0) {
        if (this._width && !isNaN(+this._width)) {
          return this._width;
        } else {
          return _toNumFromPx(_style.call(this, "width"));
        }
      }

      var oldWidth = this._width;

      this._width = newWidth;

      // only if the width actually changed:
      if (this._width !== oldWidth) {

        // set higher container width
        this.base.attr("width", this._width);

        // trigger a change event
        this.trigger("change:width", this._width, oldWidth);

        // redraw if we saved the data on the chart
        if (this.data) {
          this.draw(this.data);
        }
      }

      // always return the chart, for chaining magic.
      return this;
    },

    height: function(newHeight) {
      if (arguments.length === 0) {
        if (this._height && !isNaN(+this._height)) {
          return this._height;
        } else {
          return _toNumFromPx(_style.call(this, "height"));
        }
      }

      var oldHeight = this._height;

      this._height = newHeight;

      if (this._height !== oldHeight) {

        this.base.attr("height", this._height);

        this.trigger("change:height", this._height, oldHeight);

        if (this.data) {
          this.draw(this.data);
        }
      }

      return this;
    }
  });

  var oldLayer = BaseChart.prototype.layer;
  BaseChart.prototype.layer = function(name, selection, options) {

    var chart = this;

    // just return an existing layer if all we are
    // passed is the name argument
    if (arguments.length === 1) {
      return oldLayer.call(this, name);
    }

    // save all the layer arguments. For layers that are created
    // but do not need to be rendered in the current mode, this
    // will ensure their arguments are intact for when they do
    // need to be created.
    chart._layersArguments[name] = {
      name : name,
      selection: selection,
      options : options,
      showing: false, // default hidden
      layer: null // layer handle
    };

    // create the layer if it should exist in the curret
    // mode.
    var layer;
    if (typeof options.modes === "undefined" ||
        ("modes" in options &&
          options.modes.indexOf(chart.mode()) > -1)) {

      // run default layer code
      layer = oldLayer.call(this, name, selection, options);

      // mark layer as showing.
      chart._layersArguments[name].showing = true;
      chart._layersArguments[name].layer = layer;
    }

    if ("modes" in options) {

      // save available modes on the layer if we created it
      if (layer) {
        layer._modes = options.modes;
      }

      // cache the layer under the mode name. This will be useful
      // when we are repainting layers.
      options.modes.forEach(function(mode) {

        // make sure mode exists
        if (mode in chart._modes) {

           chart._modeLayers[mode] = chart._modeLayers[mode] || [];

           // save the layer as being mapped to this mode.
           chart._modeLayers[mode].push(name);

        } else {
          throw new Error("Mode " + mode + " is not defined");
        }
      });

    // make sure this layer has all modes if none were
    // specified as an option.
    } else if (chart._modes) {

      var allModes = Object.keys(chart._modes);

      if (layer) {
        layer._modes = allModes;
      }

      allModes.forEach(function(mode) {
        chart._modeLayers[mode] = chart._modeLayers[mode] || [];
        chart._modeLayers[mode].push(name);
      });

      // mark layer as showing.
      chart._layersArguments[name].showing = true;
      chart._layersArguments[name].layer = layer;
    }

    return layer;
  };

}(window.d3));<commit_msg>linting<commit_after>(function(d3) {

  // obtains element computed style
  // context is chart
  function _style(attr) {
    var style,
      element = this.base[0][0];
    if (window.getComputedStyle) {
      style = window.getComputedStyle(element);
    } else if (element.currentStyle) {
      style = element.currentStyle;
    }

    if (!attr) {
      return style;
    } else {
      return style[attr];
    }
  }

  // converts pixel values
  var _toNumFromPx = (function() {
    var rx = /px$/;
    return function(value) {
      if (rx.test(value)) {
        return +(value.replace(rx, ""));
      } else {
        return value;
      }
    };
  }());

  // helper attribute setter on chart base.
  // context is chart
  function _initAttr(internalName, d3Name, defaultValue) {
    var current = _toNumFromPx(_style.call(this, d3Name));

    if (current === null || current === 0 || current === "") {
      this[internalName] = defaultValue;
      this.base.attr(d3Name, defaultValue);
    } else {
      this[internalName] = _toNumFromPx(_style.call(this, d3Name));
    }
  }

  // Borrowed from Underscore.js 1.5.2
  //     http://underscorejs.org
  //     (c) 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
  //     Underscore may be freely distributed under the MIT license.
  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  function _debounce(func, wait, immediate) {
    var timeout, args, context, timestamp, result;
    return function() {
      context = this;
      args = arguments;
      timestamp = new Date();
      var later = function() {
        var last = (new Date()) - timestamp;
        if (last < wait) {
          timeout = setTimeout(later, wait - last);
        } else {
          timeout = null;
          if (!immediate) { result = func.apply(context, args); }
        }
      };
      var callNow = immediate && !timeout;
      if (!timeout) {
        timeout = setTimeout(later, wait);
      }
      if (callNow) { result = func.apply(context, args); }
      return result;
    };
  }

  // go over existing modes and determine which we are in
  // returns true if a mode change occured, false otherwise.
  function _determineMode() {
    var oldMode = this._currentMode;
    this._currentMode = null;

    if ("modes" in this) {
      var result = false;
      for (var mode in this._modes) {
        result = this._modes[mode].call(this);
        if (result) {
          this._currentMode = mode;
          break;
        }
      }
    }
    return oldMode !== this._currentMode;
  }

  // takes care of removing/adding appropriate layers
  function _onModeChange() {

    var chart = this;

    for(var layerName in chart._layersArguments) {

      // is this chart in the current mode?
      var layerArgs = chart._layersArguments[layerName];

      // if this layer should not exist in the current mode
      // unlayer it and then save it so we can reattach it
      // later.
      if (layerArgs.options.modes.indexOf(chart.mode()) === -1) {

        // is it showing?
        if (layerArgs.showing === true) {

          // nope? remove it.
          var removedLayer = chart.unlayer(layerName);
          removedLayer.style("display","none");
          chart._layersArguments[layerName].showing = false;
          chart._layersArguments[layerName].layer = removedLayer;
        }

      } else {

        // this layer is not showing, we need to add it
        if (chart._layersArguments[layerName].showing === false) {

          // if the layer has already been created, just re-add it
          if (chart._layersArguments[layerName].layer !== null) {
            oldLayer.call(chart, layerName, chart._layersArguments[layerName].layer);
            chart._layersArguments[layerName].layer.style("display","inline");
          } else {

            // this layer must not have been drawn in the initial rendering
            // but we do have the arguments, so render it using the
            // old layering.
            oldLayer.call(chart,
              chart._layersArguments[layerName].name,
              chart._layersArguments[layerName].selection,
              chart._layersArguments[layerName].options);
          }

          chart._layersArguments[layerName].showing = true;
        }
      }
    }
  }

  var BaseChart = d3.chart("BaseChart", {
    initialize: function() {

      var chart = this;
      // layer structures container - for layers that are
      // created on initialize but do not actually need to
      // be rendered in the detected mode, we need to save the
      // actual arguments so that we can construct it later.
      this._layersArguments = {};

      // save mode functions
      this._modes = this.modes || {};
      delete this.modes;

      // store layers referenced per mode
      this._modeLayers = {};

      // determine current mode on initialization
      _determineMode.call(this);

      // setup some reasonable defaults
      chart._width  = _toNumFromPx(_style.call(chart, "width")) || 200;
      chart._height = _toNumFromPx(_style.call(chart, "height")) || 200;

      // make sure container height and width are set.
      _initAttr.call(this, "_width", "width", 200);
      _initAttr.call(this, "_height", "height", 200);

      // bind to winow resize end
      window.addEventListener("resize", _debounce(function() {

        chart._width  = _toNumFromPx(_style.call(chart, "width")) || 200;
        chart._height = _toNumFromPx(_style.call(chart, "height")) || 200;

        // update current mode
        var changed = _determineMode.call(chart);
        if (changed) {
          chart.trigger("mode:change", this._currentMode);
        }

        // only redraw if there is data
        if (chart.data) {
          chart.draw(chart.data);
        }

      }, 150));

      window.addEventListener("orientationchange", function() {
        // redraw on device rotation
        chart.trigger("mode:change", this._currentMode);

        // only redraw if there is data
        if (chart.data) {
          chart.draw(chart.data);
        }
      }, false);

      // on mode change, update height and width, and redraw
      // the chart
      chart.on("mode:change", function() {

        // sort out current mode
        _onModeChange.call(chart);

      });
    },

    // returns current mode
    mode : function() {
      return this._currentMode;
    },

    width: function(newWidth) {
      if (arguments.length === 0) {
        if (this._width && !isNaN(+this._width)) {
          return this._width;
        } else {
          return _toNumFromPx(_style.call(this, "width"));
        }
      }

      var oldWidth = this._width;

      this._width = newWidth;

      // only if the width actually changed:
      if (this._width !== oldWidth) {

        // set higher container width
        this.base.attr("width", this._width);

        // trigger a change event
        this.trigger("change:width", this._width, oldWidth);

        // redraw if we saved the data on the chart
        if (this.data) {
          this.draw(this.data);
        }
      }

      // always return the chart, for chaining magic.
      return this;
    },

    height: function(newHeight) {
      if (arguments.length === 0) {
        if (this._height && !isNaN(+this._height)) {
          return this._height;
        } else {
          return _toNumFromPx(_style.call(this, "height"));
        }
      }

      var oldHeight = this._height;

      this._height = newHeight;

      if (this._height !== oldHeight) {

        this.base.attr("height", this._height);

        this.trigger("change:height", this._height, oldHeight);

        if (this.data) {
          this.draw(this.data);
        }
      }

      return this;
    }
  });

  var oldLayer = BaseChart.prototype.layer;
  BaseChart.prototype.layer = function(name, selection, options) {

    var chart = this;

    // just return an existing layer if all we are
    // passed is the name argument
    if (arguments.length === 1) {
      return oldLayer.call(this, name);
    }

    // save all the layer arguments. For layers that are created
    // but do not need to be rendered in the current mode, this
    // will ensure their arguments are intact for when they do
    // need to be created.
    chart._layersArguments[name] = {
      name : name,
      selection: selection,
      options : options,
      showing: false, // default hidden
      layer: null // layer handle
    };

    // create the layer if it should exist in the curret
    // mode.
    var layer;
    if (typeof options.modes === "undefined" ||
        ("modes" in options &&
          options.modes.indexOf(chart.mode()) > -1)) {

      // run default layer code
      layer = oldLayer.call(this, name, selection, options);

      // mark layer as showing.
      chart._layersArguments[name].showing = true;
      chart._layersArguments[name].layer = layer;
    }

    if ("modes" in options) {

      // save available modes on the layer if we created it
      if (layer) {
        layer._modes = options.modes;
      }

      // cache the layer under the mode name. This will be useful
      // when we are repainting layers.
      options.modes.forEach(function(mode) {

        // make sure mode exists
        if (mode in chart._modes) {

           chart._modeLayers[mode] = chart._modeLayers[mode] || [];

           // save the layer as being mapped to this mode.
           chart._modeLayers[mode].push(name);

        } else {
          throw new Error("Mode " + mode + " is not defined");
        }
      });

    // make sure this layer has all modes if none were
    // specified as an option.
    } else if (chart._modes) {

      var allModes = Object.keys(chart._modes);

      if (layer) {
        layer._modes = allModes;
      }

      allModes.forEach(function(mode) {
        chart._modeLayers[mode] = chart._modeLayers[mode] || [];
        chart._modeLayers[mode].push(name);
      });

      // mark layer as showing.
      chart._layersArguments[name].showing = true;
      chart._layersArguments[name].layer = layer;
    }

    return layer;
  };

}(window.d3));<|endoftext|>


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

var assert = require('assert');
var stringify = require('./stringify');
var inherits = require('util').inherits;
var Transform = require('stream').Transform;
var debug = require('debug')('icecast:writer');

/**
 * Module exports.
 */

module.exports = Writer;

/**
 * The value of the metabyte on each pass is
 * "ceil(metadata.length / META_BLOCK_SIZE)".
 */

var META_BLOCK_SIZE = 16;

/**
 * The maximum byte length of a metadata chunk.
 */

var MAX_LENGTH = META_BLOCK_SIZE * 255;

/**
 * Usually, the "metabyte" will just be 1 NUL byte, because there is no metadata
 * to be sent. Create that 1-byte buffer up-front.
 */

var NO_METADATA = new Buffer([0]);

/**
 * The `Writer` class is a duplex stream that accepts raw audio/video data and
 * passes it through untouched. It also has a `queueMetadata()` function that will
 * queue the Writer to inject the metadata into the stream at the next "metaint"
 * interval.
 *
 * @api public
 */

function Writer (metaint, opts) {
  if (!(this instanceof Writer)) {
    return new Writer(metaint, opts);
  }
  if (!isFinite(metaint)) {
    throw new Error('Writer requires a "metaint" number');
  }
  Transform.call(this, opts);
  this.metaint = +metaint;
  this.left = this.metaint;
  this._queue = [];
}
inherits(Writer, Transform);

/**
 * Queues a piece of metadata to be sent along with the stream.
 * `metadata` may be a String and be any title (up to 4066 chars),
 * or may be an Object containing at least a "StreamTitle" key, with a String
 * value. The serialized metadata payload must be <= 4080 bytes.
 *
 * @param {String|Object} metadata
 * @api public
 */

Writer.prototype.queue = function (metadata) {
  if ('string' == typeof metadata) {
    // string title
    metadata = { StreamTitle: metadata };
  } else if (metadata && Object(metadata) === metadata) {
    // an object
  } else {
    throw new TypeError('don\'t know how to format metadata: ' + metadata);
  }
  if (!('StreamTitle' in metadata)) {
    throw new TypeError('a "StreamTitle" property is required for metadata');
  }
  var str = stringify(metadata);
  var len = Buffer.byteLength(str);
  if (len > MAX_LENGTH) {
    throw new Error('metadata must be <= 4080, got: ' + len);
  }
  var meta = Math.ceil(len / META_BLOCK_SIZE)
  var buf = new Buffer(meta + 1);
  buf[0] = meta;
  var written = buf.write(str, 1);
  for (var i = written + 1; i < buf.length; i++) {
    // pad with NULL bytes
    buf[i] = 0;
  }

  this._queue.push(buf);
};

/**
 *
 */

Writer.prototype._transform = function (chunk, write, done) {
  debug('_transform: (chunk.length: %d)', chunk.length);
  assert(this.left > 0);
  if (chunk.length <= this.left) {
    // small buffer fits within the "left" window
    this._onRawData(chunk, write);
    done();
  } else {
    // large buffer needs to be spliced on "left" and processed
    var b = chunk.slice(0, this.left);
    this._onRawData(b, write);
    if (chunk.length > b.length) {
      this._transform(chunk.slice(b.length), write, done);
    }
  }
};

/**
 *
 */

Writer.prototype._onRawData = function (chunk, write) {
  debug('_onRawData: (chunk.length: %d)', chunk.length);
  write(chunk);
  this.left -= chunk.length;
  if (0 === this.left) {
    this._inject(write);
  }
};

/**
 * Writes the next "metabyte" to the output stream.
 */

Writer.prototype._inject = function (write) {
  assert(0 === this.left);
  var buffer;
  if (0 === this._queue.length) {
    buffer = NO_METADATA;
  } else {
    buffer = this._queue.shift();
  }
  write(buffer);
  this.left = this.metaint;
};
<commit_msg>writer: fix typo, which in turn makes it work :)<commit_after>
/**
 * Module dependencies.
 */

var assert = require('assert');
var stringify = require('./stringify');
var inherits = require('util').inherits;
var Transform = require('stream').Transform;
var debug = require('debug')('icecast:writer');

/**
 * Module exports.
 */

module.exports = Writer;

/**
 * The value of the metabyte on each pass is
 * "ceil(metadata.length / META_BLOCK_SIZE)".
 */

var META_BLOCK_SIZE = 16;

/**
 * The maximum byte length of a metadata chunk.
 */

var MAX_LENGTH = META_BLOCK_SIZE * 255;

/**
 * Usually, the "metabyte" will just be 1 NUL byte, because there is no metadata
 * to be sent. Create that 1-byte buffer up-front.
 */

var NO_METADATA = new Buffer([0]);

/**
 * The `Writer` class is a duplex stream that accepts raw audio/video data and
 * passes it through untouched. It also has a `queueMetadata()` function that will
 * queue the Writer to inject the metadata into the stream at the next "metaint"
 * interval.
 *
 * @api public
 */

function Writer (metaint, opts) {
  if (!(this instanceof Writer)) {
    return new Writer(metaint, opts);
  }
  if (!isFinite(metaint)) {
    throw new Error('Writer requires a "metaint" number');
  }
  Transform.call(this, opts);
  this.metaint = +metaint;
  this.left = this.metaint;
  this._queue = [];
}
inherits(Writer, Transform);

/**
 * Queues a piece of metadata to be sent along with the stream.
 * `metadata` may be a String and be any title (up to 4066 chars),
 * or may be an Object containing at least a "StreamTitle" key, with a String
 * value. The serialized metadata payload must be <= 4080 bytes.
 *
 * @param {String|Object} metadata
 * @api public
 */

Writer.prototype.queue = function (metadata) {
  if ('string' == typeof metadata) {
    // string title
    metadata = { StreamTitle: metadata };
  } else if (metadata && Object(metadata) === metadata) {
    // an object
  } else {
    throw new TypeError('don\'t know how to format metadata: ' + metadata);
  }
  if (!('StreamTitle' in metadata)) {
    throw new TypeError('a "StreamTitle" property is required for metadata');
  }
  var str = stringify(metadata);
  var len = Buffer.byteLength(str);
  if (len > MAX_LENGTH) {
    throw new Error('metadata must be <= 4080, got: ' + len);
  }
  var meta = Math.ceil(len / META_BLOCK_SIZE)
  var buf = new Buffer(meta * META_BLOCK_SIZE + 1);
  buf[0] = meta;
  var written = buf.write(str, 1);
  for (var i = written + 1; i < buf.length; i++) {
    // pad with NULL bytes
    buf[i] = 0;
  }

  this._queue.push(buf);
};

/**
 *
 */

Writer.prototype._transform = function (chunk, write, done) {
  debug('_transform: (chunk.length: %d)', chunk.length);
  assert(this.left > 0);
  if (chunk.length <= this.left) {
    // small buffer fits within the "left" window
    this._onRawData(chunk, write);
    done();
  } else {
    // large buffer needs to be spliced on "left" and processed
    var b = chunk.slice(0, this.left);
    this._onRawData(b, write);
    if (chunk.length > b.length) {
      this._transform(chunk.slice(b.length), write, done);
    }
  }
};

/**
 *
 */

Writer.prototype._onRawData = function (chunk, write) {
  debug('_onRawData: (chunk.length: %d)', chunk.length);
  write(chunk);
  this.left -= chunk.length;
  if (0 === this.left) {
    this._inject(write);
  }
};

/**
 * Writes the next "metabyte" to the output stream.
 */

Writer.prototype._inject = function (write) {
  assert(0 === this.left);
  var buffer;
  if (0 === this._queue.length) {
    buffer = NO_METADATA;
  } else {
    buffer = this._queue.shift();
  }
  write(buffer);
  this.left = this.metaint;
};
<|endoftext|>


<commit_before>"use strict";
module.exports = function(Promise, INTERNAL, tryConvertToPromise) {
var rejectThis = function(_, e) {
    this._reject(e);
};

var targetRejected = function(e, context) {
    context.promiseRejectionQueued = true;
    context.bindingPromise._then(rejectThis, rejectThis, null, this, e);
};

var bindingResolved = function(thisArg, context) {
    this._setBoundTo(thisArg);
    if (this._isPending()) {
        this._follow(context.target);
    }
};

var bindingRejected = function(e, context) {
    if (!context.promiseRejectionQueued) this._reject(e);
};

Promise.prototype.bind = function (thisArg) {
    var maybePromise = tryConvertToPromise(thisArg);
    var ret = new Promise(INTERNAL);
    ret._propagateFrom(this, PROPAGATE_CANCEL);
    var target = this._target();
    if (maybePromise instanceof Promise) {
        var context = {
            promiseRejectionQueued: false,
            promise: ret,
            target: target,
            bindingPromise: maybePromise
        };
        target._then(INTERNAL, targetRejected, ret._progress, ret, context);
        maybePromise._then(
            bindingResolved, bindingRejected, ret._progress, ret, context);
    } else {
        ret._setBoundTo(thisArg);
        ret._follow(target);
    }
    return ret;
};

Promise.bind = function (thisArg, value) {
    var maybePromise = tryConvertToPromise(thisArg);
    var ret = new Promise(INTERNAL);

    if (maybePromise instanceof Promise) {
        maybePromise._then(function(thisArg) {
            ret._setBoundTo(thisArg);
            ret._resolveCallback(value);
        }, ret._reject, ret._progress, ret, null);
    } else {
        ret._setBoundTo(thisArg);
        ret._resolveCallback(value);
    }
    return ret;
};
};
<commit_msg>Move rest of the bind methods to bind.js<commit_after>"use strict";
module.exports = function(Promise, INTERNAL, tryConvertToPromise) {
var rejectThis = function(_, e) {
    this._reject(e);
};

var targetRejected = function(e, context) {
    context.promiseRejectionQueued = true;
    context.bindingPromise._then(rejectThis, rejectThis, null, this, e);
};

var bindingResolved = function(thisArg, context) {
    this._setBoundTo(thisArg);
    if (this._isPending()) {
        this._follow(context.target);
    }
};

var bindingRejected = function(e, context) {
    if (!context.promiseRejectionQueued) this._reject(e);
};

Promise.prototype.bind = function (thisArg) {
    var maybePromise = tryConvertToPromise(thisArg);
    var ret = new Promise(INTERNAL);
    ret._propagateFrom(this, PROPAGATE_CANCEL);
    var target = this._target();
    if (maybePromise instanceof Promise) {
        var context = {
            promiseRejectionQueued: false,
            promise: ret,
            target: target,
            bindingPromise: maybePromise
        };
        target._then(INTERNAL, targetRejected, ret._progress, ret, context);
        maybePromise._then(
            bindingResolved, bindingRejected, ret._progress, ret, context);
    } else {
        ret._setBoundTo(thisArg);
        ret._follow(target);
    }
    return ret;
};

Promise.prototype._setBoundTo = function (obj) {
    if (obj !== undefined) {
        this._bitField = this._bitField | IS_BOUND;
        this._boundTo = obj;
    } else {
        this._bitField = this._bitField & (~IS_BOUND);
    }
};

Promise.prototype._isBound = function () {
    return (this._bitField & IS_BOUND) === IS_BOUND;
};

Promise.bind = function (thisArg, value) {
    var maybePromise = tryConvertToPromise(thisArg);
    var ret = new Promise(INTERNAL);

    if (maybePromise instanceof Promise) {
        maybePromise._then(function(thisArg) {
            ret._setBoundTo(thisArg);
            ret._resolveCallback(value);
        }, ret._reject, ret._progress, ret, null);
    } else {
        ret._setBoundTo(thisArg);
        ret._resolveCallback(value);
    }
    return ret;
};
};
<|endoftext|>


<commit_before>/**
 * XadillaX created at 2014-09-09 14:21
 *
 * Copyright (c) 2014 Huaban.com, all rights
 * reserved.
 */
var mysql = require("mysql");
var async = require("async");
var fieldType = require("./fieldType");
var ResultPromisor = require("./resultpromisor");

/**
 * Yakumo Yukari is the entity of Yakumo Toshihiko.
 * @param model
 * @param source
 * @constructor
 */
var Yukari = function(model, source) {
    this.$model = model;
    this.$toshihiko = model.toshihiko;

    this.$fields = model._fieldsObject;
    this.$origData = {};

    this.$source = source;

    this.$dbName = this.$toshihiko.database;
    this.$tableName = this.$model.name;
    this.$cache = model.cache;

    this.$fromCache = false;
};

/**
 * get primary keys and values
 * @param {Boolean} [fromOrig] is primary keys from original data
 * @returns {*}
 * @private
 */
Yukari.prototype._getPrimaryKeys = function(fromOrig) {
    var keys = this.$model.getPrimaryKeysName();
    if(typeof keys === "string") {
        return (fromOrig || this[keys] === undefined) ?
            this.$origData[keys] :
            this[keys];
    }

    var obj = {};
    for(var i = 0; i < keys.length; i++) {
        obj[keys[i]] = (fromOrig || this[keys[i]] === undefined) ?
            this.$origData[keys[i]] :
            this[keys[i]];
    }
    return obj;
};

/**
 * build row
 * @param row
 * @param useOrigName
 * @private
 */
Yukari.prototype._buildRow = function(row, useOrigName) {
    this.$origData = {};

    for(var i = 0; i < this.$fields.length; i++) {
        var field = this.$fields[i];
        var value = row[useOrigName ? field.column : field.name];

        if(undefined === value) {
            if(undefined !== field.defaultValue) {
                value = field.defaultValue;
            }
        }

        if(undefined === value) {
            continue;
        }

        this[field.name] = Object.clone(value);
    }

    if(row.$fromCache) {
        this.$fromCache = true;
    }
};

/**
 * initialize row
 * @param row
 * @param useOrigName
 * @private
 */
Yukari.prototype._initRow = function(row, useOrigName) {
    for(var i = 0; i < this.$fields.length; i++) {
        var field = this.$fields[i];
        var name = useOrigName ? field.column : field.name;
        if(undefined === row[name]) {
            continue;
        }

        this.$origData[field.name] = {
            fieldIdx    : i,
            data        : row[name] === null ? null : field.type.parse(row[name])
        };
    }

    for(var key in this.$origData) {
        if(!this.$origData.hasOwnProperty(key)) {
            continue;
        }

        this[key] = Object.clone(this.$origData[key].data, true);
    }

    if(row.$fromCache) {
        this.$fromCache = true;
    }
};

/**
 * to json object
 * @param [old]
 * @returns {{}}
 */
Yukari.prototype.toJSON = function(old) {
    var obj = old ? this.$origData : this;
    var result = {};
    for(var key in obj) {
        if(key.length && key[0] !== '$' && typeof obj[key] !== "function") {
            result[key] = obj[key];
        }
    }

    return result;
};

/**
 * field at
 * @param name
 * @returns {*}
 * @private
 */
Yukari.prototype._fieldAt = function(name) {
    if(this.$source !== "new" && this.$origData[name]) {
        return this.$origData[name].fieldIdx;
    } else if(this.$source === "new") {
        for(var i = 0; i < this.$fields.length; i++) {
            if(this.$fields[i].name === name) {
                return i;
            }
        }
    }

    return -1;
};

/**
 * validate all fields
 * @returns {*}
 */
Yukari.prototype.validateAll = function() {
    for(var key in this) {
        if(!this.hasOwnProperty(key)) {
            continue;
        }

        if(key[0] === "$" ||
            typeof this[key] === "function" ||
            this._fieldAt(key) === -1) {
            continue;
        }

        var result = this.validateOne(key, this[key]);
        if(typeof result === "string" && result.length) {
            return result;
        }
    }

    return "";
};

/**
 * validate one field
 * @param name
 * @param value
 * @returns {*}
 */
Yukari.prototype.validateOne = function(name, value) {
    var fieldIdx = this._fieldAt(name);
    if(-1 === fieldIdx) {
        return "No such field.";
    }

    var field = this.$fields[fieldIdx];

    if(null === value && !field.allowNull) {
        return "Field " + name + " can't be NULL.";
    } else if(null === value && field.allowNull) {
        return "";
    }

    for(var i = 0; i < field.validators.length; i++) {
        var result = field.validators[i](value);
        if(typeof result === "string" && result.length) {
            return result;
        }
    }

    return "";
};

/**
 * insert a record
 * @param callback
 * @returns {*}
 */
Yukari.prototype.insert = function(callback) {
    var promise = new ResultPromisor();
    if(callback === undefined) {
        callback = function() {};
    }

    if(this.$source !== "new") {
        callback(new Error("You must call this function via a new Yukari object."));
        promise._onExecuteResult(new Error("You must call this function via a new Yukari object."));
        return promise;
    }

    var vldt = this.validateAll();
    if(vldt.length) {
        callback(new Error(vldt));
        promise._onExecuteResult(new Error(vldt))
        return promise;
    }

    var data = {};
    for(var key in this) {
        if(!this.hasOwnProperty(key)) {
            continue;
        }

        if(key[0] === "$") {
            continue;
        }

        var fieldIdx = this._fieldAt(key);

        if(-1 === fieldIdx) {
            continue;
        }

        var field = this.$fields[fieldIdx];
        if(this[key] !== null) {
            data[field.column] = field.type.restore(this[key]);
        } else {
            this[key] = null;
        }
    }

    var sql = "INSERT INTO `{table}` SET ?".assign({ table: this.$model.name });
    sql = mysql.format(sql, data);

    if(this.$toshihiko.options.showSql) {
        console.log("❤ Toshihiko is executing SQL: 【" + sql + "】...");
    }

    var self = this;
    this.$toshihiko.execute(sql, function(err, row) {
        if(err) {
            callback(err, undefined, sql);
            promise._onExecuteResult(err, undefined, sql);
            return promise;
        }

        if(!row) {
            callback(new Error("No row inserted."), undefined, sql);
            promise._onExecuteResult(new Error("No row inserted."), undefined, sql);
            return promise;

        }

        self.$source = "query";

        var where = {};
        var pk = self.$model.primaryKeys;
        if(row.insertId) {
            where[pk[0].name] = row.insertId;
        } else {
            for(var i = 0; i < self.$model.primaryKeys.length; i++) {
                where[pk[0].name] = self[pk[0].name];
            }
        }

        self.$model.where(where).findOne(function(err, row) {
            if(err) {
                callback(new Error("Insert successfully but failed to read the record: " + err.message));
                promise._onExecuteResult(new Error("Insert successfully but failed to read the record: " + err.message));
                return promise;
            }

            if(!row) {
                callback(new Error("Insert successfully but failed to read the record: Unknown error."));
                promise._onExecuteResult(new Error("Insert successfully but failed to read the record: Unknown error."));
                return promise;
            }

            for(var key in row) {
                if(!row.hasOwnProperty(key)) {
                    continue;
                }

                if(key[0] === '$' && key !== "$origData") {
                    continue;
                }

                if(typeof row[key] === "function") {
                    continue;
                }

                self[key] = Object.clone(row[key], true);
            }

            callback(undefined, self, sql);
            promise._onExecuteResult(undefined, self, sql);
            return promise;
        });
    });
};

/**
 * updata a record
 * @param callback
 * @returns {*}
 */
Yukari.prototype.update = function(callback) {
    var promise = new ResultPromisor();
    if(undefined === callback) {
        callback = function() {};
    }

    if(this.$source !== "query") {
        callback(new Error("You must call this function via a Yukari object which is queried."));
        promise._onExecuteResult(new Error("You must call this function via a Yukari object which is queried."));
        return promise;
    }

    var vldt = this.validateAll();
    if(vldt.length) {
        callback(new Error(vldt));
        promise._onExecuteResult(new Error(vldt));
        return promise;
    }

    var data = {};

    for(var key in this) {
        if(!this.hasOwnProperty(key)) {
            continue;
        }

        if(key[0] === "$") {
            continue;
        }

        if(typeof this[key] === "function") {
            continue;
        }

        var fieldIdx = this._fieldAt(key);
        if(-1 === fieldIdx) {
            continue;
        }

        var field = this.$fields[fieldIdx];
        var equalFunc = (undefined === field.type.equal ? fieldType.$equal : field.type.equal);

        // only change changed data
        if(!equalFunc(this[key], this.$origData[key].data)) {
            data[field.name] = {
                data    : this[key],
                field   : field
            };
        }
    }

    if(!Object.size(data)) {
        callback(new Error("No data changed."));
        promise._onExecuteResult(new Error("No data changed."))
        return promise;
    }

    // set data
    var sql = "UPDATE `{table}` SET ?";
    var finalData = {};
    for(var key in data) {
        if(data[key].data !== null) {
            finalData[this.$model._fieldsKeyMap.n2c[key]] = data[key].field.type.restore(data[key].data);
        } else {
            finalData[this.$model._fieldsKeyMap.n2c[key]] = null;
        }
    }

    sql = sql.assign({ table: this.$model.name });
    sql = mysql.format(sql, finalData) + " WHERE ";

    // set primary key condition
    var conditionSql = "";
    var pk = [];
    for(var i = 0; i < this.$model.primaryKeys.length; i++) {
        var p = this.$model.primaryKeys[i];
        if(pk.length) {
            conditionSql += " AND ";
        }

        conditionSql += "`" + p.column + "` = ?";

        pk.push(p.type.restore(this.$origData[p.name].data));
    }
    sql += mysql.format(conditionSql, pk);

    if(this.$toshihiko.options.showSql) {
        console.log("❤ Toshihiko is executing SQL: 【" + sql + "】...");
    }

    var self = this;
    async.waterfall([
        /**
         * step 1.
         *   if cache...
         * @param callback
         */
        function(callback) {
            if(!self.$cache) {
                return callback();
            }

            var primary = self._getPrimaryKeys(true);
            if(typeof primary === "object") {
                self.$model.nameToColumn(primary);
            }

            self.$cache.deleteData(self.$dbName, self.$tableName, primary, function(err) {
                callback(err);
            });
        },

        /**
         * step 2.
         *   edit 本体...
         * @param callback
         */
        function(callback) {
            self.$toshihiko.execute(sql, function(err, results) {
                if(err) {
                    return callback(err);
                }

                if(!results.changedRows) {
                    return callback(new Error("No row(s) effected."));
                }

                for(var key in data) {
                    if(!data.hasOwnProperty(key)) {
                        continue;
                    }

                    var d = data[key];
                    self.$origData[d.field.name].data = d.data;
                }
                self.$source = "query";

                callback();
            });
        }
    ], function(err) {
        callback(err, self, sql);
        promise._onExecuteResult(err, self, sql);
    });
    return promise;
};

/**
 * delete this yukari
 * @param callback
 * @returns {*}
 */
Yukari.prototype.delete = function(callback) {
    var promise = new ResultPromisor();
    if(undefined === callback) {
        callback = function(){};
    }

    if(this.$source === "new") {
        callback(new Error("You must delete a queried record."));
        promise._onExecuteResult(new Error("You must delete a queried record."));
        return promise;
    }

    var pk = {};
    for(var i = 0; i < this.$model.primaryKeys.length; i++) {
        var field = this.$model.primaryKeys[i];
        pk[field.name] = this.$origData[field.name].data;
    }

    /**
     * Model::where -> new Query
     * Query::delete 已经有做对 CACHE 的处理，无需多处理
     */
    this.$model.where(pk).limit("0,1").delete(function(err, result, sql) {
        if(err) {
            callback(err, undefined, sql);
            promise._onExecuteResult(err, undefined, sql);
            return promise;
        }

        callback(undefined, result.affectedRows, sql);
        promise._onExecuteResult(undefined, result.affectedRows, sql);
        return promise;
    });
};

/**
 * save / insert a record
 * @param callback
 */
Yukari.prototype.save = function(callback) {
    if(undefined === callback) {
        callback = function() {};
    }

    if(this.$source === "query") {
        return this.update(callback);
    } else if(this.$source === "new") {
        return this.insert(callback);
    }
};

module.exports = Yukari;

<commit_msg>update promise like to yukari<commit_after>/**
 * XadillaX created at 2014-09-09 14:21
 *
 * Copyright (c) 2014 Huaban.com, all rights
 * reserved.
 */
var mysql = require("mysql");
var async = require("async");
var fieldType = require("./fieldType");
var ResultPromisor = require("./resultpromisor");

/**
 * Yakumo Yukari is the entity of Yakumo Toshihiko.
 * @param model
 * @param source
 * @constructor
 */
var Yukari = function(model, source) {
    this.$model = model;
    this.$toshihiko = model.toshihiko;

    this.$fields = model._fieldsObject;
    this.$origData = {};

    this.$source = source;

    this.$dbName = this.$toshihiko.database;
    this.$tableName = this.$model.name;
    this.$cache = model.cache;

    this.$fromCache = false;
};

/**
 * get primary keys and values
 * @param {Boolean} [fromOrig] is primary keys from original data
 * @returns {*}
 * @private
 */
Yukari.prototype._getPrimaryKeys = function(fromOrig) {
    var keys = this.$model.getPrimaryKeysName();
    if(typeof keys === "string") {
        return (fromOrig || this[keys] === undefined) ?
            this.$origData[keys] :
            this[keys];
    }

    var obj = {};
    for(var i = 0; i < keys.length; i++) {
        obj[keys[i]] = (fromOrig || this[keys[i]] === undefined) ?
            this.$origData[keys[i]] :
            this[keys[i]];
    }
    return obj;
};

/**
 * build row
 * @param row
 * @param useOrigName
 * @private
 */
Yukari.prototype._buildRow = function(row, useOrigName) {
    this.$origData = {};

    for(var i = 0; i < this.$fields.length; i++) {
        var field = this.$fields[i];
        var value = row[useOrigName ? field.column : field.name];

        if(undefined === value) {
            if(undefined !== field.defaultValue) {
                value = field.defaultValue;
            }
        }

        if(undefined === value) {
            continue;
        }

        this[field.name] = Object.clone(value);
    }

    if(row.$fromCache) {
        this.$fromCache = true;
    }
};

/**
 * initialize row
 * @param row
 * @param useOrigName
 * @private
 */
Yukari.prototype._initRow = function(row, useOrigName) {
    for(var i = 0; i < this.$fields.length; i++) {
        var field = this.$fields[i];
        var name = useOrigName ? field.column : field.name;
        if(undefined === row[name]) {
            continue;
        }

        this.$origData[field.name] = {
            fieldIdx    : i,
            data        : row[name] === null ? null : field.type.parse(row[name])
        };
    }

    for(var key in this.$origData) {
        if(!this.$origData.hasOwnProperty(key)) {
            continue;
        }

        this[key] = Object.clone(this.$origData[key].data, true);
    }

    if(row.$fromCache) {
        this.$fromCache = true;
    }
};

/**
 * to json object
 * @param [old]
 * @returns {{}}
 */
Yukari.prototype.toJSON = function(old) {
    var obj = old ? this.$origData : this;
    var result = {};
    for(var key in obj) {
        if(key.length && key[0] !== '$' && typeof obj[key] !== "function") {
            result[key] = obj[key];
        }
    }

    return result;
};

/**
 * field at
 * @param name
 * @returns {*}
 * @private
 */
Yukari.prototype._fieldAt = function(name) {
    if(this.$source !== "new" && this.$origData[name]) {
        return this.$origData[name].fieldIdx;
    } else if(this.$source === "new") {
        for(var i = 0; i < this.$fields.length; i++) {
            if(this.$fields[i].name === name) {
                return i;
            }
        }
    }

    return -1;
};

/**
 * validate all fields
 * @returns {*}
 */
Yukari.prototype.validateAll = function() {
    for(var key in this) {
        if(!this.hasOwnProperty(key)) {
            continue;
        }

        if(key[0] === "$" ||
            typeof this[key] === "function" ||
            this._fieldAt(key) === -1) {
            continue;
        }

        var result = this.validateOne(key, this[key]);
        if(typeof result === "string" && result.length) {
            return result;
        }
    }

    return "";
};

/**
 * validate one field
 * @param name
 * @param value
 * @returns {*}
 */
Yukari.prototype.validateOne = function(name, value) {
    var fieldIdx = this._fieldAt(name);
    if(-1 === fieldIdx) {
        return "No such field.";
    }

    var field = this.$fields[fieldIdx];

    if(null === value && !field.allowNull) {
        return "Field " + name + " can't be NULL.";
    } else if(null === value && field.allowNull) {
        return "";
    }

    for(var i = 0; i < field.validators.length; i++) {
        var result = field.validators[i](value);
        if(typeof result === "string" && result.length) {
            return result;
        }
    }

    return "";
};

/**
 * insert a record
 * @param callback
 * @returns {*}
 */
Yukari.prototype.insert = function(callback) {
    var promise = new ResultPromisor();
    if(callback === undefined) {
        callback = function() {};
    }

    if(this.$source !== "new") {
        callback(new Error("You must call this function via a new Yukari object."));
        promise._onExecuteResult(new Error("You must call this function via a new Yukari object."));
        return promise;
    }

    var vldt = this.validateAll();
    if(vldt.length) {
        callback(new Error(vldt));
        promise._onExecuteResult(new Error(vldt))
        return promise;
    }

    var data = {};
    for(var key in this) {
        if(!this.hasOwnProperty(key)) {
            continue;
        }

        if(key[0] === "$") {
            continue;
        }

        var fieldIdx = this._fieldAt(key);

        if(-1 === fieldIdx) {
            continue;
        }

        var field = this.$fields[fieldIdx];
        if(this[key] !== null) {
            data[field.column] = field.type.restore(this[key]);
        } else {
            this[key] = null;
        }
    }

    var sql = "INSERT INTO `{table}` SET ?".assign({ table: this.$model.name });
    sql = mysql.format(sql, data);

    if(this.$toshihiko.options.showSql) {
        console.log("❤ Toshihiko is executing SQL: 【" + sql + "】...");
    }

    var self = this;
    this.$toshihiko.execute(sql, function(err, row) {
        if(err) {
            callback(err, undefined, sql);
            promise._onExecuteResult(err, undefined, sql);
            return promise;
        }

        if(!row) {
            callback(new Error("No row inserted."), undefined, sql);
            promise._onExecuteResult(new Error("No row inserted."), undefined, sql);
            return promise;

        }

        self.$source = "query";

        var where = {};
        var pk = self.$model.primaryKeys;
        if(row.insertId) {
            where[pk[0].name] = row.insertId;
        } else {
            for(var i = 0; i < self.$model.primaryKeys.length; i++) {
                where[pk[0].name] = self[pk[0].name];
            }
        }

        self.$model.where(where).findOne(function(err, row) {
            if(err) {
                callback(new Error("Insert successfully but failed to read the record: " + err.message));
                promise._onExecuteResult(new Error("Insert successfully but failed to read the record: " + err.message));
                return promise;
            }

            if(!row) {
                callback(new Error("Insert successfully but failed to read the record: Unknown error."));
                promise._onExecuteResult(new Error("Insert successfully but failed to read the record: Unknown error."));
                return promise;
            }

            for(var key in row) {
                if(!row.hasOwnProperty(key)) {
                    continue;
                }

                if(key[0] === '$' && key !== "$origData") {
                    continue;
                }

                if(typeof row[key] === "function") {
                    continue;
                }

                self[key] = Object.clone(row[key], true);
            }

            callback(undefined, self, sql);
            promise._onExecuteResult(undefined, self, sql);
        });
    });
    return promise;
};

/**
 * updata a record
 * @param callback
 * @returns {*}
 */
Yukari.prototype.update = function(callback) {
    var promise = new ResultPromisor();
    if(undefined === callback) {
        callback = function() {};
    }

    if(this.$source !== "query") {
        callback(new Error("You must call this function via a Yukari object which is queried."));
        promise._onExecuteResult(new Error("You must call this function via a Yukari object which is queried."));
        return promise;
    }

    var vldt = this.validateAll();
    if(vldt.length) {
        callback(new Error(vldt));
        promise._onExecuteResult(new Error(vldt));
        return promise;
    }

    var data = {};

    for(var key in this) {
        if(!this.hasOwnProperty(key)) {
            continue;
        }

        if(key[0] === "$") {
            continue;
        }

        if(typeof this[key] === "function") {
            continue;
        }

        var fieldIdx = this._fieldAt(key);
        if(-1 === fieldIdx) {
            continue;
        }

        var field = this.$fields[fieldIdx];
        var equalFunc = (undefined === field.type.equal ? fieldType.$equal : field.type.equal);

        // only change changed data
        if(!equalFunc(this[key], this.$origData[key].data)) {
            data[field.name] = {
                data    : this[key],
                field   : field
            };
        }
    }

    if(!Object.size(data)) {
        callback(new Error("No data changed."));
        promise._onExecuteResult(new Error("No data changed."))
        return promise;
    }

    // set data
    var sql = "UPDATE `{table}` SET ?";
    var finalData = {};
    for(var key in data) {
        if(data[key].data !== null) {
            finalData[this.$model._fieldsKeyMap.n2c[key]] = data[key].field.type.restore(data[key].data);
        } else {
            finalData[this.$model._fieldsKeyMap.n2c[key]] = null;
        }
    }

    sql = sql.assign({ table: this.$model.name });
    sql = mysql.format(sql, finalData) + " WHERE ";

    // set primary key condition
    var conditionSql = "";
    var pk = [];
    for(var i = 0; i < this.$model.primaryKeys.length; i++) {
        var p = this.$model.primaryKeys[i];
        if(pk.length) {
            conditionSql += " AND ";
        }

        conditionSql += "`" + p.column + "` = ?";

        pk.push(p.type.restore(this.$origData[p.name].data));
    }
    sql += mysql.format(conditionSql, pk);

    if(this.$toshihiko.options.showSql) {
        console.log("❤ Toshihiko is executing SQL: 【" + sql + "】...");
    }

    var self = this;
    async.waterfall([
        /**
         * step 1.
         *   if cache...
         * @param callback
         */
        function(callback) {
            if(!self.$cache) {
                return callback();
            }

            var primary = self._getPrimaryKeys(true);
            if(typeof primary === "object") {
                self.$model.nameToColumn(primary);
            }

            self.$cache.deleteData(self.$dbName, self.$tableName, primary, function(err) {
                callback(err);
            });
        },

        /**
         * step 2.
         *   edit 本体...
         * @param callback
         */
        function(callback) {
            self.$toshihiko.execute(sql, function(err, results) {
                if(err) {
                    return callback(err);
                }

                if(!results.changedRows) {
                    return callback(new Error("No row(s) effected."));
                }

                for(var key in data) {
                    if(!data.hasOwnProperty(key)) {
                        continue;
                    }

                    var d = data[key];
                    self.$origData[d.field.name].data = d.data;
                }
                self.$source = "query";

                callback();
            });
        }
    ], function(err) {
        callback(err, self, sql);
        promise._onExecuteResult(err, self, sql);
    });
    return promise;
};

/**
 * delete this yukari
 * @param callback
 * @returns {*}
 */
Yukari.prototype.delete = function(callback) {
    var promise = new ResultPromisor();
    if(undefined === callback) {
        callback = function(){};
    }

    if(this.$source === "new") {
        callback(new Error("You must delete a queried record."));
        promise._onExecuteResult(new Error("You must delete a queried record."));
        return promise;
    }

    var pk = {};
    for(var i = 0; i < this.$model.primaryKeys.length; i++) {
        var field = this.$model.primaryKeys[i];
        pk[field.name] = this.$origData[field.name].data;
    }

    /**
     * Model::where -> new Query
     * Query::delete 已经有做对 CACHE 的处理，无需多处理
     */
    this.$model.where(pk).limit("0,1").delete(function(err, result, sql) {
        if(err) {
            callback(err, undefined, sql);
            promise._onExecuteResult(err, undefined, sql);
        }

        callback(undefined, result.affectedRows, sql);
        promise._onExecuteResult(undefined, result.affectedRows, sql);
    });
    return promise;
};

/**
 * save / insert a record
 * @param callback
 */
Yukari.prototype.save = function(callback) {
    if(undefined === callback) {
        callback = function() {};
    }

    if(this.$source === "query") {
        return this.update(callback);
    } else if(this.$source === "new") {
        return this.insert(callback);
    }
};

module.exports = Yukari;

<|endoftext|>


<commit_before>'use strict';

var createStore = require('fluxible/addons/createStore');

module.exports = createStore({
    storeName: 'HomeLinkStore',
    all: function () {
        return [
            {
                displayName: "Coiffeur 1er Arrondissement",
                address: "1er-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 2ème Arrondissement",
                address: "2e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 3ème Arrondissement",
                address: "3e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 4ème Arrondissement",
                address: "4e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 5ème Arrondissement",
                address: "4e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 6ème Arrondissement",
                address: "6e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 7ème Arrondissement",
                address: "7e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 8ème Arrondissement",
                address: "8e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 9ème Arrondissement",
                address: "9e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 10ème Arrondissement",
                address: "10e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 11ème Arrondissement",
                address: "11e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 12ème Arrondissement",
                address: "12e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 13ème Arrondissement",
                address: "13e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 14ème Arrondissement",
                address: "14e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 15ème Arrondissement",
                address: "15e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 16ème Arrondissement",
                address: "16e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 17ème Arrondissement",
                address: "17e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 18ème Arrondissement",
                address: "18e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 19ème Arrondissement",
                address: "19e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 20ème Arrondissement",
                address: "20e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur Les Batignolles",
                address: "Batignolles--75017-Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur dans le Marais",
                address: "Le-Marais--Paris--France",
                category: ""
            }
        ];
    }
});
<commit_msg>Correction<commit_after>'use strict';

var createStore = require('fluxible/addons/createStore');

module.exports = createStore({
    storeName: 'HomeLinkStore',
    all: function () {
        return [
            {
                displayName: "Coiffeur 1er Arrondissement",
                address: "1er-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 2ème Arrondissement",
                address: "2e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 3ème Arrondissement",
                address: "3e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 4ème Arrondissement",
                address: "4e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 5ème Arrondissement",
                address: "5e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 6ème Arrondissement",
                address: "6e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 7ème Arrondissement",
                address: "7e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 8ème Arrondissement",
                address: "8e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 9ème Arrondissement",
                address: "9e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 10ème Arrondissement",
                address: "10e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 11ème Arrondissement",
                address: "11e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 12ème Arrondissement",
                address: "12e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 13ème Arrondissement",
                address: "13e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 14ème Arrondissement",
                address: "14e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 15ème Arrondissement",
                address: "15e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 16ème Arrondissement",
                address: "16e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 17ème Arrondissement",
                address: "17e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 18ème Arrondissement",
                address: "18e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 19ème Arrondissement",
                address: "19e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur 20ème Arrondissement",
                address: "20e-Arrondissement--Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur Les Batignolles",
                address: "Batignolles--75017-Paris--France",
                category: ""
            },
            {
                displayName: "Coiffeur dans le Marais",
                address: "Le-Marais--Paris--France",
                category: ""
            }
        ];
    }
});
<|endoftext|>


<commit_before>const _ = require("lodash");
const got = require('got');
const Flickr = require("flickrapi");
const exifparser = require('exif-parser');
var logger;
const config = require('config');
var watcher
const flickrOptions = {
      api_key: config.get('api_key'),
      secret: config.get('secret'),
      user_id: config.get('user_id'),
      access_token: config.get('access_token'),
      access_token_secret: config.get('access_token_secret')
    };
const PubSub = require('pubsub-js');

module.exports = { handlePhotoRequest: handlePhotoRequest }

async function handlePhotoRequest(msg, data) {
  console.log('Photo loader has received a request.');
  try {
    let newPhoto = await getAGoodPhoto(data.logger);
    PubSub.publish( 'photosArrivals', newPhoto)
  } catch(err) {
    data.logger.error('Photo retrieval failure', err);
    process.exit(1);
  }
}

async function getAGoodPhoto(winston) {
  logger = winston;
  return new Promise(async function(resolve, reject) {
    try  {
      let flickrConnection = await getFlickrConnection();
      let userids = config.get('users');

      let userId = userids[_.random(0, userids.length - 1, false)];

      let totatNumberOfPhotos = await getTotalNumberOfPhotos(flickrConnection, userId);
      let aGoodPictureData = undefined;
      let aGoodPictureBits = undefined;
      let aGoodPictureExif = undefined;
      while (aGoodPictureData == undefined) {
        let aRandomPictureData = await getARandomPictureData(flickrConnection, userId, totatNumberOfPhotos)
        let aRandomPictureSizes = await getPictureAvailableSizes(flickrConnection, aRandomPictureData, userId)
        var aRandomPictureOriginalSize = _.find(aRandomPictureSizes.sizes.size, { label: "Original"});
        if (await pictureDataIsGood(aRandomPictureData, aRandomPictureOriginalSize)) {
          aGoodPictureData = aRandomPictureData;
          aGoodPictureBits = await getPictureBits(aRandomPictureOriginalSize.source);
          aGoodPictureExif = await getPictureExif(aGoodPictureBits);
        }
        else {
          logger.info('Picture ' + aRandomPictureData.photos.photo[0].title + ' is not good.  Trying another.')
        }
      }

      logger.info('Good picture ready');

      resolve(
        {
          photoInfo: aGoodPictureData,
          photoExif: aGoodPictureExif,
          photoData: Buffer.from(aGoodPictureBits).toString('base64')
        });
    } catch (err) {
      reject(err);
    }
  });
}

async function getFlickrConnection() {
  return new Promise(async function(resolve, reject) {
    Flickr.authenticate(flickrOptions, function(error, flickr) {
      if(error)
        reject(error)
      else
        resolve(flickr)
    });
  });
}

async function getTotalNumberOfPhotos(flickrConnection, userId) {
  return new Promise(async function(resolve, reject) {
      flickrConnection.people.getPhotos({ user_id: userId, authenticated: true, privacy_filter: 4, per_page: 1}, function(err, result) {
        if(err)
          reject(err);
        else
          resolve(result.photos.total);
      });
  });
}

async function getARandomPictureData(flickrConnection, userId, totatNumberOfPhotos) {
  return new Promise(async function(resolve, reject) {
    var randomPhotoNumber = Math.floor(Math.random() * totatNumberOfPhotos);
    try{
      //if(flickr.options && flickr.options.users && flickr.options.users.length)
      //  user = flickr.options.users[0];

      flickrConnection.photos.search({
        user_id: userId,
        authenticated: true,
        per_page: 1,
        page: randomPhotoNumber
      }, function(err, searchResult) {
        resolve(searchResult);
      });
    } catch (err)  {
      logger.error('Failed to flickr search', err)
      reject(err)
    }
  })
}

async function getPictureAvailableSizes(flickrConnection, flickrPhotoSearchResult, userId) {
  return new Promise(async function(resolve, reject) {
    flickrConnection.photos.getSizes({
      user_id: userId,
      authenticated: true,
      photo_id: flickrPhotoSearchResult.photos.photo[0].id
    }, function(err, availableSizes) {
        if(err)
          reject(err);
        else
          resolve(availableSizes);
    });
  });
}

async function pictureDataIsGood(flickrPhotoSearchResult, originalPhotoData) {
  return new Promise(async function(resolve, reject) {
    let photoIsGood = flickrPhotoSearchResult.photos.photo[0].ispublic || flickrPhotoSearchResult.photos.photo[0].isfamily || flickrPhotoSearchResult.photos.photo[0].isfriend;
    photoIsGood = photoIsGood && originalPhotoData.media == "photo";
    if(photoIsGood) {
      resolve(true)
    } else {
      resolve(false);
    }
  })
}

async function getPictureBits(url) {
  return new Promise(async function(resolve, reject) {
    logger.info('Fetching ' + url);
    var response = await got(url, { encoding: null });
    logger.info('Got response ' + response.body.length);
    resolve(response.body);
  });
}

function getPictureExif(pictureData) {
  return new Promise(async function(resolve, reject) {
    try {
      logger.info('Extracting EXIF');
      var parser = exifparser.create(pictureData);
      var result = parser.parse();
      resolve(result);
    } catch (error) {
        console.log('Error: ' + error.message);
    }
  });
}
<commit_msg>Adds retry when flickr fails<commit_after>const _ = require("lodash");
const got = require('got');
const Flickr = require("flickrapi");
const exifparser = require('exif-parser');
var logger;
const config = require('config');
var watcher
const flickrOptions = {
      api_key: config.get('api_key'),
      secret: config.get('secret'),
      user_id: config.get('user_id'),
      access_token: config.get('access_token'),
      access_token_secret: config.get('access_token_secret')
    };
const PubSub = require('pubsub-js');

module.exports = { handlePhotoRequest: handlePhotoRequest }

async function handlePhotoRequest(msg, data) {
  console.log('Photo loader has received a request.');
  try {
    let newPhoto = await getAGoodPhoto(data.logger);
    PubSub.publish( 'photosArrivals', newPhoto)
  } catch(err) {
    data.logger.error('Photo retrieval failure', err);
    if(err.startsWith('Photo retrieval failure could not parse body as JSON')) {
      logger.warn('Error while calling flickr. Non JSON response.  They\'re probably down. Retrying in 5 seconds.');
      setTimeout(() => { PubSub.publish( 'photosRequests', { logger: data.logger } ) },5000);
    } else {
      process.exit(1);
    }

  }
}

async function getAGoodPhoto(winston) {
  logger = winston;
  return new Promise(async function(resolve, reject) {
    try  {
      let flickrConnection = await getFlickrConnection();
      let userids = config.get('users');

      let userId = userids[_.random(0, userids.length - 1, false)];

      let totatNumberOfPhotos = await getTotalNumberOfPhotos(flickrConnection, userId);
      let aGoodPictureData = undefined;
      let aGoodPictureBits = undefined;
      let aGoodPictureExif = undefined;
      while (aGoodPictureData == undefined) {
        let aRandomPictureData = await getARandomPictureData(flickrConnection, userId, totatNumberOfPhotos)
        let aRandomPictureSizes = await getPictureAvailableSizes(flickrConnection, aRandomPictureData, userId)
        var aRandomPictureOriginalSize = _.find(aRandomPictureSizes.sizes.size, { label: "Original"});
        if (await pictureDataIsGood(aRandomPictureData, aRandomPictureOriginalSize)) {
          aGoodPictureData = aRandomPictureData;
          aGoodPictureBits = await getPictureBits(aRandomPictureOriginalSize.source);
          aGoodPictureExif = await getPictureExif(aGoodPictureBits);
        }
        else {
          logger.info('Picture ' + aRandomPictureData.photos.photo[0].title + ' is not good.  Trying another.')
        }
      }

      logger.info('Good picture ready');

      resolve(
        {
          photoInfo: aGoodPictureData,
          photoExif: aGoodPictureExif,
          photoData: Buffer.from(aGoodPictureBits).toString('base64')
        });
    } catch (err) {
      reject(err);
    }
  });
}

async function getFlickrConnection() {
  return new Promise(async function(resolve, reject) {
    Flickr.authenticate(flickrOptions, function(error, flickr) {
      if(error)
        reject(error)
      else
        resolve(flickr)
    });
  });
}

async function getTotalNumberOfPhotos(flickrConnection, userId) {
  return new Promise(async function(resolve, reject) {
      flickrConnection.people.getPhotos({ user_id: userId, authenticated: true, privacy_filter: 4, per_page: 1}, function(err, result) {
        if(err)
          reject(err);
        else
          resolve(result.photos.total);
      });
  });
}

async function getARandomPictureData(flickrConnection, userId, totatNumberOfPhotos) {
  return new Promise(async function(resolve, reject) {
    var randomPhotoNumber = Math.floor(Math.random() * totatNumberOfPhotos);
    try{
      //if(flickr.options && flickr.options.users && flickr.options.users.length)
      //  user = flickr.options.users[0];

      flickrConnection.photos.search({
        user_id: userId,
        authenticated: true,
        per_page: 1,
        page: randomPhotoNumber
      }, function(err, searchResult) {
        resolve(searchResult);
      });
    } catch (err)  {
      logger.error('Failed to flickr search', err)
      reject(err)
    }
  })
}

async function getPictureAvailableSizes(flickrConnection, flickrPhotoSearchResult, userId) {
  return new Promise(async function(resolve, reject) {
    flickrConnection.photos.getSizes({
      user_id: userId,
      authenticated: true,
      photo_id: flickrPhotoSearchResult.photos.photo[0].id
    }, function(err, availableSizes) {
        if(err)
          reject(err);
        else
          resolve(availableSizes);
    });
  });
}

async function pictureDataIsGood(flickrPhotoSearchResult, originalPhotoData) {
  return new Promise(async function(resolve, reject) {
    let photoIsGood = flickrPhotoSearchResult.photos.photo[0].ispublic || flickrPhotoSearchResult.photos.photo[0].isfamily || flickrPhotoSearchResult.photos.photo[0].isfriend;
    photoIsGood = photoIsGood && originalPhotoData.media == "photo";
    if(photoIsGood) {
      resolve(true)
    } else {
      resolve(false);
    }
  })
}

async function getPictureBits(url) {
  return new Promise(async function(resolve, reject) {
    logger.info('Fetching ' + url);
    var response = await got(url, { encoding: null });
    logger.info('Got response ' + response.body.length);
    resolve(response.body);
  });
}

function getPictureExif(pictureData) {
  return new Promise(async function(resolve, reject) {
    try {
      logger.info('Extracting EXIF');
      var parser = exifparser.create(pictureData);
      var result = parser.parse();
      resolve(result);
    } catch (error) {
        console.log('Error: ' + error.message);
    }
  });
}
<|endoftext|>


<commit_before>// class Stage
// express中叫 layer, 这里则称为 stage（阶段），代表某个处理阶段
const concat = Array.prototype.concat;
function Stage(stages) {
  const befores = {};
  const afters = {};

  this.stages = this.actions = stages;
  this.stageNames = stages.map(stage => {
    const name = stage.name;

    befores[name] = [];
    afters[name] = [];

    return name;
  });
  this.befores = befores;
  this.afters = afters;
  this.props = {};
}

['before', 'after'].map(filterName => {
  Stage.prototype[filterName] = function filter(stageName, action) {
    const propname = `${filterName}s`;
    const filters = this[propname][stageName]; // this.befores[stagesName];

    if (! filters) {
      throw new Error(`Stage ${name} does not exist`);
    }

    filters.push(action);

    this.merge();

    return this;
  };

  return filterName;
});
Stage.prototype.merge = function merge() {
  // 合并所有的filter、stage
  this.actions = [];
  this.stages.map((stage, i) => {
    const name = this.stageNames[i];
    const befores = this.befores[name];
    const afters = this.afters[name];
    this.actions = concat.call(this.actions, befores, [stage], afters);

    return null;
  });
};
Stage.prototype.set = function set(name, value) {
  this.props[name] = value;
  return this;
};
Stage.prototype.get = function get(name) {
  return this.props[name];
};
Stage.prototype.handle = function handle(req, res, next) {
  const stage = this;
  const actions = this.actions;
  const originNext = next;
  const startIndex = 0;
  // 特别注意，nextStage不应改变全局变量
  function nextStage(error) {
    // 已响应了客户端，则不再继续任何处理
    if (res.hasSent || res.headersSent) {
      return;
    }

    // res.forward会产生多个流程，需打断原流程
    if (this.pathname !== req.pathname) {
      return;
    }

    if (error) {
      originNext(error);
      return;
    }

    const prevIndex = req.stageIndex;
    const stageIndex = prevIndex + 1;
    const isLast = stageIndex === actions.length;

    if (isLast) {
      originNext();
      return;
    }

    req.stageIndex = stageIndex;

    invokeAction(stageIndex); // eslint-disable-line no-use-before-define
  }
  // 提供跳过stage处理流程的功能
  nextStage.origin = originNext;

  function invokeAction(stageIndex) {
    actions[stageIndex].call(stage, req, res, nextStage.bind({
      'pathname': req.pathname, stageIndex
    }));
  }

  // 添加扩展属性
  // 增加一个pathname自定义属性，用于取代req.path
  // pathname可实现forward功能
  req.pathname = req.path.replace(/\/+/g, '/'); // 纠正多个/的问题
  req.stageIndex = startIndex;
  req.stage = res.stage = stage;

  res.apiData = {};
  res.apiInfo = {};
  res.forward = pathname => {
    if (pathname === req.path) {
      const error = new Error('foward path cannot be the same as req.path!');
      originNext(error);
      return;
    }

    res.forwardSent = true;
    req.pathname = pathname;
    req.stageIndex = 0;
    // 让转发前的流程先执行并在下一个next时终止
    // 然后继续转发后的流程
    // 这样可以避免两个流程的变量污染
    process.nextTick(() => {
      invokeAction(req.stageIndex);
    });
  };

  invokeAction(startIndex);
};

module.exports = Stage;
<commit_msg>add next.to() and fix next.origin<commit_after>// class Stage
// express中叫 layer, 这里则称为 stage（阶段），代表某个处理阶段
const concat = Array.prototype.concat;
function Stage(stages) {
  const befores = {};
  const afters = {};

  this.stages = this.actions = stages;
  this.stageNames = stages.map(stage => {
    const name = stage.name;

    befores[name] = [];
    afters[name] = [];

    return name;
  });
  this.befores = befores;
  this.afters = afters;
  this.props = {};
}

['before', 'after'].map(filterName => {
  Stage.prototype[filterName] = function filter(stageName, action) {
    const propname = `${filterName}s`;
    const filters = this[propname][stageName]; // this.befores[stagesName];

    if (! filters) {
      throw new Error(`Stage ${name} does not exist`);
    }

    filters.push(action);

    this.merge();

    return this;
  };

  return filterName;
});
Stage.prototype.merge = function merge() {
  // 合并所有的filter、stage
  this.actions = [];
  this.stages.map((stage, i) => {
    const name = this.stageNames[i];
    const befores = this.befores[name];
    const afters = this.afters[name];
    this.actions = concat.call(this.actions, befores, [stage], afters);

    return null;
  });
};
Stage.prototype.set = function set(name, value) {
  this.props[name] = value;
  return this;
};
Stage.prototype.get = function get(name) {
  return this.props[name];
};
Stage.prototype.handle = function handle(req, res, originNext) {
  const stage = this;
  const actions = this.actions;
  const startIndex = 0;

  // 执行指定名称的action
  function to(actionName) {
    const index = actions.findIndex(action => action.name === actionName);

    if (!~index) {
      throw new Error(`Action ${actionName} does not exist!`);
    }

    invokeAction(index);
  }

  // 特别注意，nextStage不应改变全局变量
  function nextStage(error) {
    // 已响应了客户端，则不再继续任何处理
    if (res.hasSent || res.headersSent) {
      return;
    }

    // res.forward会产生多个流程，需打断原流程
    if (this.pathname !== req.pathname) {
      return;
    }

    if (error) {
      originNext(error);
      return;
    }

    const prevIndex = req.stageIndex;
    const stageIndex = prevIndex + 1;
    const isLast = stageIndex === actions.length;

    if (isLast) {
      originNext();
      return;
    }

    req.stageIndex = stageIndex;

    invokeAction(stageIndex); // eslint-disable-line no-use-before-define
  }

  function invokeAction(stageIndex) {
    const next = nextStage.bind({
      'pathname': req.pathname, stageIndex
    });
    // 提供跳过stage处理流程的功能
    next.origin = originNext;
    next.to = to;

    return actions[stageIndex].call(stage, req, res, next);
  }

  // 添加扩展属性
  // 增加一个pathname自定义属性，用于取代req.path
  // pathname可实现forward功能
  req.pathname = req.path.replace(/\/+/g, '/'); // 纠正多个/的问题
  req.stageIndex = startIndex;
  req.stage = res.stage = stage;

  res.apiData = {};
  res.apiInfo = {};
  res.forward = pathname => {
    if (pathname === req.path) {
      const error = new Error('foward path cannot be the same as req.path!');
      originNext(error);
      return;
    }

    res.forwardSent = true;
    req.pathname = pathname;
    req.stageIndex = 0;
    // 让转发前的流程先执行并在下一个next时终止
    // 然后继续转发后的流程
    // 这样可以避免两个流程的变量污染
    process.nextTick(() => {
      invokeAction(req.stageIndex);
    });
  };

  invokeAction(startIndex);
};

module.exports = Stage;
<|endoftext|>


<commit_before>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        var directives = {};
        this.$el = document.getElementById(_el);
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        // Change state when $data is changed
        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        // Utility: Raw Attributes -> Key Value Pairs
        var extractAttrs = function(node) {
          var attrs = {};
          if(!node.attributes) return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        // Utility: Compile a template and return html
        var compileTemplate = function(template, data) {
          var code = template,
              re = /{{([.]?\w+)}}/gi;
          code.replace(re, function(match, p) {
            code = code.replace(match, "` + data." + p + " + `");
          });
          var compile = new Function("data", "var out = `" + code + "`; return out");
          return compile(data);
        }

        // Utility: Create Elements Recursively For all Children
        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, extractAttrs(child), child));
          }
          return recursiveChildrenArr;
        }

        // Utility: Create Virtual DOM Instance
        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, extractAttrs(node), node);
          this.dom = vdom;
        }

        // Build the DOM with $data
        this.build = function(children) {
          for(var i = 0; i < children.length; i++) {
            var el = children[i];

            if(el.type === "#text") {
              el.node.textContent = compileTemplate(el.val, this.$data);
            } else {
              for(var prop in el.props) {
                var propVal = el.props[prop];
                if(directives[prop]) {
                  directives[prop](el.node);
                }
                el.node.setAttribute(prop, compileTemplate(propVal, this.$data));
              }
            }

            this.build(el.children);
          }
        }

        // Create Virtual DOM Object from Params
        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        // Set any value in $data
        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        // Get any value in $data
        this.get = function(key) {
          return this.$data[key];
        }

        // Make AJAX GET/POST requests
        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        // Call a method defined in _methods
        this.method = function(method) {
          _methods[method]();
        }

        // Directive Initialization
        this.directive = function(name, action) {
          directives['m-' + name] = action;
        }

        // Initialize
        this.createVirtualDOM(this.$el);
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<commit_msg>take out moon prefix<commit_after>"use strict";
(function(window) {
    function Moon(opts) {
        var _el = opts.el;
        var _data = opts.data;
        var _methods = opts.methods;
        var directives = {};
        this.$el = document.getElementById(_el);
        this.dom = {type: this.$el.nodeName, children: [], node: this.$el};

        // Change state when $data is changed
        Object.defineProperty(this, '$data', {
            get: function() {
                return _data;
            },
            set: function(value) {
                _data = value;
                this.build(this.dom.children);
            }
        });

        // Utility: Raw Attributes -> Key Value Pairs
        var extractAttrs = function(node) {
          var attrs = {};
          if(!node.attributes) return attrs;
          var rawAttrs = node.attributes;
          for(var i = 0; i < rawAttrs.length; i++) {
            attrs[rawAttrs[i].name] = rawAttrs[i].value
          }

          return attrs;
        }

        // Utility: Compile a template and return html
        var compileTemplate = function(template, data) {
          var code = template,
              re = /{{([.]?\w+)}}/gi;
          code.replace(re, function(match, p) {
            code = code.replace(match, "` + data." + p + " + `");
          });
          var compile = new Function("data", "var out = `" + code + "`; return out");
          return compile(data);
        }

        // Utility: Create Elements Recursively For all Children
        this.recursiveChildren = function(children) {
          var recursiveChildrenArr = [];
          for(var i = 0; i < children.length; i++) {
            var child = children[i];
            recursiveChildrenArr.push(this.createElement(child.nodeName, this.recursiveChildren(child.childNodes), child.textContent, extractAttrs(child), child));
          }
          return recursiveChildrenArr;
        }

        // Utility: Create Virtual DOM Instance
        this.createVirtualDOM = function(node) {
          var vdom = this.createElement(node.nodeName, this.recursiveChildren(node.childNodes), node.textContent, extractAttrs(node), node);
          this.dom = vdom;
        }

        // Build the DOM with $data
        this.build = function(children) {
          for(var i = 0; i < children.length; i++) {
            var el = children[i];

            if(el.type === "#text") {
              el.node.textContent = compileTemplate(el.val, this.$data);
            } else {
              for(var prop in el.props) {
                var propVal = el.props[prop];
                if(directives[prop]) {
                  directives[prop](el.node);
                }
                el.node.setAttribute(prop, compileTemplate(propVal, this.$data));
              }
            }

            this.build(el.children);
          }
        }

        // Create Virtual DOM Object from Params
        this.createElement = function(type, children, val, props, node) {
          return {type: type, children: children, val: val, props: props, node: node};
        }

        // Set any value in $data
        this.set = function(key, val) {
          this.$data[key] = val;
          this.build(this.dom.children);
        }

        // Get any value in $data
        this.get = function(key) {
          return this.$data[key];
        }

        // Make AJAX GET/POST requests
        this.ajax = function(method, url, params, cb) {
          var xmlHttp = new XMLHttpRequest();
          method = method.toUpperCase();
          if(typeof params === "function") {
            cb = params;
          }
          var urlParams = "?";
          if(method === "POST") {
            http.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            for(var param in params) {
              urlParams += param + "=" + params[param] + "&";
            }
          }
          xmlHttp.onreadystatechange = function() {
          if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
            cb(JSON.parse(xmlHttp.responseText));
          }
          xmlHttp.open(method, url, true);
          xmlHttp.send(method === "POST" ? urlParams : null);
        }

        // Call a method defined in _methods
        this.method = function(method) {
          _methods[method]();
        }

        // Directive Initialization
        this.directive = function(name, action) {
          directives[name] = action;
        }

        // Initialize
        this.createVirtualDOM(this.$el);
        this.build(this.dom.children);
    }

    window.Moon = Moon;
    window.$ = function(el) {
      el = document.querySelectorAll(el);
      return el.length === 1 ? el[0] : el;
    }

})(window);
<|endoftext|>