<commit_before>/*!
 * http://github.com/mckinney/lectric
 *
 * Copyright 2011, McKinney
 * Licensed under the MIT license.
 * http://github.com/mckinney/lectric/blob/master/LICENSE
 *
 * Author: Brett C. Buddin (http://github.com/brettbuddin)
 */

(function(window) {
  var ua = navigator.userAgent.toLowerCase();
  isWebkit = !!ua.match(/applewebkit/i);
  var supportsTouch = false;
  try {
    document.createEvent("TouchEvent");
    supportsTouch = true;
  } catch (e) {}

  var cssWithoutUnit = function(element, attribute) {
    var measure = element.css(attribute);
    return (measure !== undefined) ? parseInt(measure.replace('px', ''), 10) : 0;
  };

  var Position = function(x, y) {
    if (x && x.hasOwnProperty('x') && x.hasOwnProperty('y')) {
      x = x.x;
      y = x.y;
    }
    this.x = x;
    this.y = y;
  };
  Position.prototype = {
    difference: function(p) {
      return new Position(p.x - this.x, p.y - this.y);
    }
  };

  var Slider = function() {
    if (supportsTouch && isWebkit) {
      return new TouchSlider();
    } else {
      return new BaseSlider();
    }
  };

  var BaseSlider = function() {};

  // Initialize the BaseSlider.
  //
  // text - The String CSS selector of the slider container.
  // opts - The Map of extra parameters.
  //
  // Returns nothing.
  BaseSlider.prototype.init = function(target, opts) {
    this.opts = jQuery.extend({
      reverse: false,
      next: undefined,
      previous: undefined,
      itemWrapperClassName: 'items',
      itemClassName: 'item',
      limitLeft: false,
      limitRight: false,
      animateDuration: 400,
      hooks: {}
    }, opts);

    this.position = new Position(0, 0);
    this.startPosition = new Position(this.position);
    this.lastPosition = new Position(this.position);

    // Set up the styling of the slider
    var element = $('<div/>', {
      className: this.opts.itemWrapperClassName
    });
    element.css('width', '1000000px');

    var itemSelector = '.' + this.opts.itemClassName;
    var itemWrapperSelector = '.' + this.opts.itemWrapperClassName;

    $(target).css('overflow', 'hidden');
    $(target).find(itemSelector).css('float', 'left').wrapAll(element);
    this.element = $(target).find(itemWrapperSelector);
    this.element.itemSelector = itemSelector;
    this.element.itemWrapperSelector = itemWrapperSelector;

    var self = this;

    var type = supportsTouch ? 'touchstart' : 'click';
    $(this.opts.next).bind(type, function(e) {
      e.preventDefault();
      var page = self.page();
      self.to(page + 1);
      self.element.trigger('nextButton.lectric');
    });

    $(this.opts.previous).bind(type, function(e) {
      e.preventDefault();
      var page = self.page();
      self.to(page - 1);
      self.element.trigger('previousButton.lectric');
    });

    // Keep clicks from doing what they do if
    // we support touch on this device
    if (supportsTouch) {
      $(this.opts.next).click(function(e) {
        e.preventDefault();
      });

      $(this.opts.previous).click(function(e) {
        e.preventDefault();
      });
    }

    // Bind callbacks passed in at initialization
    $.each(this.opts.hooks, function(name, fn) {
      if ($.isArray(fn)) {
        $.each(fn, function(fn2) {
          self.subscribe(name, fn2);
        });
      } else {
        self.subscribe(name, fn);
      }
    });

    this.element.trigger('init.lectric');
  };

  // Update the current position of the slider.
  //
  // opts - The Map of extra parameters:
  //        animate - Boolean of whether or not to animate between two states.
  //        triggerMove - Boolean of whether or not to trigger the move hook.
  //
  // Returns nothing.
  BaseSlider.prototype.update = function(opts) {
    var options = jQuery.extend({animate: true, triggerMove: true}, opts);

    var self = this;
    var after = function() {
      self.element.trigger('animationEnd.lectric');
      $(this).dequeue();
    };

    if (options.animate) {
      this.element.animate({'margin-left': this.position.x + 'px'}, this.opts.animateDuration).queue(after);
    } else {
      this.element.css({'margin-left': this.position.x + 'px'}).queue(after);
    }

    if (options.triggerMove) { this.element.trigger('move.lectric'); }
  };

  // Subscribe a callback function to a hook.
  //
  // name - The String name of the hook.
  // fn - The Function callback to execute when the hook is triggered.
  //
  // Returns the Function callback that was bound to the hook.
  BaseSlider.prototype.subscribe = function(name, fn) {
    var self = this;
    var callback = function(e) {
      if (e.target == self.element[0]) {
        fn(self, e);
      }
    };

    this.element.bind(name + '.lectric', callback);
    return callback;
  };

  // Unsubscribe a callback function from a hook or unsubscribe all callbacks from a hook.
  //
  // name - The String name of the hook.
  // fn - The Function callback to execute when the hook is triggered.
  //
  // Returns nothing.
  BaseSlider.prototype.unsubscribe = function(name, fn) {
    if (typeof fn !== undefined && $.isFunction(fn)) {
      this.element.unbind(name + '.lectric', fn);
    } else {
      this.element.unbind(name + '.lectric');
    }
  };

  // Retrieve the current page of the slider.
  //
  // Returns the Integer page number of the slider.
  BaseSlider.prototype.page = function() {
    return Math.abs(Math.round(this.position.x / this.itemWidth()));
  };

  // Move to a specific page number.
  //
  // page - The Integer page number to move to.
  //
  // Returns nothing.
  BaseSlider.prototype.to = function(page) {
    var previous = this.position.x;
    this.position.x = this.limitXBounds(this.xForPage(page));
    if (this.position.x !== previous) {
      this.update();
    }
  };

  // Move to a specific item in the slider, regardless of its position.
  //
  // item - The DOM Reference of the item you'd like to move to.
  //
  // Returns nothing.
  BaseSlider.prototype.toItem = function(item) {
    var all = this.element.find(this.element.itemSelector);

    var i;
    var length = all.length;
    for (i = 0; i < length; i++) {
      if ($(all[i])[0] == item[0]) { this.to(i); }
    }
  };

  // Retrieve the current X position.
  //
  // page - The Integer page number.
  //
  // Returns the Integer X position of the slider.
  BaseSlider.prototype.xForPage = function(page) {
    var flip = (this.opts.reverse) ? 1 : -1;
    return flip * page * this.itemWidth();
  };

  // Retrieve the width of a single item (including margin-right and padding).
  //
  // Returns the Integer width of a single item.
  BaseSlider.prototype.itemWidth = function() {
    var first = this.element.find(this.element.itemSelector).eq(0);
    var padding = cssWithoutUnit(first, 'paddingRight') + cssWithoutUnit(first, 'paddingLeft');
    return cssWithoutUnit(first, 'marginRight') + padding + first.width();
  };

  // Retrieve number of items in the slider.
  //
  // Returns the Integer number of items.
  BaseSlider.prototype.itemCount = function() {
    return this.element.find(this.element.itemSelector).size();
  };

  // Constrain the X position to within the slider beginning and end.
  //
  // x - The Integer X position
  //
  // Returns the Integer X position after being constrained.
  BaseSlider.prototype.limitXBounds = function(x) {
    var itemWidth = this.itemWidth();
    var itemCount = this.itemCount();
    var totalWidth = itemWidth * itemCount;

    if (this.opts.reverse) {
      x = (x > totalWidth - itemWidth) ?  totalWidth - itemWidth : x;
      x = (x < 0) ? 0 : x;
    } else {
      x = (x < -totalWidth + itemWidth) ?  -totalWidth + itemWidth : x;
      x = (x > 0) ? 0 : x;
    }

    if ((this.position.x - x > 0 && this.opts.limitRight) ||
        (this.position.x - x < 0 && this.opts.limitRight)) {
      x = this.position.x;
    }

    return x;
  };

  var TouchSlider = function() {};
  TouchSlider.prototype = new BaseSlider();
  TouchSlider.superobject = BaseSlider.prototype;

  // Initialize the TouchSlider.
  //
  // text - The String CSS selector of the slider container.
  // opts - The Map of extra parameters.
  //
  // Returns nothing.
  TouchSlider.prototype.init = function(target, opts) {
    TouchSlider.superobject.init.call(this, target, opts);
    this.opts = $.extend({
      tossFunction: function(x, dx, dt) {
        return x + dx * 100 / dt;
      }
    }, this.opts);
    this.element.parent().addClass('lectric-slider-touch');

    this.gesturing = false;
    $(target)[0].addEventListener('touchstart', this, false);
    $(target)[0].addEventListener('webkitTransitionEnd', this, false);
  };

  // Proxy the events triggered on the element to another function.
  //
  // event - The Event triggered on the element
  //
  // Returns nothing.
  TouchSlider.prototype.handleEvent = function(event) {
    TouchEvents[event.type].call(this, event);
  };

  // Update the current position of the slider.
  //
  // opts - The Map of extra parameters:
  //        animate - Boolean of whether or not to animate between two states.
  //        triggerMove - Boolean of whether or not to trigger the move hook.
  //
  // Returns nothing.
  TouchSlider.prototype.update = function(opts) {
    var options = jQuery.extend({animate: true, triggerMove: true}, opts);
    if (options.animate) { this.decayOn(); }
    this.element.css({'-webkit-transform': 'translate3d(' + this.position.x + 'px, 0, 0)'});

    if (options.triggerMove) { this.element.trigger('move.lectric'); }
  };

  // Turn off CSS3 animation decay.
  //
  // Returns nothing.
  TouchSlider.prototype.decayOff = function() {
    this.element.css({'-webkit-transition-duration': '0s'});
    this.element.css({'-webkit-transition-property': 'none'});
  };

  // Turn on CSS3 animation decay.
  //
  // Returns nothing.
  TouchSlider.prototype.decayOn = function() {
    var duration = this.opts.animateDuration / 1000;
    this.element.css({'-webkit-transition-duration': duration + 's'});
    this.element.css({'-webkit-transition-property': '-webkit-transform'});
  };

  var TouchEvents = {
    click: function(e) {
      if (this.moved) { e.preventDefault(); }
      this.element[0].removeEventListener('click', this, false);
      return false;
    },

    touchstart: function(e) {
      this.currentTarget = e.currentTarget;
      this.startPosition.x = e.touches[0].pageX - this.position.x;
      this.startPosition.y = e.touches[0].pageY - this.position.y;
      this.moved = false;

      window.addEventListener('gesturestart', this, false);
      window.addEventListener('gestureend', this, false);
      window.addEventListener('touchmove', this, false);
      window.addEventListener('touchend', this, false);
      this.element[0].addEventListener('click', this, false);

      this.decayOff();

      this.element.trigger('start.lectric');
    },

    touchmove: function(e) {
      if (this.gesturing) { return false; }

      if (!this.moved) {
        var deltaY = e.touches[0].pageY - this.startPosition.y;
        var deltaX = e.touches[0].pageX - this.startPosition.x;
        if (Math.abs(deltaY) < 15) {
          e.preventDefault();
        }

        this.element.trigger('firstMove.lectric');
      }

      this.moved = true;
      this.lastPosition.x = this.position.x;
      this.lastPosition.y = this.position.y;
      this.lastMoveTime = new Date();

      this.position.x = this.limitXBounds(e.touches[0].pageX - this.startPosition.x);

      this.update({animate: false});
    },

    touchend: function(e) {
      window.removeEventListener('gesturestart', this, false);
      window.removeEventListener('gestureend', this, false);
      window.removeEventListener('touchmove', this, false);
      window.removeEventListener('touchend', this, false);

      if (this.moved) {
        var dx = this.position.x - this.lastPosition.x;
        var dt = (new Date()) - this.lastMoveTime + 1;

        var tossedX = this.limitXBounds(this.opts.tossFunction(this.position.x, dx, dt));
        var width = this.itemWidth();

        // Find the nearest page
        this.position.x = Math.round(this.position.x / width) * width;

        this.update();
        this.element.trigger('end.lectric');
      } else {
        this.element.trigger('endNoMove.lectric');
      }

      this.currentTarget = undefined;
    },

    gesturestart: function(e) {
      this.gesturing = true;
    },

    gestureend: function(e) {
      this.gesturing = false;
    },

    webkitTransitionEnd: function(e) {
      this.element.trigger('animationEnd.lectric');
    }
  };

  var Lectric = {};
  Lectric.Slider = Slider;
  Lectric.BaseSlider = BaseSlider;
  Lectric.TouchSlider = TouchSlider;
  window.Lectric = Lectric;
})(window);
<commit_msg>Put the comments back on top<commit_after>/*!
 * Lectric
 * http://github.com/mckinney/lectric
 *
 * Copyright 2011, McKinney
 * Licensed under the MIT license.
 * http://github.com/mckinney/lectric/blob/master/LICENSE
 *
 * Author: Brett C. Buddin (http://github.com/brettbuddin)
 */

(function(window) {
  var ua = navigator.userAgent.toLowerCase();
  isWebkit = !!ua.match(/applewebkit/i);
  var supportsTouch = false;
  try {
    document.createEvent("TouchEvent");
    supportsTouch = true;
  } catch (e) {}

  var cssWithoutUnit = function(element, attribute) {
    var measure = element.css(attribute);
    return (measure !== undefined) ? parseInt(measure.replace('px', ''), 10) : 0;
  };

  var Position = function(x, y) {
    if (x && x.hasOwnProperty('x') && x.hasOwnProperty('y')) {
      x = x.x;
      y = x.y;
    }
    this.x = x;
    this.y = y;
  };
  Position.prototype = {
    difference: function(p) {
      return new Position(p.x - this.x, p.y - this.y);
    }
  };

  var Slider = function() {
    if (supportsTouch && isWebkit) {
      return new TouchSlider();
    } else {
      return new BaseSlider();
    }
  };

  var BaseSlider = function() {};

  // Initialize the BaseSlider.
  //
  // text - The String CSS selector of the slider container.
  // opts - The Map of extra parameters.
  //
  // Returns nothing.
  BaseSlider.prototype.init = function(target, opts) {
    this.opts = jQuery.extend({
      reverse: false,
      next: undefined,
      previous: undefined,
      itemWrapperClassName: 'items',
      itemClassName: 'item',
      limitLeft: false,
      limitRight: false,
      animateDuration: 400,
      hooks: {}
    }, opts);

    this.position = new Position(0, 0);
    this.startPosition = new Position(this.position);
    this.lastPosition = new Position(this.position);

    // Set up the styling of the slider
    var element = $('<div/>', {
      className: this.opts.itemWrapperClassName
    });
    element.css('width', '1000000px');

    var itemSelector = '.' + this.opts.itemClassName;
    var itemWrapperSelector = '.' + this.opts.itemWrapperClassName;

    $(target).css('overflow', 'hidden');
    $(target).find(itemSelector).css('float', 'left').wrapAll(element);
    this.element = $(target).find(itemWrapperSelector);
    this.element.itemSelector = itemSelector;
    this.element.itemWrapperSelector = itemWrapperSelector;

    var self = this;

    var type = supportsTouch ? 'touchstart' : 'click';
    $(this.opts.next).bind(type, function(e) {
      e.preventDefault();
      var page = self.page();
      self.to(page + 1);
      self.element.trigger('nextButton.lectric');
    });

    $(this.opts.previous).bind(type, function(e) {
      e.preventDefault();
      var page = self.page();
      self.to(page - 1);
      self.element.trigger('previousButton.lectric');
    });

    // Keep clicks from doing what they do if
    // we support touch on this device
    if (supportsTouch) {
      $(this.opts.next).click(function(e) {
        e.preventDefault();
      });

      $(this.opts.previous).click(function(e) {
        e.preventDefault();
      });
    }

    // Bind callbacks passed in at initialization
    $.each(this.opts.hooks, function(name, fn) {
      if ($.isArray(fn)) {
        $.each(fn, function(fn2) {
          self.subscribe(name, fn2);
        });
      } else {
        self.subscribe(name, fn);
      }
    });

    this.element.trigger('init.lectric');
  };

  // Update the current position of the slider.
  //
  // opts - The Map of extra parameters:
  //        animate - Boolean of whether or not to animate between two states.
  //        triggerMove - Boolean of whether or not to trigger the move hook.
  //
  // Returns nothing.
  BaseSlider.prototype.update = function(opts) {
    var options = jQuery.extend({animate: true, triggerMove: true}, opts);

    var self = this;
    var after = function() {
      self.element.trigger('animationEnd.lectric');
      $(this).dequeue();
    };

    if (options.animate) {
      this.element.animate({'margin-left': this.position.x + 'px'}, this.opts.animateDuration).queue(after);
    } else {
      this.element.css({'margin-left': this.position.x + 'px'}).queue(after);
    }

    if (options.triggerMove) { this.element.trigger('move.lectric'); }
  };

  // Subscribe a callback function to a hook.
  //
  // name - The String name of the hook.
  // fn - The Function callback to execute when the hook is triggered.
  //
  // Returns the Function callback that was bound to the hook.
  BaseSlider.prototype.subscribe = function(name, fn) {
    var self = this;
    var callback = function(e) {
      if (e.target == self.element[0]) {
        fn(self, e);
      }
    };

    this.element.bind(name + '.lectric', callback);
    return callback;
  };

  // Unsubscribe a callback function from a hook or unsubscribe all callbacks from a hook.
  //
  // name - The String name of the hook.
  // fn - The Function callback to execute when the hook is triggered.
  //
  // Returns nothing.
  BaseSlider.prototype.unsubscribe = function(name, fn) {
    if (typeof fn !== undefined && $.isFunction(fn)) {
      this.element.unbind(name + '.lectric', fn);
    } else {
      this.element.unbind(name + '.lectric');
    }
  };

  // Retrieve the current page of the slider.
  //
  // Returns the Integer page number of the slider.
  BaseSlider.prototype.page = function() {
    return Math.abs(Math.round(this.position.x / this.itemWidth()));
  };

  // Move to a specific page number.
  //
  // page - The Integer page number to move to.
  //
  // Returns nothing.
  BaseSlider.prototype.to = function(page) {
    var previous = this.position.x;
    this.position.x = this.limitXBounds(this.xForPage(page));
    if (this.position.x !== previous) {
      this.update();
    }
  };

  // Move to a specific item in the slider, regardless of its position.
  //
  // item - The DOM Reference of the item you'd like to move to.
  //
  // Returns nothing.
  BaseSlider.prototype.toItem = function(item) {
    var all = this.element.find(this.element.itemSelector);

    var i;
    var length = all.length;
    for (i = 0; i < length; i++) {
      if ($(all[i])[0] == item[0]) { this.to(i); }
    }
  };

  // Retrieve the current X position.
  //
  // page - The Integer page number.
  //
  // Returns the Integer X position of the slider.
  BaseSlider.prototype.xForPage = function(page) {
    var flip = (this.opts.reverse) ? 1 : -1;
    return flip * page * this.itemWidth();
  };

  // Retrieve the width of a single item (including margin-right and padding).
  //
  // Returns the Integer width of a single item.
  BaseSlider.prototype.itemWidth = function() {
    var first = this.element.find(this.element.itemSelector).eq(0);
    var padding = cssWithoutUnit(first, 'paddingRight') + cssWithoutUnit(first, 'paddingLeft');
    return cssWithoutUnit(first, 'marginRight') + padding + first.width();
  };

  // Retrieve number of items in the slider.
  //
  // Returns the Integer number of items.
  BaseSlider.prototype.itemCount = function() {
    return this.element.find(this.element.itemSelector).size();
  };

  // Constrain the X position to within the slider beginning and end.
  //
  // x - The Integer X position
  //
  // Returns the Integer X position after being constrained.
  BaseSlider.prototype.limitXBounds = function(x) {
    var itemWidth = this.itemWidth();
    var itemCount = this.itemCount();
    var totalWidth = itemWidth * itemCount;

    if (this.opts.reverse) {
      x = (x > totalWidth - itemWidth) ?  totalWidth - itemWidth : x;
      x = (x < 0) ? 0 : x;
    } else {
      x = (x < -totalWidth + itemWidth) ?  -totalWidth + itemWidth : x;
      x = (x > 0) ? 0 : x;
    }

    if ((this.position.x - x > 0 && this.opts.limitRight) ||
        (this.position.x - x < 0 && this.opts.limitRight)) {
      x = this.position.x;
    }

    return x;
  };

  var TouchSlider = function() {};
  TouchSlider.prototype = new BaseSlider();
  TouchSlider.superobject = BaseSlider.prototype;

  // Initialize the TouchSlider.
  //
  // text - The String CSS selector of the slider container.
  // opts - The Map of extra parameters.
  //
  // Returns nothing.
  TouchSlider.prototype.init = function(target, opts) {
    TouchSlider.superobject.init.call(this, target, opts);
    this.opts = $.extend({
      tossFunction: function(x, dx, dt) {
        return x + dx * 100 / dt;
      }
    }, this.opts);
    this.element.parent().addClass('lectric-slider-touch');

    this.gesturing = false;
    $(target)[0].addEventListener('touchstart', this, false);
    $(target)[0].addEventListener('webkitTransitionEnd', this, false);
  };

  // Proxy the events triggered on the element to another function.
  //
  // event - The Event triggered on the element
  //
  // Returns nothing.
  TouchSlider.prototype.handleEvent = function(event) {
    TouchEvents[event.type].call(this, event);
  };

  // Update the current position of the slider.
  //
  // opts - The Map of extra parameters:
  //        animate - Boolean of whether or not to animate between two states.
  //        triggerMove - Boolean of whether or not to trigger the move hook.
  //
  // Returns nothing.
  TouchSlider.prototype.update = function(opts) {
    var options = jQuery.extend({animate: true, triggerMove: true}, opts);
    if (options.animate) { this.decayOn(); }
    this.element.css({'-webkit-transform': 'translate3d(' + this.position.x + 'px, 0, 0)'});

    if (options.triggerMove) { this.element.trigger('move.lectric'); }
  };

  // Turn off CSS3 animation decay.
  //
  // Returns nothing.
  TouchSlider.prototype.decayOff = function() {
    this.element.css({'-webkit-transition-duration': '0s'});
    this.element.css({'-webkit-transition-property': 'none'});
  };

  // Turn on CSS3 animation decay.
  //
  // Returns nothing.
  TouchSlider.prototype.decayOn = function() {
    var duration = this.opts.animateDuration / 1000;
    this.element.css({'-webkit-transition-duration': duration + 's'});
    this.element.css({'-webkit-transition-property': '-webkit-transform'});
  };

  var TouchEvents = {
    click: function(e) {
      if (this.moved) { e.preventDefault(); }
      this.element[0].removeEventListener('click', this, false);
      return false;
    },

    touchstart: function(e) {
      this.currentTarget = e.currentTarget;
      this.startPosition.x = e.touches[0].pageX - this.position.x;
      this.startPosition.y = e.touches[0].pageY - this.position.y;
      this.moved = false;

      window.addEventListener('gesturestart', this, false);
      window.addEventListener('gestureend', this, false);
      window.addEventListener('touchmove', this, false);
      window.addEventListener('touchend', this, false);
      this.element[0].addEventListener('click', this, false);

      this.decayOff();

      this.element.trigger('start.lectric');
    },

    touchmove: function(e) {
      if (this.gesturing) { return false; }

      if (!this.moved) {
        var deltaY = e.touches[0].pageY - this.startPosition.y;
        var deltaX = e.touches[0].pageX - this.startPosition.x;
        if (Math.abs(deltaY) < 15) {
          e.preventDefault();
        }

        this.element.trigger('firstMove.lectric');
      }

      this.moved = true;
      this.lastPosition.x = this.position.x;
      this.lastPosition.y = this.position.y;
      this.lastMoveTime = new Date();

      this.position.x = this.limitXBounds(e.touches[0].pageX - this.startPosition.x);

      this.update({animate: false});
    },

    touchend: function(e) {
      window.removeEventListener('gesturestart', this, false);
      window.removeEventListener('gestureend', this, false);
      window.removeEventListener('touchmove', this, false);
      window.removeEventListener('touchend', this, false);

      if (this.moved) {
        var dx = this.position.x - this.lastPosition.x;
        var dt = (new Date()) - this.lastMoveTime + 1;

        var tossedX = this.limitXBounds(this.opts.tossFunction(this.position.x, dx, dt));
        var width = this.itemWidth();

        // Find the nearest page
        this.position.x = Math.round(this.position.x / width) * width;

        this.update();
        this.element.trigger('end.lectric');
      } else {
        this.element.trigger('endNoMove.lectric');
      }

      this.currentTarget = undefined;
    },

    gesturestart: function(e) {
      this.gesturing = true;
    },

    gestureend: function(e) {
      this.gesturing = false;
    },

    webkitTransitionEnd: function(e) {
      this.element.trigger('animationEnd.lectric');
    }
  };

  var Lectric = {};
  Lectric.Slider = Slider;
  Lectric.BaseSlider = BaseSlider;
  Lectric.TouchSlider = TouchSlider;
  window.Lectric = Lectric;
})(window);
<|endoftext|>


<commit_before>Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm");

const EXPORTED_SYMBOLS = ["SiteInfo", "SiteInfoSet"];

const DEFAULT_PREFIX = "__default__";
let evaluator = new XPathEvaluator();

function SiteInfo(data, url, doc) {
    this.data = data;
    this.url = url;
    // 念のため循環参照にならないよう弱参照で持つ
    // 本当に弱参照にしないといけないか (そうしないと
    // メモリリークが発生するか) は不明、未確認。
    this._win = doc.defaultView
                   .QueryInterface(Ci.nsISupportsWeakReference)
                   .GetWeakReference();
    this._isHTML = (doc.contentType === "text/html");
    this._exprs = {};
}

extend(SiteInfo.prototype, {
    get doc SI_get_doc() let (w = this.win) w && w.document,
    get win SI_get_win() {
        try {
            return this._win.QueryReferent(Ci.nsIDOMWindow);
        } catch (ex) {
            return null;
        }
    },

    query: function SI_query(key, context, resultType) {
        context = context || this.doc;
        let expr = this._exprs[key];
        if (!expr) {
            expr = this.data[key];
            if (!expr) return null;
            if (typeof expr === "string") {
                if (!this._isHTML)
                    expr = addDefaultPrefix(expr, DEFAULT_PREFIX);
                try {
                    expr = evaluator.createExpression(expr, this);
                } catch (ex) {
                    return null;
                }
            }
            this._exprs[key] = expr;
        }
        if (typeof expr === "function")
            return expr.call(this, context, resultType);

        resultType = resultType || XPathResult.FIRST_ORDERED_NODE_TYPE;
        if (typeof resultType !== "number") {
            switch (resultType.name || String(resultType)) {
            case "Number":  resultType = XPathResult.NUMBER_TYPE;  break;
            case "String":  resultType = XPathResult.STRING_TYPE;  break;
            case "Boolean": resultType = XPathResult.BOOLEAN_TYPE; break;
            case "Array":
                resultType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
                break;
            default:
                resultType = XPathResult.FIRST_ORDERED_NODE_TYPE;
            }
        }

        let result;
        try {
            result = expr.evaluate(context, resultType, null);
        } catch (ex) {
            return null;
        }
        switch (result.resultType) {
        case XPathResult.NUMBER_TYPE:  return result.numberValue;
        case XPathResult.STRING_TYPE:  return result.stringValue;
        case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
        case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
        case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
            return result;
        case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
        case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
            let nodes = [];
            for (let i = 0, n = result.snapshotLength; i < n; i++)
                nodes.push(result.snapshotItem(i));
            return nodes;
        case XPathResult.ANY_UNORDERED_NODE_TYPE:
        case XPathResult.FIRST_ORDERED_NODE_TYPE:
            return result.singleNodeValue;
        }
        return null;
    },

    queryAll: function SI_queryAll(key, context) {
        return this.query(key, context, Array) || [];
    },

    queryFirstString: function SI_queryFirstString(key, source) {
        let expr = this._exprs[key];
        if (!expr) {
            expr = this.data[key];
            if (typeof expr === "string")
                expr = new RegExp(expr);
            this._exprs[key] = expr;
        }
        if (typeof expr === "function" && expr.call)
            return expr.call(this, source);

        let match = expr.exec(source);
        if (!match) return null;
        for (let i = 1; i < match.length; i++)
            if (match[i])
                return match[i];
        return match[0];
    },

    lookupNamespaceURI: function SI_lookupNamespaceURI(prefix) {
        return (prefix === DEFAULT_PREFIX)
            ? this.doc.lookupNamespaceURI(null)
            : this.doc.lookupNamespaceURI(prefix);
    },
});

function SiteInfoSet(options) {
    this.matcher = options.matcher; // matcher: (siteinfo, url, doc) -> boolean
    this.sources = [];
    (options.sources || []).forEach(this.insertSource, this);
    SiteInfoSet._instances.push(this);
}

extend(SiteInfoSet.prototype, {
    dispose: function SIS_dispose() {
        this.matcher = null;
        this.sources = null;
        let i = SiteInfoSet._instances.indexOf(this);
        if (i !== -1)
            SiteInfoSet._instances.splice(i, 1);
    },

    insertSource: function SIS_insertSource(source, index) {
        source = extend({ updated: 0, items: [], format: '' }, source);
        if (source.file && !source.fileObject) {
            let file = DirectoryService.get('ProfD', Ci.nsIFile);
            file.append('hatenabookmark');
            if (!file.exists())
                file.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
            file.append(source.file);
            source.fileObject = file;
        }
        this._refreshSource(source);
        this._fetchSource(source);
        if (typeof index === 'undefined' || index < 0)
            this.sources.push(source);
        else
            this.sources.splice(index, 0, source);
    },

    update: function SIS_update(forceUpdate) {
        this.sources.forEach(function (source) {
            if (forceUpdate)
                this._refreshSource(source);
            this._fetchSource(source, forceUpdate);
        }, this);
    },

    get: function SIS_get(doc) {
        if (!doc) return null;
        let url = doc.defaultView.location.href;
        let matcher = this.matcher;
        for (let i = 0; i < this.sources.length; i++) {
            let source = this.sources[i];
            if (source.shouldUse && !source.shouldUse()) continue;
            let items = source.items;
            if (!items) continue;
            for (let j = 0; j < items.length; j++) {
                if (matcher(items[j], url, doc))
                    return new SiteInfo(items[j], url, doc);
            }
        }
        return null;
    },

    _refreshSource: function SIS__refreshSource(source) {
        if (!source.fileObject || !source.fileObject.exists()) return;
        let loader = getService('@mozilla.org/moz/jssubscript-loader;1',
                                Ci.mozIJSSubScriptLoader);
        let handler = getService('@mozilla.org/network/protocol;1?name=file',
                                 Ci.nsIFileProtocolHandler);
        let url = handler.getURLSpecFromFile(source.fileObject);
        let data = null;
        try {
            data = loader.loadSubScript(url);
            p('SiteInfoSet#_refreshSource: read from ' + source.fileObject.path);
        } catch (ex) {}
        [source.items, source.updated] = this._getItemsAndUpdated(data);
    },

    _writeSource: function SIS__writeSource(source) {
        if (!source.fileObject) return;
        let stream = Cc['@mozilla.org/network/file-output-stream;1'].
                     createInstance(Ci.nsIFileOutputStream);
        // We assume that the result of toSource() is an ASCII string.
        let data = { updated: source.updated, items: source.items }.toSource();
        try {
            stream.init(source.fileObject, 0x02 | 0x08 | 0x20, 0644, 0);
            stream.write(data, data.length);
            p('SiteInfoSet#_writeSource: written to ' + source.fileObject.path)
        } catch (ex) {
        } finally {
            stream.close();
        }
    },

    _fetchSource: function SIS__fetchSource(source, forceFetch) {
        if (!source.urls && !source.url) return;
        let updateInterval = PrefService.getIntPref('extensions.hatenabookmark.embed.siteInfoUpdateInterval') * 1000;
        if (!forceFetch && source.updated + updateInterval > Date.now()) return;
        let urls = [].concat(source.urls || source.url);
        this._doFetchSource(source, urls);
    },

    _doFetchSource: function SIS__doFetchSource(source, urls) {
        p('SiteInfoSet#_doFetchSource')
        let url = urls.shift();
        let xhr = new XMLHttpRequest();
        xhr.mozBackgroundRequest = true;
        xhr.open('GET', url);
        xhr.setRequestHeader('If-Modifield-Since',
                             new Date(source.updated).toUTCString());
        xhr.addEventListener('load', onFetch, false);
        xhr.addEventListener('error', onFetch, false);
        xhr.send(null);
        let timer = new BuiltInTimer({ observe: onTimeout }, 60 * 1000,
                                     Ci.nsITimer.TYPE_ONE_SHOT);

        let self = this;
        function onFetch(event) {
            p('SiteInfoSet#_doFetchSource onFetch: ' + url);
            let succeeded = false;
            if (event.type === 'load') {
                if (xhr.status === 200) {
                    if (xhr.responseText) {
                        [source.items, source.updated] =
                            self._getItemsAndUpdated(xhr.responseText,
                                                     source.format);
                        succeeded = true;
                    }
                } else if (xhr.status === 304) {
                    self.updated = Date.now();
                    succeeded = true;
                }
            }
            if (succeeded)
                self._writeSource(source);
            else if (urls.length)
                self._doFetchSource(source, urls);
            xhr.removeEventListener('load', onFetch, false);
            xhr.removeEventListener('error', onFetch, false);
            xhr = null;
        }

        function onTimeout() {
            if (xhr) {
                xhr.abort();
                onFetch({ type: 'timeout' });
            }
            timer.cancel();
            timer = null;
        }
    },

    _getItemsAndUpdated: function SIS__getItemsAndUpdated(rawData, format) {
        if (typeof rawData === 'string') {
            if (!(format in SiteInfoSet.converters))
                format = 'wedata';
            return [SiteInfoSet.converters[format](rawData), Date.now()];
        }
        if (Object.prototype.toString.call(rawData) === '[object Array]') {
            let items = (rawData.length && typeof rawData[0].data === 'object')
                        ? rawData.map(function (i) i.data) : rawData;
            return [items, Date.now()];
        }
        rawData = rawData || {};
        return [rawData.items || [], rawData.updated || Date.now()];
    },
});

extend(SiteInfoSet, {
    _instances: [],
    _timer: null,

    onTimer: function SIS_s_onTimer() {
        this._instances.forEach(function (set) set.update());
    },

    startObserving: function SIS_s_startObserving() {
        this._timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
        this._timer.init(this, 10 * 60 * 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
        ObserverService.addObserver(this, 'quit-application', false);
    },

    stopObserving: function SIS_s_stopObserving() {
        this._timer.cancel();
        this._timer = null;
        ObserverService.removeObserver(this, 'quit-application');
        this._instances.length = 0;
    },

    observe: function SIS_s_observe(subject, topic, data) {
        switch (topic) {
        case 'timer-callback':
            this.onTimer();
            break;
        case 'quit-application':
            this.stopObserving();
            break;
        }
    },

    createURLMatcher: function SIS_s_createURLMatcher(key) {
        return function SIS_urlMatcher(item, url) {
            let pattern = item._urlPattern;
            if (!pattern) {
                pattern = item[key];
                if (typeof pattern === 'string')
                    pattern = new RegExp(pattern);
                item._urlPattern = pattern;
            }
            return pattern.test(url);
        };
    },

    converters: {
        wedata: function SIS_wedata_converter(text) {
            let items = decodeJSON(text) || [];
            if (items.length && typeof items[0].data === 'object')
                items = items.map(function (i) i.data);
            return items;
        },
    },
});

SiteInfoSet.startObserving();
<commit_msg>nit: change debug message.<commit_after>Components.utils.import("resource://hatenabookmark/modules/00-utils.jsm");

const EXPORTED_SYMBOLS = ["SiteInfo", "SiteInfoSet"];

const DEFAULT_PREFIX = "__default__";
let evaluator = new XPathEvaluator();

function SiteInfo(data, url, doc) {
    this.data = data;
    this.url = url;
    // 念のため循環参照にならないよう弱参照で持つ
    // 本当に弱参照にしないといけないか (そうしないと
    // メモリリークが発生するか) は不明、未確認。
    this._win = doc.defaultView
                   .QueryInterface(Ci.nsISupportsWeakReference)
                   .GetWeakReference();
    this._isHTML = (doc.contentType === "text/html");
    this._exprs = {};
}

extend(SiteInfo.prototype, {
    get doc SI_get_doc() let (w = this.win) w && w.document,
    get win SI_get_win() {
        try {
            return this._win.QueryReferent(Ci.nsIDOMWindow);
        } catch (ex) {
            return null;
        }
    },

    query: function SI_query(key, context, resultType) {
        context = context || this.doc;
        let expr = this._exprs[key];
        if (!expr) {
            expr = this.data[key];
            if (!expr) return null;
            if (typeof expr === "string") {
                if (!this._isHTML)
                    expr = addDefaultPrefix(expr, DEFAULT_PREFIX);
                try {
                    expr = evaluator.createExpression(expr, this);
                } catch (ex) {
                    return null;
                }
            }
            this._exprs[key] = expr;
        }
        if (typeof expr === "function")
            return expr.call(this, context, resultType);

        resultType = resultType || XPathResult.FIRST_ORDERED_NODE_TYPE;
        if (typeof resultType !== "number") {
            switch (resultType.name || String(resultType)) {
            case "Number":  resultType = XPathResult.NUMBER_TYPE;  break;
            case "String":  resultType = XPathResult.STRING_TYPE;  break;
            case "Boolean": resultType = XPathResult.BOOLEAN_TYPE; break;
            case "Array":
                resultType = XPathResult.ORDERED_NODE_SNAPSHOT_TYPE;
                break;
            default:
                resultType = XPathResult.FIRST_ORDERED_NODE_TYPE;
            }
        }

        let result;
        try {
            result = expr.evaluate(context, resultType, null);
        } catch (ex) {
            return null;
        }
        switch (result.resultType) {
        case XPathResult.NUMBER_TYPE:  return result.numberValue;
        case XPathResult.STRING_TYPE:  return result.stringValue;
        case XPathResult.BOOLEAN_TYPE: return result.booleanValue;
        case XPathResult.UNORDERED_NODE_ITERATOR_TYPE:
        case XPathResult.ORDERED_NODE_ITERATOR_TYPE:
            return result;
        case XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE:
        case XPathResult.ORDERED_NODE_SNAPSHOT_TYPE:
            let nodes = [];
            for (let i = 0, n = result.snapshotLength; i < n; i++)
                nodes.push(result.snapshotItem(i));
            return nodes;
        case XPathResult.ANY_UNORDERED_NODE_TYPE:
        case XPathResult.FIRST_ORDERED_NODE_TYPE:
            return result.singleNodeValue;
        }
        return null;
    },

    queryAll: function SI_queryAll(key, context) {
        return this.query(key, context, Array) || [];
    },

    queryFirstString: function SI_queryFirstString(key, source) {
        let expr = this._exprs[key];
        if (!expr) {
            expr = this.data[key];
            if (typeof expr === "string")
                expr = new RegExp(expr);
            this._exprs[key] = expr;
        }
        if (typeof expr === "function" && expr.call)
            return expr.call(this, source);

        let match = expr.exec(source);
        if (!match) return null;
        for (let i = 1; i < match.length; i++)
            if (match[i])
                return match[i];
        return match[0];
    },

    lookupNamespaceURI: function SI_lookupNamespaceURI(prefix) {
        return (prefix === DEFAULT_PREFIX)
            ? this.doc.lookupNamespaceURI(null)
            : this.doc.lookupNamespaceURI(prefix);
    },
});

function SiteInfoSet(options) {
    this.matcher = options.matcher; // matcher: (siteinfo, url, doc) -> boolean
    this.sources = [];
    (options.sources || []).forEach(this.insertSource, this);
    SiteInfoSet._instances.push(this);
}

extend(SiteInfoSet.prototype, {
    dispose: function SIS_dispose() {
        this.matcher = null;
        this.sources = null;
        let i = SiteInfoSet._instances.indexOf(this);
        if (i !== -1)
            SiteInfoSet._instances.splice(i, 1);
    },

    insertSource: function SIS_insertSource(source, index) {
        source = extend({ updated: 0, items: [], format: '' }, source);
        if (source.file && !source.fileObject) {
            let file = DirectoryService.get('ProfD', Ci.nsIFile);
            file.append('hatenabookmark');
            if (!file.exists())
                file.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
            file.append(source.file);
            source.fileObject = file;
        }
        this._refreshSource(source);
        this._fetchSource(source);
        if (typeof index === 'undefined' || index < 0)
            this.sources.push(source);
        else
            this.sources.splice(index, 0, source);
    },

    update: function SIS_update(forceUpdate) {
        this.sources.forEach(function (source) {
            if (forceUpdate)
                this._refreshSource(source);
            this._fetchSource(source, forceUpdate);
        }, this);
    },

    get: function SIS_get(doc) {
        if (!doc) return null;
        let url = doc.defaultView.location.href;
        let matcher = this.matcher;
        for (let i = 0; i < this.sources.length; i++) {
            let source = this.sources[i];
            if (source.shouldUse && !source.shouldUse()) continue;
            let items = source.items;
            if (!items) continue;
            for (let j = 0; j < items.length; j++) {
                if (matcher(items[j], url, doc))
                    return new SiteInfo(items[j], url, doc);
            }
        }
        return null;
    },

    _refreshSource: function SIS__refreshSource(source) {
        if (!source.fileObject || !source.fileObject.exists()) return;
        let loader = getService('@mozilla.org/moz/jssubscript-loader;1',
                                Ci.mozIJSSubScriptLoader);
        let handler = getService('@mozilla.org/network/protocol;1?name=file',
                                 Ci.nsIFileProtocolHandler);
        let url = handler.getURLSpecFromFile(source.fileObject);
        let data = null;
        try {
            data = loader.loadSubScript(url);
            p('SiteInfoSet#_refreshSource: read from ' + source.fileObject.path);
        } catch (ex) {}
        [source.items, source.updated] = this._getItemsAndUpdated(data);
    },

    _writeSource: function SIS__writeSource(source) {
        if (!source.fileObject) return;
        let stream = Cc['@mozilla.org/network/file-output-stream;1'].
                     createInstance(Ci.nsIFileOutputStream);
        // We assume that the result of toSource() is an ASCII string.
        let data = { updated: source.updated, items: source.items }.toSource();
        try {
            stream.init(source.fileObject, 0x02 | 0x08 | 0x20, 0644, 0);
            stream.write(data, data.length);
            p('SiteInfoSet#_writeSource: written to ' + source.fileObject.path)
        } catch (ex) {
        } finally {
            stream.close();
        }
    },

    _fetchSource: function SIS__fetchSource(source, forceFetch) {
        if (!source.urls && !source.url) return;
        let updateInterval = PrefService.getIntPref('extensions.hatenabookmark.embed.siteInfoUpdateInterval') * 1000;
        if (!forceFetch && source.updated + updateInterval > Date.now()) return;
        let urls = [].concat(source.urls || source.url);
        this._doFetchSource(source, urls);
    },

    _doFetchSource: function SIS__doFetchSource(source, urls) {
        p('SiteInfoSet#_doFetchSource')
        let url = urls.shift();
        let xhr = new XMLHttpRequest();
        xhr.mozBackgroundRequest = true;
        xhr.open('GET', url);
        xhr.setRequestHeader('If-Modifield-Since',
                             new Date(source.updated).toUTCString());
        xhr.addEventListener('load', onFetch, false);
        xhr.addEventListener('error', onFetch, false);
        xhr.send(null);
        let timer = new BuiltInTimer({ observe: onTimeout }, 60 * 1000,
                                     Ci.nsITimer.TYPE_ONE_SHOT);

        let self = this;
        function onFetch(event) {
            p('SiteInfoSet#_doFetchSource onFetch: [' + event.type + '] ' + url);
            let succeeded = false;
            if (event.type === 'load') {
                if (xhr.status === 200) {
                    if (xhr.responseText) {
                        [source.items, source.updated] =
                            self._getItemsAndUpdated(xhr.responseText,
                                                     source.format);
                        succeeded = true;
                    }
                } else if (xhr.status === 304) {
                    self.updated = Date.now();
                    succeeded = true;
                }
            }
            if (succeeded)
                self._writeSource(source);
            else if (urls.length)
                self._doFetchSource(source, urls);
            xhr.removeEventListener('load', onFetch, false);
            xhr.removeEventListener('error', onFetch, false);
            xhr = null;
        }

        function onTimeout() {
            if (xhr) {
                xhr.abort();
                onFetch({ type: 'timeout' });
            }
            timer.cancel();
            timer = null;
        }
    },

    _getItemsAndUpdated: function SIS__getItemsAndUpdated(rawData, format) {
        if (typeof rawData === 'string') {
            if (!(format in SiteInfoSet.converters))
                format = 'wedata';
            return [SiteInfoSet.converters[format](rawData), Date.now()];
        }
        if (Object.prototype.toString.call(rawData) === '[object Array]') {
            let items = (rawData.length && typeof rawData[0].data === 'object')
                        ? rawData.map(function (i) i.data) : rawData;
            return [items, Date.now()];
        }
        rawData = rawData || {};
        return [rawData.items || [], rawData.updated || Date.now()];
    },
});

extend(SiteInfoSet, {
    _instances: [],
    _timer: null,

    onTimer: function SIS_s_onTimer() {
        this._instances.forEach(function (set) set.update());
    },

    startObserving: function SIS_s_startObserving() {
        this._timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
        this._timer.init(this, 10 * 60 * 1000, Ci.nsITimer.TYPE_REPEATING_SLACK);
        ObserverService.addObserver(this, 'quit-application', false);
    },

    stopObserving: function SIS_s_stopObserving() {
        this._timer.cancel();
        this._timer = null;
        ObserverService.removeObserver(this, 'quit-application');
        this._instances.length = 0;
    },

    observe: function SIS_s_observe(subject, topic, data) {
        switch (topic) {
        case 'timer-callback':
            this.onTimer();
            break;
        case 'quit-application':
            this.stopObserving();
            break;
        }
    },

    createURLMatcher: function SIS_s_createURLMatcher(key) {
        return function SIS_urlMatcher(item, url) {
            let pattern = item._urlPattern;
            if (!pattern) {
                pattern = item[key];
                if (typeof pattern === 'string')
                    pattern = new RegExp(pattern);
                item._urlPattern = pattern;
            }
            return pattern.test(url);
        };
    },

    converters: {
        wedata: function SIS_wedata_converter(text) {
            let items = decodeJSON(text) || [];
            if (items.length && typeof items[0].data === 'object')
                items = items.map(function (i) i.data);
            return items;
        },
    },
});

SiteInfoSet.startObserving();
<|endoftext|>


<commit_before>'use strict';

// MODULES //

var tape = require( 'tape' );
var abs = require( 'math-abs' );
var ln = require( 'math-ln' );
var pow = require( 'math-power' );
var gammaln = require( './../lib' );

// CONSTANTS //

var PINF = require( 'const-pinf-float64' );
var NINF = require( 'const-ninf-float64' );

// FIXTURES //

var data1 = require( './fixtures/data1.json' );
var expected1 = require( './fixtures/expected1.json' );
var data2 = require( './fixtures/data2.json' );
var expected2 = require( './fixtures/expected2.json' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.ok( typeof gammaln === 'function', 'main export is a function' );
	t.end();
});

tape( 'if provided `NaN`, the function returns `NaN`', function test( t ) {
	var v = gammaln( NaN );
	t.ok( v !== v, 'returns NaN when provided a NaN' );
	t.end();
});

tape( 'the function returns `infinity` when provided `infinity`', function test( t ) {
	var v = gammaln( PINF );
	t.ok( v === PINF, 'returns +Inf when provided +Inf' );
	v = gammaln( NINF );
	t.ok( v === NINF, 'returns -Inf when provided -Inf' );
	t.end();
});

tape( 'the function returns `+infinity` when provided `0`' , function test( t ) {
	var v = gammaln( 0 );
	t.ok( v === PINF, 'returns +Inf when provided 0' );
	t.end();
});

tape( 'the function returns `+infinity` for x smaller than `-2^52`' , function test( t ) {
	var v = gammaln(  -pow( 2, 53 ) );
	t.ok( v === PINF, 'returns +Inf when provided 2^53' );
	t.end();
});

tape( 'the function returns `-ln(x)` for very small x' , function test( t ) {
	var x = 2e-90;
	var v = gammaln( x );
	t.ok( v === -ln( x ) );
	t.end();
});

tape( 'the function evaluates the natural logarithm of the gamma function (positive integers)', function test( t ) {
	var delta;
	var tol;
	var v;
	var i;

	for ( i = 0; i < data1.length; i++ ) {
		v = gammaln( data1[ i ] );
		delta = abs( v - expected1[ i ] );
		tol = 2.75e-14 * Math.max( 1, abs( v ), abs( expected1[ i ] ) );
		t.ok( delta <= tol, 'within tolerance. x: ' + data1[ i ] + '. Value: ' + v + '. Expected: ' + expected1[ i ] + '. Tolerance: ' + tol + '.' );
	}
	t.end();
});

tape( 'the function evaluates the natural logarithm of the gamma function (decimal values)', function test( t ) {
	var delta;
	var tol;
	var v;
	var i;

	for ( i = 0; i < data2.length; i++ ) {
		v = gammaln( data2[ i ] );
		delta = abs( v - expected2[ i ] );
		tol = 2.5e-12 * Math.max( 1, abs( v ), abs( expected2[ i ] ) );
		t.ok( delta <= tol, 'within tolerance. x: ' + data2[ i ] + '. Value: ' + v + '. Expected: ' + expected2[ i ] + '. Tolerance: ' + tol + '.' );
	}
	t.end();
});

tape( 'the function evaluates the natural logarithm of the gamma function for x > 2^58', function test( t ) {
	var x = pow( 2, 59 );
	var v = gammaln( x );
	t.ok( v === x * ( ln(x) - 1 ), 'returns x*(ln(x)-1) for x>2^58' );
	t.end();
});

tape( 'if provided a positive integer, the function returns the natural logarithm of the factorial of (n-1)', function test( t ) {
	t.equal( gammaln( 4 ), ln(6), 'returns ln(6)' );
	t.equal( gammaln( 5 ), ln(24), 'returns ln(24)' );
	t.equal( gammaln( 6 ), ln(120), 'returns ln(120)' );
	t.end();
});

tape( 'returns `+infinity` for `x=-2^51`', function test( t ) {
	var v = gammaln( -pow( 2, 51 ) );
	t.ok( v === PINF, 'returns +Infinity when provided x=-2^51' );
	t.end();
});
<commit_msg>Use equality test<commit_after>'use strict';

// MODULES //

var tape = require( 'tape' );
var abs = require( 'math-abs' );
var ln = require( 'math-ln' );
var pow = require( 'math-power' );
var gammaln = require( './../lib' );

// CONSTANTS //

var PINF = require( 'const-pinf-float64' );
var NINF = require( 'const-ninf-float64' );

// FIXTURES //

var data1 = require( './fixtures/data1.json' );
var expected1 = require( './fixtures/expected1.json' );
var data2 = require( './fixtures/data2.json' );
var expected2 = require( './fixtures/expected2.json' );

// TESTS //

tape( 'main export is a function', function test( t ) {
	t.equal( typeof gammaln, 'function', 'main export is a function' );
	t.end();
});

tape( 'if provided `NaN`, the function returns `NaN`', function test( t ) {
	var v = gammaln( NaN );
	t.ok( v !== v, 'returns NaN when provided a NaN' );
	t.end();
});

tape( 'the function returns `infinity` when provided `infinity`', function test( t ) {
	var v = gammaln( PINF );
	t.ok( v === PINF, 'returns +Inf when provided +Inf' );
	v = gammaln( NINF );
	t.ok( v === NINF, 'returns -Inf when provided -Inf' );
	t.end();
});

tape( 'the function returns `+infinity` when provided `0`' , function test( t ) {
	var v = gammaln( 0 );
	t.ok( v === PINF, 'returns +Inf when provided 0' );
	t.end();
});

tape( 'the function returns `+infinity` for x smaller than `-2^52`' , function test( t ) {
	var v = gammaln(  -pow( 2, 53 ) );
	t.ok( v === PINF, 'returns +Inf when provided 2^53' );
	t.end();
});

tape( 'the function returns `-ln(x)` for very small x' , function test( t ) {
	var x = 2e-90;
	var v = gammaln( x );
	t.ok( v === -ln( x ) );
	t.end();
});

tape( 'the function evaluates the natural logarithm of the gamma function (positive integers)', function test( t ) {
	var delta;
	var tol;
	var v;
	var i;

	for ( i = 0; i < data1.length; i++ ) {
		v = gammaln( data1[ i ] );
		delta = abs( v - expected1[ i ] );
		tol = 2.75e-14 * Math.max( 1, abs( v ), abs( expected1[ i ] ) );
		t.ok( delta <= tol, 'within tolerance. x: ' + data1[ i ] + '. Value: ' + v + '. Expected: ' + expected1[ i ] + '. Tolerance: ' + tol + '.' );
	}
	t.end();
});

tape( 'the function evaluates the natural logarithm of the gamma function (decimal values)', function test( t ) {
	var delta;
	var tol;
	var v;
	var i;

	for ( i = 0; i < data2.length; i++ ) {
		v = gammaln( data2[ i ] );
		delta = abs( v - expected2[ i ] );
		tol = 2.5e-12 * Math.max( 1, abs( v ), abs( expected2[ i ] ) );
		t.ok( delta <= tol, 'within tolerance. x: ' + data2[ i ] + '. Value: ' + v + '. Expected: ' + expected2[ i ] + '. Tolerance: ' + tol + '.' );
	}
	t.end();
});

tape( 'the function evaluates the natural logarithm of the gamma function for x > 2^58', function test( t ) {
	var x = pow( 2, 59 );
	var v = gammaln( x );
	t.ok( v === x * ( ln(x) - 1 ), 'returns x*(ln(x)-1) for x>2^58' );
	t.end();
});

tape( 'if provided a positive integer, the function returns the natural logarithm of the factorial of (n-1)', function test( t ) {
	t.equal( gammaln( 4 ), ln(6), 'returns ln(6)' );
	t.equal( gammaln( 5 ), ln(24), 'returns ln(24)' );
	t.equal( gammaln( 6 ), ln(120), 'returns ln(120)' );
	t.end();
});

tape( 'returns `+infinity` for `x=-2^51`', function test( t ) {
	var v = gammaln( -pow( 2, 51 ) );
	t.ok( v === PINF, 'returns +Infinity when provided x=-2^51' );
	t.end();
});
<|endoftext|>


<commit_before>var bulk = require('./bulk'),
    queue = require('./queue'),
    EventEmitter = require('events').EventEmitter,
    common = require('@screeps/common'),
    db = common.storage.db,
    env = common.storage.env,
    pubsub = common.storage.pubsub,
    config = Object.assign(common.configManager.config, {engine: new EventEmitter()}),
    q = require('q'),
    _ = require('lodash'),
    os = require('os'),
    zlib = require('zlib'),
    runtimeUserVm = require('./runtime/user-vm'),
    roomStatsUpdates = {},
    genericPool = require('generic-pool'),
    worldSize;

_.extend(config.engine, {
    driver: exports,
    mainLoopMinDuration: 200,
    mainLoopResetInterval: 5000,
    mainLoopCustomStage() {
        return q.when();
    },
    cpuMaxPerTick: 500,
    cpuBucketSize: 10000,
    customIntentTypes: {},
    historyChunkSize: 20,
    useSigintTimeout: false,
    reportMemoryUsageInterval: 0,
    enableInspector: false,
});

config.engine.on('playerSandbox', (sandbox) => {
    sandbox.run(`Game.shard = Object.create(null, {
        name: {
            value: "${os.hostname()}",
            writable: true,
            enumerable: true
        },
        type: {
            value: 'normal',
            writable: true,
            enumerable: true
        },
        ptr: {
            value: false,
            enumerable: true
        }
    });`);
});

exports.customObjectPrototypes = [];

Object.defineProperty(config.engine, 'registerCustomObjectPrototype', {
    value: function(objectType, name, opts) {
        if(!objectType) {
            throw new Error('No object type provided!');
        }
        if(!name) {
            throw new Error('No prototype name provided!');
        }
        exports.customObjectPrototypes.push({objectType, name, opts});
    }
});

exports.config = config.engine;

function checkNotificationOnline(userId) {
    return q.when(true); // TODO
}

function getAllTerrainData() {
    return env.get(env.keys.TERRAIN_DATA)
        .then(compressed => {
            const buf = Buffer.from(compressed, 'base64');
            return q.ninvoke(zlib, 'inflate', buf);
        })
        .then(data => JSON.parse(data));
}

exports.getAllTerrainData = getAllTerrainData;

const pathFinderFactory = require('./path-finder');
exports.pathFinder = pathFinderFactory.create(require('../native/build/Release/native'));

exports.connect = function(processType) {

    common.configManager.load();

    return common.storage._connect()
    .then(() => {

        if (processType == 'runner') {
            runtimeUserVm.init();
            pubsub.subscribe(pubsub.keys.RUNTIME_RESTART, () => {
                console.log('runtime restart signal');
                runtimeUserVm.clearAll();
            });
        }

        if (processType == 'runtime') {
        }

        if (processType == 'processor') {
            getAllTerrainData()
                .then(rooms => pathFinderFactory.init(require('../native/build/Release/native'), rooms));
        }

        if (processType == 'main') {
        }
    })
    .then(() => db.rooms.find({}, {_id: true}))
    .then(common.calcWorldSize)
    .then(_worldSize => worldSize = _worldSize)
    .then(() => {
        config.engine.emit('init', processType);
        return true;
    });
};

exports.getAllUsers = function() {
    return db.users.find({$and: [{active: {$ne: 0}}, {cpu: {$gt: 0}}]})
    .then((data) => {
        data.sort((a,b) => (b.lastUsedDirtyTime || 0) - (a.lastUsedDirtyTime || 0));

        return {ivm: data, legacy: []};
    })
};

exports.saveUserMemory = function(userId, memory, onlyInRoom) {

    if(memory.data.length > 2*1024*1024) {
        return q.reject('Script execution has been terminated: memory allocation limit reached');
    }

    if(onlyInRoom) {
        userId += ','+onlyInRoom;
    }

    return env.set(env.keys.MEMORY+userId, memory.data);
};

exports.saveUserMemorySegments = function(userId, segments) {

    if(Object.keys(segments).length > 0) {
        return env.hmset(env.keys.MEMORY_SEGMENTS+userId, segments);
    }
    return q.when();
};

exports.saveUserIntents = function(userId, intents) {
    var updates = [];
    for(var room in intents) {

        if(room == 'notify') {
            updates.push(checkNotificationOnline(userId)
                .then(() => {

                    if (intents.notify.length > 20) {
                        intents.notify = _.take(intents.notify, 20);
                    }

                    var promises = [q.when()];

                    intents.notify.forEach((i) => {
                        if (i.groupInterval < 0) {
                            i.groupInterval = 0;
                        }
                        if (i.groupInterval > 1440) {
                            i.groupInterval = 1440;
                        }
                        i.groupInterval *= 60 * 1000;
                        i.groupInterval = Math.floor(i.groupInterval);
                        var date = i.groupInterval ?
                            new Date(Math.ceil(new Date().getTime() / i.groupInterval) * i.groupInterval) :
                            new Date();

                        var message = (""+i.message).substring(0,500);

                        promises.push(db['users.notifications'].update({
                            $and: [
                                {user: userId},
                                {message},
                                {date: date.getTime()},
                                {type: 'msg'}
                            ]
                        }, {
                            $inc: {count: 1}
                        },
                        {upsert: true}));
                    });

                    return q.all(promises);
                }));
            continue;
        }

        if(room == 'global') {
            updates.push(db['users.intents'].insert({user: userId, intents: intents[room]}));
            continue;
        }

        updates.push(
        db['rooms.intents'].update({room}, {$merge: {users: {[userId]: {objects: intents[room]}}}}, {upsert: true}));
    }

    return q.all(updates);
};

exports.getAllRooms = function() {
    return db.rooms.find({active: true});
};

exports.getRoomIntents = function(roomId) {
    return db['rooms.intents'].findOne({room: roomId});
};

exports.getRoomObjects = function(roomId) {
    var result = {};
    return db['rooms.objects'].find({room: roomId})
        .then((objects) => {
            var users = {};
            result.objects = exports.mapById(objects, obj => {
                if(obj.user) {
                    users[obj.user] = true;
                }
            });
            users = Object.keys(users);
            if(users.length) {
                return db['users'].find({_id: {$in: users}})
            }
            else {
                return [];
            }
        })
        .then(users => {
            result.users = exports.mapById(users);
            return result;
        });
};

exports.getRoomFlags = function(roomId) {
    return db['rooms.flags'].find({room: roomId});
};

exports.getRoomTerrain = function(roomId) {
    return db['rooms.terrain'].find({room: roomId})
    .then((result) => exports.mapById(result));
};

exports.bulkObjectsWrite = function() {
    return bulk('rooms.objects');
};

exports.bulkFlagsWrite = function() {
    return bulk('rooms.flags');
};

exports.bulkUsersWrite = function() {
    return bulk('users');
};

exports.bulkRoomsWrite = function() {
    return bulk('rooms');
};

exports.bulkTransactionsWrite = function() {
    return bulk('transactions');
};

exports.bulkMarketOrders = function() {
    return bulk('market.orders');
};

exports.bulkMarketIntershardOrders = function() {
    return bulk('market.orders');
};

exports.bulkUsersMoney = function() {
    return bulk('users.money');
};

exports.bulkUsersResources = function() {
    return bulk('users.resources');
};

exports.bulkUsersPowerCreeps = function() {
    return bulk('users.power_creeps');
};

exports.clearRoomIntents = function(roomId) {
    return db['rooms.intents'].removeWhere({room: roomId});
};

exports.clearGlobalIntents = function() {
    return db['users.intents'].clear();
};

exports.mapById = function(array, fn) {
    return _.reduce(array, (result, i) => {
        result[i._id.toString()] = i;
        fn && fn(i);
        return result;
    }, {});
};

exports.notifyTickStarted = function() {
    return env.get(env.keys.MAIN_LOOP_PAUSED)
        .then(paused => {
            if(+paused) {
                return q.reject('Simulation paused');
            }
            return pubsub.publish(pubsub.keys.TICK_STARTED, "1");
        });
};

exports.notifyRoomsDone = function(gameTime) {
    return pubsub.publish(pubsub.keys.ROOMS_DONE, gameTime);
};

exports.sendConsoleMessages = function(userId, messages) {
    if(userId == '3') {
        if(messages.log.length) {
            console.log("Source Keeper console", messages.log);
        }
        return q.when();
    }
    if(userId == '2') {
        if(messages.log.length) {
            console.log("Invader console", messages.log);
        }
        return q.when();
    }
    return pubsub.publish(`user:${userId}/console`, JSON.stringify({messages, userId}));
};

exports.sendConsoleError = function(userId, error) {

    if(!error) {
        return q.when();
    }

    if(userId == '3') {
        console.log("Source Keeper error", _.isObject(error) && error.stack || error);
        return q.when();
    }

    if(userId == '2') {
        console.log("Invader error", _.isObject(error) && error.stack || error);
        return q.when();
    }

    error = error.toString();

    var user;

    db.users.findOne({_id: userId})
    .then((_user) => {
        user = _user;
        return checkNotificationOnline(user);
    })
    .then(() => {
        var interval = 30*60*1000;
        if(user.notifyPrefs && user.notifyPrefs.errorsInterval) {
            interval = user.notifyPrefs.errorsInterval * 60*1000;
        }
        var date = new Date(Math.ceil(new Date().getTime() / interval) * interval);

        db['users.notifications'].update({$and: [{user: userId}, {message: error}, {type: 'error'}, {date: {$lte: date.getTime()}}]},
        {$set: {user: userId, message: error, type: 'error', date: date.getTime()}, $inc: {count: 1}},
        {upsert: true});
    });

    return pubsub.publish(`user:${userId}/console`, JSON.stringify({userId, error}));
};

exports.getGameTime = function() {
    return common.getGametime();
};

exports.incrementGameTime = function() {
    return common.getGametime()
    .then(gameTime => env.set(env.keys.GAMETIME, gameTime+1));
};

exports.getRoomInfo = function(roomId) {
    return db.rooms.findOne({_id: roomId});
};

exports.saveRoomInfo = function(roomId, roomInfo) {
    return db.rooms.update({_id: roomId}, {$set: roomInfo});
};

exports.getInterRoom = () => {
    return q.all([
        common.getGametime(),
        db['rooms.objects'].find({$and: [{type: {$in: ['creep','powerCreep']}}, {interRoom: {$ne: null}}]}),
        db.rooms.find({status: 'normal'})
            .then((rooms) => exports.mapById(_.filter(rooms, i => !i.openTime || i.openTime < Date.now()))),
        db['rooms.objects'].find({type: {$in: ['terminal', 'powerSpawn', 'powerCreep']}}),
        q.all([
            db['market.orders'].find(),
            db['users.power_creeps'].find(),
            db['users.intents'].find()
        ]).then(result => db.users.find({_id: {$in: _.map(_.flatten(result), 'user')}})
            .then(users => ({
                users,
                orders: result[0],
                userPowerCreeps: result[1],
                userIntents: result[2],
                shardName: ''
            })))
    ]);
};

exports.setRoomStatus = (roomId, status) => {
    return db.rooms.update({_id: roomId}, {$set: {status}});
};

exports.sendNotification = (userId, message) => {
    return checkNotificationOnline(userId)
    .then(() => db['users.notifications'].update({
        user: userId,
        message,
        date: {$lte: Date.now()},
        type: 'msg'
    }, {
        $set: {
            user: userId,
            message,
            date: Date.now(),
            type: 'msg'
        },
        $inc: {count: 1}
    }, {upsert: true}));
};

exports.getRoomStatsUpdater = (room) => {
    return {
        inc(name, userId, amount) {
            roomStatsUpdates[room] = roomStatsUpdates[room] || {};
            roomStatsUpdates[room][userId] = roomStatsUpdates[room][userId] || {};
            roomStatsUpdates[room][userId][name] = roomStatsUpdates[room][userId][name] || 0;
            roomStatsUpdates[room][userId][name] += amount;
        }
    }
};

exports.roomsStatsSave = () => {
    // TODO
    return q.when();
};

exports.updateAccessibleRoomsList = () => {
    return db.rooms.find({status: 'normal'})
    .then((rooms) => {
        var list = _(rooms).filter(i => !i.openTime || i.openTime < Date.now()).map('_id').value();
        return env.set(env.keys.ACCESSIBLE_ROOMS, JSON.stringify(list));
    });
};

exports.saveIdleTime = (name, time) => {
    return q.when();
};

exports.mapViewSave = (roomId, mapView) => {
    return env.set(env.keys.MAP_VIEW+roomId, JSON.stringify(mapView));
};

exports.commitDbBulk = () => {
    return q.when();
};

exports.getWorldSize = () => {
    return worldSize;
};

exports.addRoomToUser = (roomId, user, bulk) => {
    if(!user.rooms || user.rooms.indexOf(roomId) == -1) {
        bulk.addToSet(user, 'rooms', roomId);
    }
};

exports.removeRoomFromUser = (roomId, user, bulk) => {
    if(user.rooms && user.rooms.indexOf(roomId) != -1) {
        bulk.pull(user, 'rooms', roomId);
    }
};

exports.bufferFromBase64 = (base64) => {
    return Buffer.from(base64, 'base64');
};

exports.startLoop = function(name, fn) {

    let counter = 0;

    const pool = genericPool.createPool({
        create() { return {name: name + (counter++)} },
        destroy() {}
    }, {
        max: process.env.RUNNER_THREADS || 2,
        min: 0
    });

    function loop() {
        pool.acquire().then(poolItem => {
            setTimeout(loop, 0);
            return fn().finally(() => {
                pool.release(poolItem);
            });
        });
    }

    loop();
};

exports.saveRoomEventLog = function saveRoomEventLog(roomId, eventLog) {
    return env.hset(env.keys.ROOM_EVENT_LOG, roomId, JSON.stringify(eventLog));
};

exports.makeRuntime = require('./runtime/make');

exports.history = require('./history');

exports.queue = queue;

exports.constants = config.common.constants;

process.on('disconnect', () => process.exit());
<commit_msg>refact: remove ivm users queue<commit_after>var bulk = require('./bulk'),
    queue = require('./queue'),
    EventEmitter = require('events').EventEmitter,
    common = require('@screeps/common'),
    db = common.storage.db,
    env = common.storage.env,
    pubsub = common.storage.pubsub,
    config = Object.assign(common.configManager.config, {engine: new EventEmitter()}),
    q = require('q'),
    _ = require('lodash'),
    os = require('os'),
    zlib = require('zlib'),
    runtimeUserVm = require('./runtime/user-vm'),
    roomStatsUpdates = {},
    genericPool = require('generic-pool'),
    worldSize;

_.extend(config.engine, {
    driver: exports,
    mainLoopMinDuration: 200,
    mainLoopResetInterval: 5000,
    mainLoopCustomStage() {
        return q.when();
    },
    cpuMaxPerTick: 500,
    cpuBucketSize: 10000,
    customIntentTypes: {},
    historyChunkSize: 20,
    useSigintTimeout: false,
    reportMemoryUsageInterval: 0,
    enableInspector: false,
});

config.engine.on('playerSandbox', (sandbox) => {
    sandbox.run(`Game.shard = Object.create(null, {
        name: {
            value: "${os.hostname()}",
            writable: true,
            enumerable: true
        },
        type: {
            value: 'normal',
            writable: true,
            enumerable: true
        },
        ptr: {
            value: false,
            enumerable: true
        }
    });`);
});

exports.customObjectPrototypes = [];

Object.defineProperty(config.engine, 'registerCustomObjectPrototype', {
    value: function(objectType, name, opts) {
        if(!objectType) {
            throw new Error('No object type provided!');
        }
        if(!name) {
            throw new Error('No prototype name provided!');
        }
        exports.customObjectPrototypes.push({objectType, name, opts});
    }
});

exports.config = config.engine;

function checkNotificationOnline(userId) {
    return q.when(true); // TODO
}

function getAllTerrainData() {
    return env.get(env.keys.TERRAIN_DATA)
        .then(compressed => {
            const buf = Buffer.from(compressed, 'base64');
            return q.ninvoke(zlib, 'inflate', buf);
        })
        .then(data => JSON.parse(data));
}

exports.getAllTerrainData = getAllTerrainData;

const pathFinderFactory = require('./path-finder');
exports.pathFinder = pathFinderFactory.create(require('../native/build/Release/native'));

exports.connect = function(processType) {

    common.configManager.load();

    return common.storage._connect()
    .then(() => {

        if (processType == 'runner') {
            runtimeUserVm.init();
            pubsub.subscribe(pubsub.keys.RUNTIME_RESTART, () => {
                console.log('runtime restart signal');
                runtimeUserVm.clearAll();
            });
        }

        if (processType == 'runtime') {
        }

        if (processType == 'processor') {
            getAllTerrainData()
                .then(rooms => pathFinderFactory.init(require('../native/build/Release/native'), rooms));
        }

        if (processType == 'main') {
        }
    })
    .then(() => db.rooms.find({}, {_id: true}))
    .then(common.calcWorldSize)
    .then(_worldSize => worldSize = _worldSize)
    .then(() => {
        config.engine.emit('init', processType);
        return true;
    });
};

exports.getAllUsers = function() {
    return db.users.find({$and: [{active: {$ne: 0}}, {cpu: {$gt: 0}}]})
    .then((data) => {
        data.sort((a,b) => (b.lastUsedDirtyTime || 0) - (a.lastUsedDirtyTime || 0));

        return data;
    })
};

exports.saveUserMemory = function(userId, memory, onlyInRoom) {

    if(memory.data.length > 2*1024*1024) {
        return q.reject('Script execution has been terminated: memory allocation limit reached');
    }

    if(onlyInRoom) {
        userId += ','+onlyInRoom;
    }

    return env.set(env.keys.MEMORY+userId, memory.data);
};

exports.saveUserMemorySegments = function(userId, segments) {

    if(Object.keys(segments).length > 0) {
        return env.hmset(env.keys.MEMORY_SEGMENTS+userId, segments);
    }
    return q.when();
};

exports.saveUserIntents = function(userId, intents) {
    var updates = [];
    for(var room in intents) {

        if(room == 'notify') {
            updates.push(checkNotificationOnline(userId)
                .then(() => {

                    if (intents.notify.length > 20) {
                        intents.notify = _.take(intents.notify, 20);
                    }

                    var promises = [q.when()];

                    intents.notify.forEach((i) => {
                        if (i.groupInterval < 0) {
                            i.groupInterval = 0;
                        }
                        if (i.groupInterval > 1440) {
                            i.groupInterval = 1440;
                        }
                        i.groupInterval *= 60 * 1000;
                        i.groupInterval = Math.floor(i.groupInterval);
                        var date = i.groupInterval ?
                            new Date(Math.ceil(new Date().getTime() / i.groupInterval) * i.groupInterval) :
                            new Date();

                        var message = (""+i.message).substring(0,500);

                        promises.push(db['users.notifications'].update({
                            $and: [
                                {user: userId},
                                {message},
                                {date: date.getTime()},
                                {type: 'msg'}
                            ]
                        }, {
                            $inc: {count: 1}
                        },
                        {upsert: true}));
                    });

                    return q.all(promises);
                }));
            continue;
        }

        if(room == 'global') {
            updates.push(db['users.intents'].insert({user: userId, intents: intents[room]}));
            continue;
        }

        updates.push(
        db['rooms.intents'].update({room}, {$merge: {users: {[userId]: {objects: intents[room]}}}}, {upsert: true}));
    }

    return q.all(updates);
};

exports.getAllRooms = function() {
    return db.rooms.find({active: true});
};

exports.getRoomIntents = function(roomId) {
    return db['rooms.intents'].findOne({room: roomId});
};

exports.getRoomObjects = function(roomId) {
    var result = {};
    return db['rooms.objects'].find({room: roomId})
        .then((objects) => {
            var users = {};
            result.objects = exports.mapById(objects, obj => {
                if(obj.user) {
                    users[obj.user] = true;
                }
            });
            users = Object.keys(users);
            if(users.length) {
                return db['users'].find({_id: {$in: users}})
            }
            else {
                return [];
            }
        })
        .then(users => {
            result.users = exports.mapById(users);
            return result;
        });
};

exports.getRoomFlags = function(roomId) {
    return db['rooms.flags'].find({room: roomId});
};

exports.getRoomTerrain = function(roomId) {
    return db['rooms.terrain'].find({room: roomId})
    .then((result) => exports.mapById(result));
};

exports.bulkObjectsWrite = function() {
    return bulk('rooms.objects');
};

exports.bulkFlagsWrite = function() {
    return bulk('rooms.flags');
};

exports.bulkUsersWrite = function() {
    return bulk('users');
};

exports.bulkRoomsWrite = function() {
    return bulk('rooms');
};

exports.bulkTransactionsWrite = function() {
    return bulk('transactions');
};

exports.bulkMarketOrders = function() {
    return bulk('market.orders');
};

exports.bulkMarketIntershardOrders = function() {
    return bulk('market.orders');
};

exports.bulkUsersMoney = function() {
    return bulk('users.money');
};

exports.bulkUsersResources = function() {
    return bulk('users.resources');
};

exports.bulkUsersPowerCreeps = function() {
    return bulk('users.power_creeps');
};

exports.clearRoomIntents = function(roomId) {
    return db['rooms.intents'].removeWhere({room: roomId});
};

exports.clearGlobalIntents = function() {
    return db['users.intents'].clear();
};

exports.mapById = function(array, fn) {
    return _.reduce(array, (result, i) => {
        result[i._id.toString()] = i;
        fn && fn(i);
        return result;
    }, {});
};

exports.notifyTickStarted = function() {
    return env.get(env.keys.MAIN_LOOP_PAUSED)
        .then(paused => {
            if(+paused) {
                return q.reject('Simulation paused');
            }
            return pubsub.publish(pubsub.keys.TICK_STARTED, "1");
        });
};

exports.notifyRoomsDone = function(gameTime) {
    return pubsub.publish(pubsub.keys.ROOMS_DONE, gameTime);
};

exports.sendConsoleMessages = function(userId, messages) {
    if(userId == '3') {
        if(messages.log.length) {
            console.log("Source Keeper console", messages.log);
        }
        return q.when();
    }
    if(userId == '2') {
        if(messages.log.length) {
            console.log("Invader console", messages.log);
        }
        return q.when();
    }
    return pubsub.publish(`user:${userId}/console`, JSON.stringify({messages, userId}));
};

exports.sendConsoleError = function(userId, error) {

    if(!error) {
        return q.when();
    }

    if(userId == '3') {
        console.log("Source Keeper error", _.isObject(error) && error.stack || error);
        return q.when();
    }

    if(userId == '2') {
        console.log("Invader error", _.isObject(error) && error.stack || error);
        return q.when();
    }

    error = error.toString();

    var user;

    db.users.findOne({_id: userId})
    .then((_user) => {
        user = _user;
        return checkNotificationOnline(user);
    })
    .then(() => {
        var interval = 30*60*1000;
        if(user.notifyPrefs && user.notifyPrefs.errorsInterval) {
            interval = user.notifyPrefs.errorsInterval * 60*1000;
        }
        var date = new Date(Math.ceil(new Date().getTime() / interval) * interval);

        db['users.notifications'].update({$and: [{user: userId}, {message: error}, {type: 'error'}, {date: {$lte: date.getTime()}}]},
        {$set: {user: userId, message: error, type: 'error', date: date.getTime()}, $inc: {count: 1}},
        {upsert: true});
    });

    return pubsub.publish(`user:${userId}/console`, JSON.stringify({userId, error}));
};

exports.getGameTime = function() {
    return common.getGametime();
};

exports.incrementGameTime = function() {
    return common.getGametime()
    .then(gameTime => env.set(env.keys.GAMETIME, gameTime+1));
};

exports.getRoomInfo = function(roomId) {
    return db.rooms.findOne({_id: roomId});
};

exports.saveRoomInfo = function(roomId, roomInfo) {
    return db.rooms.update({_id: roomId}, {$set: roomInfo});
};

exports.getInterRoom = () => {
    return q.all([
        common.getGametime(),
        db['rooms.objects'].find({$and: [{type: {$in: ['creep','powerCreep']}}, {interRoom: {$ne: null}}]}),
        db.rooms.find({status: 'normal'})
            .then((rooms) => exports.mapById(_.filter(rooms, i => !i.openTime || i.openTime < Date.now()))),
        db['rooms.objects'].find({type: {$in: ['terminal', 'powerSpawn', 'powerCreep']}}),
        q.all([
            db['market.orders'].find(),
            db['users.power_creeps'].find(),
            db['users.intents'].find()
        ]).then(result => db.users.find({_id: {$in: _.map(_.flatten(result), 'user')}})
            .then(users => ({
                users,
                orders: result[0],
                userPowerCreeps: result[1],
                userIntents: result[2],
                shardName: ''
            })))
    ]);
};

exports.setRoomStatus = (roomId, status) => {
    return db.rooms.update({_id: roomId}, {$set: {status}});
};

exports.sendNotification = (userId, message) => {
    return checkNotificationOnline(userId)
    .then(() => db['users.notifications'].update({
        user: userId,
        message,
        date: {$lte: Date.now()},
        type: 'msg'
    }, {
        $set: {
            user: userId,
            message,
            date: Date.now(),
            type: 'msg'
        },
        $inc: {count: 1}
    }, {upsert: true}));
};

exports.getRoomStatsUpdater = (room) => {
    return {
        inc(name, userId, amount) {
            roomStatsUpdates[room] = roomStatsUpdates[room] || {};
            roomStatsUpdates[room][userId] = roomStatsUpdates[room][userId] || {};
            roomStatsUpdates[room][userId][name] = roomStatsUpdates[room][userId][name] || 0;
            roomStatsUpdates[room][userId][name] += amount;
        }
    }
};

exports.roomsStatsSave = () => {
    // TODO
    return q.when();
};

exports.updateAccessibleRoomsList = () => {
    return db.rooms.find({status: 'normal'})
    .then((rooms) => {
        var list = _(rooms).filter(i => !i.openTime || i.openTime < Date.now()).map('_id').value();
        return env.set(env.keys.ACCESSIBLE_ROOMS, JSON.stringify(list));
    });
};

exports.saveIdleTime = (name, time) => {
    return q.when();
};

exports.mapViewSave = (roomId, mapView) => {
    return env.set(env.keys.MAP_VIEW+roomId, JSON.stringify(mapView));
};

exports.commitDbBulk = () => {
    return q.when();
};

exports.getWorldSize = () => {
    return worldSize;
};

exports.addRoomToUser = (roomId, user, bulk) => {
    if(!user.rooms || user.rooms.indexOf(roomId) == -1) {
        bulk.addToSet(user, 'rooms', roomId);
    }
};

exports.removeRoomFromUser = (roomId, user, bulk) => {
    if(user.rooms && user.rooms.indexOf(roomId) != -1) {
        bulk.pull(user, 'rooms', roomId);
    }
};

exports.bufferFromBase64 = (base64) => {
    return Buffer.from(base64, 'base64');
};

exports.startLoop = function(name, fn) {

    let counter = 0;

    const pool = genericPool.createPool({
        create() { return {name: name + (counter++)} },
        destroy() {}
    }, {
        max: process.env.RUNNER_THREADS || 2,
        min: 0
    });

    function loop() {
        pool.acquire().then(poolItem => {
            setTimeout(loop, 0);
            return fn().finally(() => {
                pool.release(poolItem);
            });
        });
    }

    loop();
};

exports.saveRoomEventLog = function saveRoomEventLog(roomId, eventLog) {
    return env.hset(env.keys.ROOM_EVENT_LOG, roomId, JSON.stringify(eventLog));
};

exports.makeRuntime = require('./runtime/make');

exports.history = require('./history');

exports.queue = queue;

exports.constants = config.common.constants;

process.on('disconnect', () => process.exit());
<|endoftext|>


<commit_before>var chai = require("chai")
var chaiHttp = require('chai-http')
var app = require('./server')
var Promise = require('es6-promise').Promise;

chai.use(chaiHttp)

describe("Acceptance test", function () {
  var router = ''

  beforeEach("Create new instace", function () {
    this.router = require('../index.js')
    this.router.use(app)
  })

  it("Binds a new resource to the app", function () {
    this.router.resource('/test', require('./controller/testController.js'))
  })

  it("Shoud return a json respose on the index resource", function (done) {
    chai.request(app)
    .get('/test')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      done()
    }).catch(function (err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the store resource", function (done) {
    chai.request(app)
    .post('/test')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      done()
    }).catch(function (err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the show resource", function (done) {
    chai.request(app)
    .get('/test/1')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      chai.expect(res.body.id).to.be.equal('1');
      done()
    }).catch(function (err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the update resource", function (done) {
    chai.request(app)
    .put('/test/1')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      chai.expect(res.body.id).to.be.equal('1');
      done()
    }).catch(function(err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the destroy resource", function (done) {
    chai.request(app)
    .delete('/test/1')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      chai.expect(res.body.id).to.be.equal('1');
      done()
    }).catch(function(err) {
      done(new Error(err))
    });
  })
})

<commit_msg>Add test support for node 0.10<commit_after>var chai = require("chai")
var chaiHttp = require('chai-http')
var app = require('./server')
Promise = require('es6-promise').Promise;

chai.use(chaiHttp)

describe("Acceptance test", function () {
  var router = ''

  beforeEach("Create new instace", function () {
    this.router = require('../index.js')
    this.router.use(app)
  })

  it("Binds a new resource to the app", function () {
    this.router.resource('/test', require('./controller/testController.js'))
  })

  it("Shoud return a json respose on the index resource", function (done) {
    chai.request(app)
    .get('/test')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      done()
    }).catch(function (err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the store resource", function (done) {
    chai.request(app)
    .post('/test')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      done()
    }).catch(function (err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the show resource", function (done) {
    chai.request(app)
    .get('/test/1')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      chai.expect(res.body.id).to.be.equal('1');
      done()
    }).catch(function (err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the update resource", function (done) {
    chai.request(app)
    .put('/test/1')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      chai.expect(res.body.id).to.be.equal('1');
      done()
    }).catch(function(err) {
      done(new Error(err))
    });
  })

  it("Shoud return a json respose on the destroy resource", function (done) {
    chai.request(app)
    .delete('/test/1')
    .then(function (res) {
      chai.expect(res).to.have.status(200);
      chai.expect(res).to.be.json;
      chai.expect(res.body.succes).to.be.equal(true);
      chai.expect(res.body.id).to.be.equal('1');
      done()
    }).catch(function(err) {
      done(new Error(err))
    });
  })
})

<|endoftext|>