<commit_before>import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import sinon from 'sinon';

moduleFor('service:actions-service', 'Unit | Service | actions service', {
  // Specify the other units that are required for this test.
  // needs: ['service:foo']
  beforeEach(){
    this.store = {
      createRecord: sinon.spy(),
      unloadRecord: sinon.spy()
    }

    this.model = {
      save: sinon.spy(),
      destroy: sinon.spy(),
      rollbackAttributes: sinon.spy()
    }

    this.service = this.subject({
      store: this.store,
      resource: 'user',
      customTask: { perform: sinon.spy() }
    });
  }
});

test('it exists', function(assert) {
  assert.ok(this.service);
});

test('store', function(assert) {
  assert.ok( this.service.get('store'), 'it has access to the store' );
});

test('resource', function(assert) {
  assert.ok( this.service.get('resource'), 'user' );
});

test('.send() should route to a task', function(assert) {
  this.taskInstance = this.service.send(this.model, 'custom');
  assert.ok( this.service.customTask.perform.calledOnce, 'it routes to a task' );
});

test('.send() should return a task instance', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'create');
  });

  assert.equal( this.task.isSuccessful, true, 'it returns a task instance' );
});

test('.send() should throw on undefined tasks', function(assert) {
  assert.throws(() => {
    this.service.send(this.model, 'doSomething')
  }, new Error("Task doSomething is undefined."), 'throws on undefined tasks');
});

test('.send() should return a task instance', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'create');
  });

  assert.equal( this.task.isSuccessful, true,
    'it returns a task instance' );
});

test('createTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(null, 'create');
  });

  assert.ok( this.store.createRecord.calledWith( this.service.resource ),
    'it creates the model' );
});

test('savesTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'save');
  });

  assert.ok( this.model.save.calledOnce,
    'it saves the model' );
});

test('deleteTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'delete');
  });

  assert.ok( this.model.destroy.calledOnce,
    'it deletes the model' );
});

test('unloadTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'unload');
  });

  assert.ok( this.store.unloadRecord.calledWith(this.model),
    'it unloads the model' );
});

test('rollbackTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'rollback');
  });

  assert.ok( this.model.rollbackAttributes.calledOnce,
    'it rollbacks the model' );
});

<commit_msg>Fix unit test<commit_after>import Ember from 'ember';
import { moduleFor, test } from 'ember-qunit';
import sinon from 'sinon';

moduleFor('service:actions-service', 'Unit | Service | actions service', {
  // Specify the other units that are required for this test.
  // needs: ['service:foo']
  beforeEach(){
    this.store = {
      createRecord: sinon.spy(),
      unloadRecord: sinon.spy()
    }

    this.model = {
      save: sinon.spy(),
      destroy: sinon.spy(),
      rollbackAttributes: sinon.spy()
    }

    this.service = this.subject({
      store: this.store,
      resource: 'user',
      customTask: { perform: sinon.spy() }
    });
  }
});

test('it exists', function(assert) {
  assert.ok(this.service);
});

test('store', function(assert) {
  assert.ok( this.service.get('store'), 'it has access to the store' );
});

test('resource', function(assert) {
  assert.ok( this.service.get('resource'), 'user' );
});

test('send', function(assert) {
  this.taskInstance = this.service.send(this.model, 'custom');
  assert.ok( this.service.customTask.perform.calledOnce, 'it routes to a task' );

  Ember.run(() => { this.task = this.service.send(this.model, 'create'); });
  assert.equal( this.task.isSuccessful, true, 'it returns a task instance' );

  assert.throws(() => {
    this.service.send(this.model, 'doSomething')
  }, new Error("Task doSomething is undefined."), 'throws on undefined tasks');

  Ember.run(() => { this.task = this.service.send(this.model, 'create'); });
  assert.equal( this.task.isSuccessful, true, 'it returns a task instance' );
});

test('createTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(null, 'create');
  });

  assert.ok( this.store.createRecord.calledWith( this.service.resource ),
    'it creates the model' );
});

test('savesTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'save');
  });

  assert.ok( this.model.save.calledOnce,
    'it saves the model' );
});

test('deleteTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'delete');
  });

  assert.ok( this.model.destroy.calledOnce,
    'it deletes the model' );
});

test('unloadTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'unload');
  });

  assert.ok( this.store.unloadRecord.calledWith(this.model),
    'it unloads the model' );
});

test('rollbackTask', function(assert) {
  Ember.run(() => {
    this.task = this.service.send(this.model, 'rollback');
  });

  assert.ok( this.model.rollbackAttributes.calledOnce,
    'it rollbacks the model' );
});

<|endoftext|>


<commit_before>/*

	ROBO entry point

*/

var RoboEvents = require('./receiver.js');
var roboMotion = require('./motion-control.js');
var speaker = require('./helpers/festival.js');

var twitter = new RoboEvents.Twitter;
var firebase = new RoboEvents.Firebase;

twitter.init()
	.on('stream.tweet',function(data){
		console.log(data.text);

		//check if forward command is there in the stream. If yes move the robo forward

		if(data.text.indexOf('forward') > -1){
			if(roboMotion.board.ready){
				roboMotion.forward();
				setTimeout(function(){
					roboMotion.stop();
				}, 500);
			}
		}
	});

firebase.init()
	.on('moodChange',function(mood){
		console.log(mood + "robo");
	})
	.on('motionControl',function(motion){
		console.log(motion + " robo");

		if( roboMotion.board.ready ){
			if( motion == "forward"){
				roboMotion.forward();
			}

			if( motion == "back"){
				roboMotion.back();
			}

			if( motion == "stop"){
				roboMotion.stop();
			}

			if( motion == "left"){
				roboMotion.turnLeft();
			}

			if( motion == "right"){
				roboMotion.turnRight();
			}
		}

	})
	.on('speak',function(text){
		speaker.speak(text);
	});

//Test the motion detection module

//require('./motion-detection.js');

//Test the motor module

<commit_msg>Twitter integration for the speech engine<commit_after>/*

	ROBO entry point

*/

var RoboEvents = require('./receiver.js');
var roboMotion = require('./motion-control.js');
var speaker = require('./helpers/festival.js');

var twitter = new RoboEvents.Twitter;
var firebase = new RoboEvents.Firebase;

twitter.init()
	.on('stream.tweet',function(data){
		console.log(data.text);

		//check if forward command is there in the stream. If yes move the robo forward

		if(data.text.indexOf('forward') > -1){
			if(roboMotion.board.ready){
				roboMotion.forward();
				setTimeout(function(){
					roboMotion.stop();
				}, 500);
			}
		}

		// for the speech
		if(data.text.indexOf('#speak') > -1){

			//Strip the NodeRobo #tag
			var text = data.text.replace('#NodeRobo','');
			text = text.replace('#speak','');
			speaker.speak(text);
		}
	});

firebase.init()
	.on('moodChange',function(mood){
		console.log(mood + "robo");
	})
	.on('motionControl',function(motion){
		console.log(motion + " robo");

		if( roboMotion.board.ready ){
			if( motion == "forward"){
				roboMotion.forward();
			}

			if( motion == "back"){
				roboMotion.back();
			}

			if( motion == "stop"){
				roboMotion.stop();
			}

			if( motion == "left"){
				roboMotion.turnLeft();
			}

			if( motion == "right"){
				roboMotion.turnRight();
			}
		}

	})
	.on('speak',function(text){
		speaker.speak(text);
	});

//Test the motion detection module

//require('./motion-detection.js');

//Test the motor module

<|endoftext|>


<commit_before>/*!
* Copyright (c) 2015 Carrot Creative
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

(function (win, doc) {

  function __() {

    var RegexpValue = /[\-+]?[\d]*\.?[\d]+/g
      , ParseWildcard = '{?}'
      , ParseWildcardRegexp = /\{\?\}/g;

    var Animation = function (component) {
      var tweens = []
        , points = component.points
        , current, next;
      for (var i=0; i<points.length; i++) {
        current = points[i];
        next = points[i+1];
        tweens.push(new Tween(component.property, current, next));
      }
      this.tweens = tweens;
    }

    Animation.prototype = {
      current: function (Y) {
        var tweens = this.tweens
          , len = tweens.length
          , tween
          , current;
        for (var i=0; i<len; i++) {
          tween = tweens[i];
          if (Y >= tween.fromY || (Y <= tween.fromY && i == 0)){
            current = tween.current(Y);
          }
        }
        return current;
      }
    }

    var Tweener = {};

    Tweener.default = function (fromValue, toValue) {
      this.fromValue = ParseValue(fromValue);
      this.toValue = ParseValue(toValue);
    }

    Tweener.default.prototype = {
      tween: function (pct) {
        var fromValue = this.fromValue
          , toValue = this.toValue;
        return CompileValues(fromValue, toValue, pct);
      }
    }

    var Tween = function (property, fromPoint, toPoint) {
      if (!toPoint) toPoint = fromPoint;
      this.tweener = new (Tweener[property] || Tweener.default)(fromPoint.value, toPoint.value);
      this.fromY = fromPoint.Y;
      this.toY = toPoint.Y;
    }

    Tween.prototype = {
      current: function (Y) {
        var pct = 0
          , fromY = this.fromY
          , toY = this.toY;
        if (Y >= fromY && Y <= toY) {
          pct = (Y - fromY) / (toY - fromY);
        } else if (Y > toY) {
          pct = 1;
        }
        return this.tweener.tween(pct);
      }
    }

    function ParseValue (value) {
      var nums = [];
      value = new String(value).replace(RegexpValue, function (num) {
        nums.push(parseFloat(num));
        return ParseWildcard;
      });
      return [value, nums]
    }

    function CompileValues (fromValue, toValue, pct) {
      var i = 0
        , fromNum
        , toNum;
      return fromValue[0].replace(ParseWildcardRegexp, function () {
        from = fromValue[1][i];
        to = toValue[1][i];
        i++;
        return (from + ((to - from) * pct));
      });
    }

    var Style = function (component) {
      this.points = component.points;
    }

    Style.prototype = {
      current: function (Y) {
        var points = this.points
          , current = new String();
        for (var i=0; i<points.length; i++) {
          point = points[i];
          if (Y >= point.Y) current = point.value;
        }
        return current;
      }
    }

    var Point = function (Y, value) {
      this.Y = Y;
      this.value = value;
    }

    var Collection = function (object) {
      object = object || {};
      this.points = [];
      for (var Y in object) {
        this.add(Y, object[Y]);
      }
    }

    Collection.prototype = {
      add: function (Y, value) {
        var points = this.points.slice(0)
          , point = new Point(parseInt(Y), value)
          , isAdded = false;
        for (var i=0; i<this.points.length; i++) {
          if (this.points[i].Y > point.Y) {
            points.splice(i, 0, point);
            isAdded = true;
          }
        }
        if (!isAdded) points.push(point);
        this.points = points;
        return this;
      },
      merge: function (at, collection) {
        var points = collection.points
          , value;
        for (var i=0; i<points.length; i++) {
          action = points[i];
          this.add((at + action.Y), action.value);
        }
        return this;
      }
    }

    var Action = function (Klass, property, points) {
      var points = this.points = (new Collection(points)).points;
      this.max = points[points.length - 1].Y;
      this.property = property;
      this.klass = new Klass(this);
    }

    Action.prototype = {
      current: function (Y) {
        return this.klass.current(Y);
      }
    }

    var VendorPrefixProperties = {}, StyleDeclaration;

    function VendorPrefixProperty (property) {
      var matches;
      if (!StyleDeclaration) StyleDeclaration = '|' + Array.prototype.slice.call(win.getComputedStyle(doc.documentElement, '')).join('|') + '|'
      if (VendorPrefixProperties[property]) {
        return VendorPrefixProperties[property];
      } else if (matches = (new RegExp('\\|(-(moz|webkit|ms)-' + property + ')\\|')).exec(StyleDeclaration)) {
        return VendorPrefixProperties[property] = matches[1];
      }
      return property;
    }

    function SetElementStyle (object, property, value) {
      if (object instanceof NodeList || object instanceof Array) {
        for (var i=0; i<object.length; i++) SetElementStyle(object[i], property, value);
      } else {
        object.style[VendorPrefixProperty(property)] = value;
      }
    }

    var Element = function (selector) {
      this.selector = selector;
      this.actions = {};
    }

    Element.prototype = {
      $: function () {
        var $ = this.selector;
        if ($.nodeName) {
          $ = [$];
        } else if ('string' === typeof $) {
          $ = doc.querySelectorAll($);
        }
        return $;
      },
      add: function (Y, action) {
        var property = action.property
          , collection;
        if (!(collection = this.actions[property])) collection = new Collection();
        collection.add(Y, action);
        this.actions[property] = collection;
        return this;
      },
      set: function (wY) {
        var $ = this.$()
          , actions = this.actions
          , collection, points
          , point, action, Y;
        for (var property in actions) {
          collection = actions[property];
          points = collection.points;
          for (var x=0; x<points.length; x++) {
            point = points[x];
            action = point.value;
            Y = (wY - point.Y);
            if ( (Y > 0) || (Y < 0 && x == 0) ) {
              SetElementStyle($, property, action.current(Y));
            }
          }
        }
        return this;
      },
      unset: function () {
        var actions = this.actions
          , $ = this.$();
        for (var property in actions) {
          for (var i=0; i<$.length; i++) $[i].style[property] = '';
        }
        return this;
      }
    }

    var Storyboard = function () {
      this.elements = [];
      this.max = 0;
    }

    Storyboard.prototype = {
      add: function (Y, selector, action) {
        var elements = this.elements
          , element
          , max;
        for (var i=0; i<elements.length; i++) {
          if (elements[i].selector == selector) {
            element = elements[i];
            break;
          }
        }
        if (!element) {
          element = new Element(selector);
          elements.push(element);
        }
        if (action) {
          element.add(Y, action);
          max = Y + action.max;
          if (max > this.max) this.max = max;
        }
        this.elements = elements;
        return this;
      },
      merge: function (at, storyboard) {
        var elements = storyboard.elements
          , element, actions, collection
          , points, point;
        for (var x=0; x<elements.length; x++) {
          element = elements[x];
          actions = element.actions;
          for (var property in actions) {
            collection = actions[property];
            points = collection.points;
            for (var i=0; i<points.length; i++) {
              point = points[i];
              this.add((parseInt(at) + point.Y), element.selector, point.value);
            }
          }
        }
        return this;
      },
      set: function (Y) {
        var elements = this.elements
          , element;
        for (var i=0; i<elements.length; i++) {
          element = elements[i];
          element.set(Y);
        }
      },
      unset: function () {
        var elements = this.elements
          , element;
        for (var i=0; i<elements.length; i++) {
          element = elements[i];
          element.unset();
        }
      }
    }

    function SetStoryboardFunction (storyboard) {
      return function () {
        var wY = win.pageYOffset;
        storyboard.set(wY);
      }
    }

    function SetBodyMinHeightFunction (storyboard) {
      return function () {
        SetElementStyle(doc.body, 'minHeight', ((storyboard.max + win.innerHeight) + 'px'));
      }
    }

    function AddEvent (roll, string, callback) {
      var events = string.split(' ')
        , ev;
      for (var i=0; i<events.length; i++) {
        ev = events[i];
        if (!roll.events[ev]) roll.events[ev] = [];
        roll.events[ev].push(callback);
        win.addEventListener(ev, callback);
      }
      callback();
    }

    function RemoveEvents (roll, string) {
      var events = string.split(' ')
        , ev, callbacks, callback;
      for (var x=0; x<events.length; x++) {
        ev = events[x];
        callbacks = roll.events[ev];
        for (var y=0; x<callbacks.length; x++) {
          callback = callbacks[y];
          win.removeEventListener(ev, callback);
        }
        roll.events[ev] = [];
      }
    }

    function RemoveAllEvents (roll) {
      for (var ev in roll.events) RemoveEvents(roll, ev);
      roll.events = {};
    }

    var Roll = function () {
      this.scenes = {};
      this.ats = {};
      this.events = {};
    }

    Roll.prototype = {
      add: function (name, scene) {
        if ('function' === typeof scene) {
          var fn = scene;
          scene = new Scene();
          fn(scene);
        }
        this.scenes[name] = scene;
        return this;
      },
      at: function (Y, scene) {
        var ats = this.ats[Y] || [];
        ats.push(scene);
        this.ats[Y] = ats;
        return this;
      },
      init: function () {
        var storyboard, scenes, scene;
        storyboard = this.storyboard = new Storyboard();
        for (var Y in this.ats) {
          scenes = this.ats[Y];
          for (var i=0; i<scenes.length; i++) {
            scene = 'string' === typeof scenes[i] ? this.scenes[scenes[i]] : scenes[i]
            storyboard.merge(Y, scene.storyboard);
          }
        }
        this.bind();
        return this;
      },
      remove: function () {
        var storyboard;
        if (storyboard = this.storyboard) {
          storyboard.unset();
          this.storyboard = null;
        }
        SetElementStyle(doc.body, 'minHeight', '');
        this.unbind();
        return this;
      },
      bind: function () {
        if (this.storyboard) {
          AddEvent(this, 'scroll resize', SetStoryboardFunction(this.storyboard));
          AddEvent(this, 'resize', SetBodyMinHeightFunction(this.storyboard));
        }
        return this;
      },
      unbind: function () {
        RemoveAllEvents(this);
        return this;
      }
    }

    var Scene = Roll.Scene = function () {
      this.storyboard = new Storyboard();
    }

    Scene.prototype = {
      animate: function ($el, property, points) {
        return this.action(Animation, $el, property, points);
      },
      style: function ($el, property, points) {
        return this.action(Style, $el, property, points);
      },
      action: function (Klass, $el, property, points) {
        var properties;
        if ('undefined' === typeof points) {
          properties = property;
        } else {
          (properties = {})[property] = points;
        }
        for (property in properties) {
          action = new Action(Klass, property, properties[property]);
          this.storyboard.add(0, $el, action);
        }
        return this;
      }
    }

    return Roll;

  }

  if ('function' === typeof define && define.amd) {
    define(__);
  } else {
    win.Roll = __();
  };

}(window, document));
<commit_msg>Simply event handling. - Use arrays (more declarative) for event specification. - Rename event functions appropriately. - Bind roll to callbacks.<commit_after>/*!
* Copyright (c) 2015 Carrot Creative
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

(function (win, doc) {

  function __() {

    var RegexpValue = /[\-+]?[\d]*\.?[\d]+/g
      , ParseWildcard = '{?}'
      , ParseWildcardRegexp = /\{\?\}/g;

    var Animation = function (component) {
      var tweens = []
        , points = component.points
        , current, next;
      for (var i=0; i<points.length; i++) {
        current = points[i];
        next = points[i+1];
        tweens.push(new Tween(component.property, current, next));
      }
      this.tweens = tweens;
    }

    Animation.prototype = {
      current: function (Y) {
        var tweens = this.tweens
          , len = tweens.length
          , tween
          , current;
        for (var i=0; i<len; i++) {
          tween = tweens[i];
          if (Y >= tween.fromY || (Y <= tween.fromY && i == 0)){
            current = tween.current(Y);
          }
        }
        return current;
      }
    }

    var Tweener = {};

    Tweener.default = function (fromValue, toValue) {
      this.fromValue = ParseValue(fromValue);
      this.toValue = ParseValue(toValue);
    }

    Tweener.default.prototype = {
      tween: function (pct) {
        var fromValue = this.fromValue
          , toValue = this.toValue;
        return CompileValues(fromValue, toValue, pct);
      }
    }

    var Tween = function (property, fromPoint, toPoint) {
      if (!toPoint) toPoint = fromPoint;
      this.tweener = new (Tweener[property] || Tweener.default)(fromPoint.value, toPoint.value);
      this.fromY = fromPoint.Y;
      this.toY = toPoint.Y;
    }

    Tween.prototype = {
      current: function (Y) {
        var pct = 0
          , fromY = this.fromY
          , toY = this.toY;
        if (Y >= fromY && Y <= toY) {
          pct = (Y - fromY) / (toY - fromY);
        } else if (Y > toY) {
          pct = 1;
        }
        return this.tweener.tween(pct);
      }
    }

    function ParseValue (value) {
      var nums = [];
      value = new String(value).replace(RegexpValue, function (num) {
        nums.push(parseFloat(num));
        return ParseWildcard;
      });
      return [value, nums]
    }

    function CompileValues (fromValue, toValue, pct) {
      var i = 0
        , fromNum
        , toNum;
      return fromValue[0].replace(ParseWildcardRegexp, function () {
        from = fromValue[1][i];
        to = toValue[1][i];
        i++;
        return (from + ((to - from) * pct));
      });
    }

    var Style = function (component) {
      this.points = component.points;
    }

    Style.prototype = {
      current: function (Y) {
        var points = this.points
          , current = new String();
        for (var i=0; i<points.length; i++) {
          point = points[i];
          if (Y >= point.Y) current = point.value;
        }
        return current;
      }
    }

    var Point = function (Y, value) {
      this.Y = Y;
      this.value = value;
    }

    var Collection = function (object) {
      object = object || {};
      this.points = [];
      for (var Y in object) {
        this.add(Y, object[Y]);
      }
    }

    Collection.prototype = {
      add: function (Y, value) {
        var points = this.points.slice(0)
          , point = new Point(parseInt(Y), value)
          , isAdded = false;
        for (var i=0; i<this.points.length; i++) {
          if (this.points[i].Y > point.Y) {
            points.splice(i, 0, point);
            isAdded = true;
          }
        }
        if (!isAdded) points.push(point);
        this.points = points;
        return this;
      },
      merge: function (at, collection) {
        var points = collection.points
          , value;
        for (var i=0; i<points.length; i++) {
          action = points[i];
          this.add((at + action.Y), action.value);
        }
        return this;
      }
    }

    var Action = function (Klass, property, points) {
      var points = this.points = (new Collection(points)).points;
      this.max = points[points.length - 1].Y;
      this.property = property;
      this.klass = new Klass(this);
    }

    Action.prototype = {
      current: function (Y) {
        return this.klass.current(Y);
      }
    }

    var VendorPrefixProperties = {}, StyleDeclaration;

    function VendorPrefixProperty (property) {
      var matches;
      if (!StyleDeclaration) StyleDeclaration = '|' + Array.prototype.slice.call(win.getComputedStyle(doc.documentElement, '')).join('|') + '|'
      if (VendorPrefixProperties[property]) {
        return VendorPrefixProperties[property];
      } else if (matches = (new RegExp('\\|(-(moz|webkit|ms)-' + property + ')\\|')).exec(StyleDeclaration)) {
        return VendorPrefixProperties[property] = matches[1];
      }
      return property;
    }

    function SetElementStyle (object, property, value) {
      if (object instanceof NodeList || object instanceof Array) {
        for (var i=0; i<object.length; i++) SetElementStyle(object[i], property, value);
      } else {
        object.style[VendorPrefixProperty(property)] = value;
      }
    }

    var Element = function (selector) {
      this.selector = selector;
      this.actions = {};
    }

    Element.prototype = {
      $: function () {
        var $ = this.selector;
        if ($.nodeName) {
          $ = [$];
        } else if ('string' === typeof $) {
          $ = doc.querySelectorAll($);
        }
        return $;
      },
      add: function (Y, action) {
        var property = action.property
          , collection;
        if (!(collection = this.actions[property])) collection = new Collection();
        collection.add(Y, action);
        this.actions[property] = collection;
        return this;
      },
      set: function (wY) {
        var $ = this.$()
          , actions = this.actions
          , collection, points
          , point, action, Y;
        for (var property in actions) {
          collection = actions[property];
          points = collection.points;
          for (var x=0; x<points.length; x++) {
            point = points[x];
            action = point.value;
            Y = (wY - point.Y);
            if ( (Y > 0) || (Y < 0 && x == 0) ) {
              SetElementStyle($, property, action.current(Y));
            }
          }
        }
        return this;
      },
      unset: function () {
        var actions = this.actions
          , $ = this.$();
        for (var property in actions) {
          for (var i=0; i<$.length; i++) $[i].style[property] = '';
        }
        return this;
      }
    }

    var Storyboard = function () {
      this.elements = [];
      this.max = 0;
    }

    Storyboard.prototype = {
      add: function (Y, selector, action) {
        var elements = this.elements
          , element
          , max;
        for (var i=0; i<elements.length; i++) {
          if (elements[i].selector == selector) {
            element = elements[i];
            break;
          }
        }
        if (!element) {
          element = new Element(selector);
          elements.push(element);
        }
        if (action) {
          element.add(Y, action);
          max = Y + action.max;
          if (max > this.max) this.max = max;
        }
        this.elements = elements;
        return this;
      },
      merge: function (at, storyboard) {
        var elements = storyboard.elements
          , element, actions, collection
          , points, point;
        for (var x=0; x<elements.length; x++) {
          element = elements[x];
          actions = element.actions;
          for (var property in actions) {
            collection = actions[property];
            points = collection.points;
            for (var i=0; i<points.length; i++) {
              point = points[i];
              this.add((parseInt(at) + point.Y), element.selector, point.value);
            }
          }
        }
        return this;
      },
      set: function (Y) {
        var elements = this.elements
          , element;
        for (var i=0; i<elements.length; i++) {
          element = elements[i];
          element.set(Y);
        }
      },
      unset: function () {
        var elements = this.elements
          , element;
        for (var i=0; i<elements.length; i++) {
          element = elements[i];
          element.unset();
        }
      }
    }

    function AddEvent (roll, events, callback) {
      var fn = callback.bind(null, roll)
        , ev;
      for (var i=0; i<events.length; i++) {
        ev = events[i];
        if (!roll.events[ev]) roll.events[ev] = [];
        roll.events[ev].push(fn);
        win.addEventListener(ev, fn);
      }
      fn();
    }

    function RemoveEvent (roll, events, callback) {
      var ev, callbacks, cb;
      for (var x=0; x<events.length; x++) {
        ev = events[i];
        callbacks = 'function' === typeof callback ? [callback] : roll.events[ev];
        if (roll.events[ev]) {
          for (var y=0; y<callbacks.length; y++) {
            cb = callbacks[y];
            win.removeEventListener(ev, cb);
          }
        }
      }
    }

    function OnScroll (roll) {
      roll.storyboard.set(win.pageYOffset);
    }

    function OnResize (roll) {
      SetElementStyle(doc.body, 'minHeight', ((roll.storyboard.max + win.innerHeight) + 'px'));
    }

    var Roll = function () {
      this.scenes = {};
      this.ats = {};
      this.events = {};
    }

    Roll.prototype = {
      add: function (name, scene) {
        if ('function' === typeof scene) {
          var fn = scene;
          scene = new Scene();
          fn(scene);
        }
        this.scenes[name] = scene;
        return this;
      },
      at: function (Y, scene) {
        var ats = this.ats[Y] || [];
        ats.push(scene);
        this.ats[Y] = ats;
        return this;
      },
      init: function () {
        var storyboard, scenes, scene;
        storyboard = this.storyboard = new Storyboard();
        for (var Y in this.ats) {
          scenes = this.ats[Y];
          for (var i=0; i<scenes.length; i++) {
            scene = 'string' === typeof scenes[i] ? this.scenes[scenes[i]] : scenes[i]
            storyboard.merge(Y, scene.storyboard);
          }
        }
        this.bind();
        return this;
      },
      remove: function () {
        var storyboard;
        if (storyboard = this.storyboard) {
          storyboard.unset();
          this.storyboard = null;
        }
        SetElementStyle(doc.body, 'minHeight', '');
        this.unbind();
        return this;
      },
      bind: function () {
        if (this.storyboard) {
          AddEvent(this, ['scroll', 'resize'], OnScroll);
          AddEvent(this, ['resize'], OnResize);
        }
        return this;
      },
      unbind: function () {
        RemoveAllEvents(this);
        return this;
      }
    }

    var Scene = Roll.Scene = function () {
      this.storyboard = new Storyboard();
    }

    Scene.prototype = {
      animate: function ($el, property, points) {
        return this.action(Animation, $el, property, points);
      },
      style: function ($el, property, points) {
        return this.action(Style, $el, property, points);
      },
      action: function (Klass, $el, property, points) {
        var properties;
        if ('undefined' === typeof points) {
          properties = property;
        } else {
          (properties = {})[property] = points;
        }
        for (property in properties) {
          action = new Action(Klass, property, properties[property]);
          this.storyboard.add(0, $el, action);
        }
        return this;
      }
    }

    return Roll;

  }

  if ('function' === typeof define && define.amd) {
    define(__);
  } else {
    win.Roll = __();
  };

}(window, document));
<|endoftext|>


<commit_before>// Scrollbar Width function
function getScrollBarWidth() {
    var inner = document.createElement('p');
    inner.style.width = "100%";
    inner.style.height = "200px";

    var outer = document.createElement('div');
    outer.style.position = "absolute";
    outer.style.top = "0px";
    outer.style.left = "0px";
    outer.style.visibility = "hidden";
    outer.style.width = "200px";
    outer.style.height = "150px";
    outer.style.overflow = "hidden";
    outer.appendChild(inner);

    document.body.appendChild(outer);
    var w1 = inner.offsetWidth;
    outer.style.overflow = 'scroll';
    var w2 = inner.offsetWidth;
    if (w1 == w2) w2 = outer.clientWidth;

    document.body.removeChild(outer);

    return (w1 - w2);
};

function setMenuHeight() {
    $('#sidebar .highlightable').height($('#sidebar').innerHeight() - $('#header-wrapper').height() - 40);
    $('#sidebar .highlightable').perfectScrollbar('update');
}

function fallbackMessage(action) {
    var actionMsg = '';
    var actionKey = (action === 'cut' ? 'X' : 'C');

    if (/iPhone|iPad/i.test(navigator.userAgent)) {
        actionMsg = 'No support :(';
    }
    else if (/Mac/i.test(navigator.userAgent)) {
        actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
    }
    else {
        actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
    }

    return actionMsg;
}

// for the window resize
$(window).resize(function() {
    setMenuHeight();
});

// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
(function($, sr) {

    var debounce = function(func, threshold, execAsap) {
        var timeout;

        return function debounced() {
            var obj = this, args = arguments;

            function delayed() {
                if (!execAsap)
                    func.apply(obj, args);
                timeout = null;
            };

            if (timeout)
                clearTimeout(timeout);
            else if (execAsap)
                func.apply(obj, args);

            timeout = setTimeout(delayed, threshold || 100);
        };
    }
    // smartresize
    jQuery.fn[sr] = function(fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };

})(jQuery, 'smartresize');

jQuery(document).ready(function() {
    jQuery('#sidebar .category-icon').on('click', function() {
        $( this ).toggleClass("fa-angle-down fa-angle-right") ;
        $( this ).parent().parent().children('ul').toggle() ;
        return false;
    });

    var sidebarStatus = searchStatus = 'open';
    $('#sidebar .highlightable').perfectScrollbar();
    setMenuHeight();

    jQuery('#overlay').on('click', function() {
        jQuery(document.body).toggleClass('sidebar-hidden');
        sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');

        return false;
    });

    jQuery('[data-sidebar-toggle]').on('click', function() {
        jQuery(document.body).toggleClass('sidebar-hidden');
        sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');

        return false;
    });
    jQuery('[data-clear-history-toggle]').on('click', function() {
        sessionStorage.clear();
        location.reload();
        return false;
    });
    jQuery('[data-search-toggle]').on('click', function() {
        if (sidebarStatus == 'closed') {
            jQuery('[data-sidebar-toggle]').trigger('click');
            jQuery(document.body).removeClass('searchbox-hidden');
            searchStatus = 'open';

            return false;
        }

        jQuery(document.body).toggleClass('searchbox-hidden');
        searchStatus = (jQuery(document.body).hasClass('searchbox-hidden') ? 'closed' : 'open');

        return false;
    });

    var ajax;
    jQuery('[data-search-input]').on('input', function() {
        var input = jQuery(this),
            value = input.val(),
            items = jQuery('[data-nav-id]');
        items.removeClass('search-match');
        if (!value.length) {
            $('ul.topics').removeClass('searched');
            items.css('display', 'block');
            sessionStorage.removeItem('search-value');
            $(".highlightable").unhighlight({ element: 'mark' })
            return;
        }

        sessionStorage.setItem('search-value', value);
        $(".highlightable").unhighlight({ element: 'mark' }).highlight(value, { element: 'mark' });

        if (ajax && ajax.abort) ajax.abort();

        jQuery('[data-search-clear]').on('click', function() {
            jQuery('[data-search-input]').val('').trigger('input');
            sessionStorage.removeItem('search-input');
            $(".highlightable").unhighlight({ element: 'mark' })
        });
    });

    $.expr[":"].contains = $.expr.createPseudo(function(arg) {
        return function( elem ) {
            return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
        };
    });

    if (sessionStorage.getItem('search-value')) {
        var searchValue = sessionStorage.getItem('search-value')
        $(document.body).removeClass('searchbox-hidden');
        $('[data-search-input]').val(searchValue);
        $('[data-search-input]').trigger('input');
        var searchedElem = $('#body-inner').find(':contains(' + searchValue + ')').get(0);
        if (searchedElem) {
            searchedElem.scrollIntoView(true);
            var scrolledY = window.scrollY;
            if(scrolledY){
                window.scroll(0, scrolledY - 125);
            }
        }
    }

    // clipboard
    var clipInit = false;
    $('code').each(function() {
        var code = $(this),
            text = code.text();

        if (text.length > 5) {
            if (!clipInit) {
                var text, clip = new ClipboardJS('.copy-to-clipboard', {
                    text: function(trigger) {
                        text = $(trigger).prev('code').text();
                        return text.replace(/^\$\s/gm, '');
                    }
                });

                var inPre;
                clip.on('success', function(e) {
                    e.clearSelection();
                    inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
                    $(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
                });

                clip.on('error', function(e) {
                    inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
                    $(e.trigger).attr('aria-label', fallbackMessage(e.action)).addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
                    $(document).one('copy', function(){
                        $(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
                    });
                });

                clipInit = true;
            }

            code.after('<span class="copy-to-clipboard" title="Copy to clipboard" />');
            code.next('.copy-to-clipboard').on('mouseleave', function() {
                $(this).attr('aria-label', null).removeClass('tooltipped tooltipped-s tooltipped-w');
            });
        }
    });

    // allow keyboard control for prev/next links
    jQuery(function() {
        jQuery('.nav-prev').click(function(){
            location.href = jQuery(this).attr('href');
        });
        jQuery('.nav-next').click(function() {
            location.href = jQuery(this).attr('href');
        });
    });

    jQuery('input, textarea').keydown(function (e) {
         //  left and right arrow keys
         if (e.which == '37' || e.which == '39') {
             e.stopPropagation();
         }
     });

    jQuery(document).keydown(function(e) {
      // prev links - left arrow key
      if(e.which == '37') {
        jQuery('.nav.nav-prev').click();
      }

      // next links - right arrow key
      if(e.which == '39') {
        jQuery('.nav.nav-next').click();
      }
    });

    $('#top-bar a:not(:has(img)):not(.btn)').addClass('highlight');
    $('#body-inner a:not(:has(img)):not(.btn):not(a[rel="footnote"])').addClass('highlight');

    var touchsupport = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)
    if (!touchsupport){ // browser doesn't support touch
        $('#toc-menu').hover(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });

        $('.progress').hover(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });
    }
    if (touchsupport){ // browser does support touch
        $('#toc-menu').click(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });
        $('.progress').click(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });
    }

    /**
    * Fix anchor scrolling that hides behind top nav bar
    * Courtesy of https://stackoverflow.com/a/13067009/28106
    *
    * We could use pure css for this if only heading anchors were
    * involved, but this works for any anchor, including footnotes
    **/
    (function (document, history, location) {
        var HISTORY_SUPPORT = !!(history && history.pushState);

        var anchorScrolls = {
            ANCHOR_REGEX: /^#[^ ]+$/,
            OFFSET_HEIGHT_PX: 50,

            /**
             * Establish events, and fix initial scroll position if a hash is provided.
             */
            init: function () {
                this.scrollToCurrent();
                $(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
                $('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
            },

            /**
             * Return the offset amount to deduct from the normal scroll position.
             * Modify as appropriate to allow for dynamic calculations
             */
            getFixedOffset: function () {
                return this.OFFSET_HEIGHT_PX;
            },

            /**
             * If the provided href is an anchor which resolves to an element on the
             * page, scroll to it.
             * @param  {String} href
             * @return {Boolean} - Was the href an anchor.
             */
            scrollIfAnchor: function (href, pushToHistory) {
                var match, anchorOffset;

                if (!this.ANCHOR_REGEX.test(href)) {
                    return false;
                }

                match = document.getElementById(href.slice(1));

                if (match) {
                    anchorOffset = $(match).offset().top - this.getFixedOffset();
                    $('html, body').animate({ scrollTop: anchorOffset });

                    // Add the state to history as-per normal anchor links
                    if (HISTORY_SUPPORT && pushToHistory) {
                        history.pushState({}, document.title, location.pathname + href);
                    }
                }

                return !!match;
            },

            /**
             * Attempt to scroll to the current location's hash.
             */
            scrollToCurrent: function (e) {
                if (this.scrollIfAnchor(window.location.hash) && e) {
                    e.preventDefault();
                }
            },

            /**
             * If the click event's target was an anchor, fix the scroll position.
             */
            delegateAnchors: function (e) {
                var elem = e.target;

                if (this.scrollIfAnchor(elem.getAttribute('href'), true)) {
                    e.preventDefault();
                }
            }
        };

        $(document).ready($.proxy(anchorScrolls, 'init'));
    })(window.document, window.history, window.location);

});

jQuery(window).on('load', function() {

    function adjustForScrollbar() {
        if ((parseInt(jQuery('#body-inner').height()) + 83) >= jQuery('#body').height()) {
            jQuery('.nav.nav-next').css({ 'margin-right': getScrollBarWidth() });
        } else {
            jQuery('.nav.nav-next').css({ 'margin-right': 0 });
        }
    }

    // adjust sidebar for scrollbar
    adjustForScrollbar();

    jQuery(window).smartresize(function() {
        adjustForScrollbar();
    });

    // store this page in session
    sessionStorage.setItem(jQuery('body').data('url'), 1);

    // loop through the sessionStorage and see if something should be marked as visited
    for (var url in sessionStorage) {
        if (sessionStorage.getItem(url) == 1) jQuery('[data-nav-id="' + url + '"]').addClass('visited');
    }

    $(".highlightable").highlight(sessionStorage.getItem('search-value'), { element: 'mark' });
});

$(function() {
    $('a[rel="lightbox"]').featherlight({
        root: 'section#body'
    });
});

jQuery.extend({
    highlight: function(node, re, nodeName, className) {
        if (node.nodeType === 3) {
            var match = node.data.match(re);
            if (match) {
                var highlight = document.createElement(nodeName || 'span');
                highlight.className = className || 'highlight';
                var wordNode = node.splitText(match.index);
                wordNode.splitText(match[0].length);
                var wordClone = wordNode.cloneNode(true);
                highlight.appendChild(wordClone);
                wordNode.parentNode.replaceChild(highlight, wordNode);
                return 1; //skip added node in parent
            }
        } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
            !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
            !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
            for (var i = 0; i < node.childNodes.length; i++) {
                i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
            }
        }
        return 0;
    }
});

jQuery.fn.unhighlight = function(options) {
    var settings = {
        className: 'highlight',
        element: 'span'
    };
    jQuery.extend(settings, options);

    return this.find(settings.element + "." + settings.className).each(function() {
        var parent = this.parentNode;
        parent.replaceChild(this.firstChild, this);
        parent.normalize();
    }).end();
};

jQuery.fn.highlight = function(words, options) {
    var settings = {
        className: 'highlight',
        element: 'span',
        caseSensitive: false,
        wordsOnly: false
    };
    jQuery.extend(settings, options);

    if (!words) { return; }

    if (words.constructor === String) {
        words = [words];
    }
    words = jQuery.grep(words, function(word, i) {
        return word != '';
    });
    words = jQuery.map(words, function(word, i) {
        return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    });
    if (words.length == 0) { return this; }
    ;

    var flag = settings.caseSensitive ? "" : "i";
    var pattern = "(" + words.join("|") + ")";
    if (settings.wordsOnly) {
        pattern = "\\b" + pattern + "\\b";
    }
    var re = new RegExp(pattern, flag);

    return this.each(function() {
        jQuery.highlight(this, re, settings.element, settings.className);
    });
};
<commit_msg>remove unwanted code<commit_after>// Scrollbar Width function
function getScrollBarWidth() {
    var inner = document.createElement('p');
    inner.style.width = "100%";
    inner.style.height = "200px";

    var outer = document.createElement('div');
    outer.style.position = "absolute";
    outer.style.top = "0px";
    outer.style.left = "0px";
    outer.style.visibility = "hidden";
    outer.style.width = "200px";
    outer.style.height = "150px";
    outer.style.overflow = "hidden";
    outer.appendChild(inner);

    document.body.appendChild(outer);
    var w1 = inner.offsetWidth;
    outer.style.overflow = 'scroll';
    var w2 = inner.offsetWidth;
    if (w1 == w2) w2 = outer.clientWidth;

    document.body.removeChild(outer);

    return (w1 - w2);
};

function setMenuHeight() {
    $('#sidebar .highlightable').height($('#sidebar').innerHeight() - $('#header-wrapper').height() - 40);
    $('#sidebar .highlightable').perfectScrollbar('update');
}

function fallbackMessage(action) {
    var actionMsg = '';
    var actionKey = (action === 'cut' ? 'X' : 'C');

    if (/iPhone|iPad/i.test(navigator.userAgent)) {
        actionMsg = 'No support :(';
    }
    else if (/Mac/i.test(navigator.userAgent)) {
        actionMsg = 'Press ⌘-' + actionKey + ' to ' + action;
    }
    else {
        actionMsg = 'Press Ctrl-' + actionKey + ' to ' + action;
    }

    return actionMsg;
}

// for the window resize
$(window).resize(function() {
    setMenuHeight();
});

// debouncing function from John Hann
// http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
(function($, sr) {

    var debounce = function(func, threshold, execAsap) {
        var timeout;

        return function debounced() {
            var obj = this, args = arguments;

            function delayed() {
                if (!execAsap)
                    func.apply(obj, args);
                timeout = null;
            };

            if (timeout)
                clearTimeout(timeout);
            else if (execAsap)
                func.apply(obj, args);

            timeout = setTimeout(delayed, threshold || 100);
        };
    }
    // smartresize
    jQuery.fn[sr] = function(fn) { return fn ? this.bind('resize', debounce(fn)) : this.trigger(sr); };

})(jQuery, 'smartresize');

jQuery(document).ready(function() {
    jQuery('#sidebar .category-icon').on('click', function() {
        $( this ).toggleClass("fa-angle-down fa-angle-right") ;
        $( this ).parent().parent().children('ul').toggle() ;
        return false;
    });

    var sidebarStatus = searchStatus = 'open';
    $('#sidebar .highlightable').perfectScrollbar();
    setMenuHeight();

    jQuery('#overlay').on('click', function() {
        jQuery(document.body).toggleClass('sidebar-hidden');
        sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');

        return false;
    });

    jQuery('[data-sidebar-toggle]').on('click', function() {
        jQuery(document.body).toggleClass('sidebar-hidden');
        sidebarStatus = (jQuery(document.body).hasClass('sidebar-hidden') ? 'closed' : 'open');

        return false;
    });
    jQuery('[data-clear-history-toggle]').on('click', function() {
        sessionStorage.clear();
        location.reload();
        return false;
    });
    jQuery('[data-search-toggle]').on('click', function() {
        if (sidebarStatus == 'closed') {
            jQuery('[data-sidebar-toggle]').trigger('click');
            jQuery(document.body).removeClass('searchbox-hidden');
            searchStatus = 'open';

            return false;
        }

        jQuery(document.body).toggleClass('searchbox-hidden');
        searchStatus = (jQuery(document.body).hasClass('searchbox-hidden') ? 'closed' : 'open');

        return false;
    });

    var ajax;
    jQuery('[data-search-input]').on('input', function() {
        var input = jQuery(this),
            value = input.val(),
            items = jQuery('[data-nav-id]');
        items.removeClass('search-match');
        if (!value.length) {
            $('ul.topics').removeClass('searched');
            items.css('display', 'block');
            sessionStorage.removeItem('search-value');
            $(".highlightable").unhighlight({ element: 'mark' })
            return;
        }

        sessionStorage.setItem('search-value', value);
        $(".highlightable").unhighlight({ element: 'mark' }).highlight(value, { element: 'mark' });

        if (ajax && ajax.abort) ajax.abort();

        jQuery('[data-search-clear]').on('click', function() {
            jQuery('[data-search-input]').val('').trigger('input');
            sessionStorage.removeItem('search-input');
            $(".highlightable").unhighlight({ element: 'mark' })
        });
    });

    $.expr[":"].contains = $.expr.createPseudo(function(arg) {
        return function( elem ) {
            return $(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
        };
    });

    if (sessionStorage.getItem('search-value')) {
        var searchValue = sessionStorage.getItem('search-value')
        $(document.body).removeClass('searchbox-hidden');
        $('[data-search-input]').val(searchValue);
        $('[data-search-input]').trigger('input');
        var searchedElem = $('#body-inner').find(':contains(' + searchValue + ')').get(0);
        if (searchedElem) {
            searchedElem.scrollIntoView(true);
            var scrolledY = window.scrollY;
            if(scrolledY){
                window.scroll(0, scrolledY - 125);
            }
        }
    }

    // clipboard
    var clipInit = false;
    $('code').each(function() {
        var code = $(this),
            text = code.text();

        if (text.length > 5) {
            if (!clipInit) {
                var text, clip = new ClipboardJS('.copy-to-clipboard', {
                    text: function(trigger) {
                        text = $(trigger).prev('code').text();
                        return text.replace(/^\$\s/gm, '');
                    }
                });

                var inPre;
                clip.on('success', function(e) {
                    e.clearSelection();
                    inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
                    $(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
                });

                clip.on('error', function(e) {
                    inPre = $(e.trigger).parent().prop('tagName') == 'PRE';
                    $(e.trigger).attr('aria-label', fallbackMessage(e.action)).addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
                    $(document).one('copy', function(){
                        $(e.trigger).attr('aria-label', 'Copied to clipboard!').addClass('tooltipped tooltipped-' + (inPre ? 'w' : 's'));
                    });
                });

                clipInit = true;
            }

            code.after('<span class="copy-to-clipboard" title="Copy to clipboard" />');
            code.next('.copy-to-clipboard').on('mouseleave', function() {
                $(this).attr('aria-label', null).removeClass('tooltipped tooltipped-s tooltipped-w');
            });
        }
    });

    // allow keyboard control for prev/next links
    jQuery(function() {
        jQuery('.nav-prev').click(function(){
            location.href = jQuery(this).attr('href');
        });
        jQuery('.nav-next').click(function() {
            location.href = jQuery(this).attr('href');
        });
    });

    jQuery('input, textarea').keydown(function (e) {
         //  left and right arrow keys
         if (e.which == '37' || e.which == '39') {
             e.stopPropagation();
         }
     });

    jQuery(document).keydown(function(e) {
      // prev links - left arrow key
      if(e.which == '37') {
        jQuery('.nav.nav-prev').click();
      }

      // next links - right arrow key
      if(e.which == '39') {
        jQuery('.nav.nav-next').click();
      }
    });

    $('#top-bar a:not(:has(img)):not(.btn)').addClass('highlight');
    $('#body-inner a:not(:has(img)):not(.btn):not(a[rel="footnote"])').addClass('highlight');

    var touchsupport = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)
    if (!touchsupport){ // browser doesn't support touch
        $('#toc-menu').hover(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });

        $('.progress').hover(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });
    }
    if (touchsupport){ // browser does support touch
        $('#toc-menu').click(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });
        $('.progress').click(function() {
            $('.progress').stop(true, false, true).fadeToggle(100);
        });
    }

    /**
    * Fix anchor scrolling that hides behind top nav bar
    * Courtesy of https://stackoverflow.com/a/13067009/28106
    *
    * We could use pure css for this if only heading anchors were
    * involved, but this works for any anchor, including footnotes
    **/
    (function (document, history, location) {
        var HISTORY_SUPPORT = !!(history && history.pushState);

        var anchorScrolls = {
            ANCHOR_REGEX: /^#[^ ]+$/,
            OFFSET_HEIGHT_PX: 50,

            /**
             * Establish events, and fix initial scroll position if a hash is provided.
             */
            init: function () {
                this.scrollToCurrent();
                $(window).on('hashchange', $.proxy(this, 'scrollToCurrent'));
                $('body').on('click', 'a', $.proxy(this, 'delegateAnchors'));
            },

            /**
             * Return the offset amount to deduct from the normal scroll position.
             * Modify as appropriate to allow for dynamic calculations
             */
            getFixedOffset: function () {
                return this.OFFSET_HEIGHT_PX;
            },

            /**
             * If the provided href is an anchor which resolves to an element on the
             * page, scroll to it.
             * @param  {String} href
             * @return {Boolean} - Was the href an anchor.
             */
            scrollIfAnchor: function (href, pushToHistory) {
                var match, anchorOffset;

                if (!this.ANCHOR_REGEX.test(href)) {
                    return false;
                }

                match = document.getElementById(href.slice(1));

                if (match) {
                    anchorOffset = $(match).offset().top - this.getFixedOffset();
                    $('html, body').animate({ scrollTop: anchorOffset });

                    // Add the state to history as-per normal anchor links
                    if (HISTORY_SUPPORT && pushToHistory) {
                        history.pushState({}, document.title, location.pathname + href);
                    }
                }

                return !!match;
            },

            /**
             * Attempt to scroll to the current location's hash.
             */
            scrollToCurrent: function (e) {
                if (this.scrollIfAnchor(window.location.hash) && e) {
                    e.preventDefault();
                }
            },

            /**
             * If the click event's target was an anchor, fix the scroll position.
             */
            delegateAnchors: function (e) {
                var elem = e.target;

                if (this.scrollIfAnchor(elem.getAttribute('href'), true)) {
                    e.preventDefault();
                }
            }
        };

        $(document).ready($.proxy(anchorScrolls, 'init'));
    })(window.document, window.history, window.location);

});

jQuery(window).on('load', function() {
    // store this page in session
    sessionStorage.setItem(jQuery('body').data('url'), 1);

    // loop through the sessionStorage and see if something should be marked as visited
    for (var url in sessionStorage) {
        if (sessionStorage.getItem(url) == 1) jQuery('[data-nav-id="' + url + '"]').addClass('visited');
    }

    $(".highlightable").highlight(sessionStorage.getItem('search-value'), { element: 'mark' });
});

$(function() {
    $('a[rel="lightbox"]').featherlight({
        root: 'section#body'
    });
});

jQuery.extend({
    highlight: function(node, re, nodeName, className) {
        if (node.nodeType === 3) {
            var match = node.data.match(re);
            if (match) {
                var highlight = document.createElement(nodeName || 'span');
                highlight.className = className || 'highlight';
                var wordNode = node.splitText(match.index);
                wordNode.splitText(match[0].length);
                var wordClone = wordNode.cloneNode(true);
                highlight.appendChild(wordClone);
                wordNode.parentNode.replaceChild(highlight, wordNode);
                return 1; //skip added node in parent
            }
        } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
            !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
            !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
            for (var i = 0; i < node.childNodes.length; i++) {
                i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
            }
        }
        return 0;
    }
});

jQuery.fn.unhighlight = function(options) {
    var settings = {
        className: 'highlight',
        element: 'span'
    };
    jQuery.extend(settings, options);

    return this.find(settings.element + "." + settings.className).each(function() {
        var parent = this.parentNode;
        parent.replaceChild(this.firstChild, this);
        parent.normalize();
    }).end();
};

jQuery.fn.highlight = function(words, options) {
    var settings = {
        className: 'highlight',
        element: 'span',
        caseSensitive: false,
        wordsOnly: false
    };
    jQuery.extend(settings, options);

    if (!words) { return; }

    if (words.constructor === String) {
        words = [words];
    }
    words = jQuery.grep(words, function(word, i) {
        return word != '';
    });
    words = jQuery.map(words, function(word, i) {
        return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    });
    if (words.length == 0) { return this; }
    ;

    var flag = settings.caseSensitive ? "" : "i";
    var pattern = "(" + words.join("|") + ")";
    if (settings.wordsOnly) {
        pattern = "\\b" + pattern + "\\b";
    }
    var re = new RegExp(pattern, flag);

    return this.each(function() {
        jQuery.highlight(this, re, settings.element, settings.className);
    });
};
<|endoftext|>


<commit_before>function Generator(){
}

Generator.prototype.generateMapping = function(schema, cb){
  var cleanTree = getCleanTree(schema.tree, schema.paths, ''),
      mapping = getMapping(cleanTree, '');

  cb(null, { properties: mapping });
};

module.exports = Generator;

//
// Generates the mapping
//
// Can be called recursively.
//
// @param cleanTree
// @param prefix
// @return the mapping
//
function getMapping(cleanTree, prefix) {
  var mapping = {},
      value = {};

  if (prefix !== '') {
    prefix = prefix + '.';
  }

  for (var field in cleanTree) {
    value = cleanTree[field];
    mapping[field] = {};
    mapping[field].type = value.type;

    // If there is no type, then it's an object with subfields.
    if (!value.type) {
      mapping[field].type = 'object';
      mapping[field].properties = getMapping(value, prefix + field);
      continue;
    }

    // If it is a objectid make it a string.
    if(value.type === 'objectid'){
      mapping[field].type = 'string';
      continue;
    }

    // Else, it has a type and we want to map that!
    for (var prop in value) {
      // Map to field if it's an Elasticsearch option
      if (prop.indexOf('es_') === 0 && prop !== 'es_indexed') {
        mapping[field][prop.replace(/^es_/, '')] = value[prop];
      }
    }
  }

  return mapping;
}

//
// Generates a clean tree
//
// Can be called recursively.
//
// @param tree
// @param paths
// @param prefix
// @return the tree
//
function getCleanTree(tree, paths, prefix) {
  var cleanTree = {},
      type = '',
      value = {};

  delete tree.id;
  delete tree._id;

  if (prefix !== '') {
    prefix = prefix + '.';
  }

  for (var field in tree){
    type = getTypeFromPaths(paths, prefix + field);
    value = tree[field];

    // Field has some kind of type
    if (type) {
      // If it is an nestec schema
      if (value[0]) {
        cleanTree[field] = getCleanTree(value[0].tree, value[0].paths, '');
      } else {
        cleanTree[field] = value;
        cleanTree[field].type = type;
      }

    // It has no type for some reason
    } else {
      // Because it is an geo_point object!!
      if (typeof value === 'object' && value.geo_point) {
        cleanTree[field] = value.geo_point;
        continue;
      }

      // Because it is some other object!! Or we assumed that it is one.
      if (typeof value === 'object') {
        cleanTree[field] = getCleanTree(value, paths, prefix + field);
      }
    }
  }

  return cleanTree;
}

//
// Get type from the mongoose schema
//
// Returns the type, so in case none is set, it's the mongoose type.
//
// @param paths
// @param field
// @return the type or false
//
function getTypeFromPaths(paths, field) {
  var type = false;

  if (paths[field] && paths[field].options.type === Date) {
    return 'date';
  }

  if (paths[field]) {
    type = paths[field].instance ? paths[field].instance.toLowerCase() : 'object';
  }

  return type;
}
<commit_msg>handling schemas with nested blank objects<commit_after>function Generator(){
}

Generator.prototype.generateMapping = function(schema, cb){
  var cleanTree = getCleanTree(schema.tree, schema.paths, ''),
      mapping = getMapping(cleanTree, '');

  cb(null, { properties: mapping });
};

module.exports = Generator;

//
// Generates the mapping
//
// Can be called recursively.
//
// @param cleanTree
// @param prefix
// @return the mapping
//
function getMapping(cleanTree, prefix) {
  var mapping = {},
      value = {};

  if (prefix !== '') {
    prefix = prefix + '.';
  }

  for (var field in cleanTree) {
    value = cleanTree[field];
    mapping[field] = {};
    mapping[field].type = value.type;

    // If there is no type, then it's an object with subfields.
    if (!value.type) {
      mapping[field].type = 'object';
      mapping[field].properties = getMapping(value, prefix + field);
      continue;
    }

    // If it is a objectid make it a string.
    if(value.type === 'objectid'){
      mapping[field].type = 'string';
      continue;
    }

    // Else, it has a type and we want to map that!
    for (var prop in value) {
      // Map to field if it's an Elasticsearch option
      if (prop.indexOf('es_') === 0 && prop !== 'es_indexed') {
        mapping[field][prop.replace(/^es_/, '')] = value[prop];
      }
    }
  }

  return mapping;
}

//
// Generates a clean tree
//
// Can be called recursively.
//
// @param tree
// @param paths
// @param prefix
// @return the tree
//
function getCleanTree(tree, paths, prefix) {
  var cleanTree = {},
      type = '',
      value = {};

  delete tree.id;
  delete tree._id;

  if (prefix !== '') {
    prefix = prefix + '.';
  }

  for (var field in tree){
    type = getTypeFromPaths(paths, prefix + field);
    value = tree[field];

    // Field has some kind of type
    if (type) {
      // If it is an nestec schema
      if (value[0]) {
        //A nested schema can be just a blank object with no defined paths
        if(value[0].tree && value[0].paths){
          cleanTree[field] = getCleanTree(value[0].tree, value[0].paths, '');
        }else{
          cleanTree[field] = {
            type:'object'
          };
        }
      } else {
        cleanTree[field] = value;
        cleanTree[field].type = type;
      }

    // It has no type for some reason
    } else {
      // Because it is an geo_point object!!
      if (typeof value === 'object' && value.geo_point) {
        cleanTree[field] = value.geo_point;
        continue;
      }

      // Because it is some other object!! Or we assumed that it is one.
      if (typeof value === 'object') {
        cleanTree[field] = getCleanTree(value, paths, prefix + field);
      }
    }
  }

  return cleanTree;
}

//
// Get type from the mongoose schema
//
// Returns the type, so in case none is set, it's the mongoose type.
//
// @param paths
// @param field
// @return the type or false
//
function getTypeFromPaths(paths, field) {
  var type = false;

  if (paths[field] && paths[field].options.type === Date) {
    return 'date';
  }

  if (paths[field]) {
    type = paths[field].instance ? paths[field].instance.toLowerCase() : 'object';
  }

  return type;
}
<|endoftext|>


<commit_before>var util = require('util')
  , EventEmitter = require('events').EventEmitter

  , Promise = require('bluebird')
  , request = Promise.promisify(require('request'))

  , stream = require('stream')
  , streamifier = require('streamifier');

var API_KEY = process.env.KLOUDLESS_API_KEY || '';

var Logger = function(options) {
  this.LEVELS = {
    NONE: 0,
    ERROR: 1,
    WARN: 2,
    INFO: 3,
    DEBUG: 4
  };

  options.level = options.level || 'none';
  this.level = this.LEVELS[options.level.toUpperCase()] || 1;
};
Logger.prototype.error = function() {
  if (this.level >= this.LEVELS.ERROR) {
    console.error.apply(console, arguments);
  }
};
Logger.prototype.log = function() {
  if (this.level >= this.LEVELS.DEBUG) {
    console.log.apply(console, arguments);
  }
};

var MultipartUploadFactory = function(key) {
  API_KEY = key;
  return MultipartUpload;
};

var MultipartUpload = function(options){
  EventEmitter.call(this);

  this.account_id = options.account_id;
  this.parent_id = options.parent_id;
  this.name = options.name;
  this.overwrite = options.overwrite;

  this.max_connections = options.max_connections || 5;
  this.max_retries = options.max_retries || 3;

  this.file = options.file;
  if (!(this.file instanceof stream.Readable)) {
    this.file = streamifier.createReadStream(this.file);
  }

  this.session_id = options.session_id;
  this.offset = options.offset || 0;

  this.logger = new Logger({
    level: options.logging || 'none'
  });

  this.completed_chunks = [];

  this.logger.log('Multipart upload started for file', this.name);
  this.logger.log();

  var self = this;
  self.chain = Promise.resolve(true)
    .cancellable()
    .then(function() {
      return Promise.resolve(options.resuming? true : self.initialise());
    })
    .then(function() {
      return self.loadSession();
    })
    .then(function() {
      return self.upload();
    })
    .then(function() {
      return self.finalise();
    })
    .then(function() {
      return self.complete();
    })
    .catch(function(err) {
      self.logger.error('Fatal error encountered:', err);
      self.emit('error', err, {
        completed: self.completed_chunks.length,
        offset: self.offset
      });
    });
};

util.inherits(MultipartUpload, EventEmitter);

MultipartUpload.prototype.initialise = function() {
  this.logger.log('Initialising upload session...');

  var self = this;
  self.logger.log('Pinging: https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart' + (self.overwrite? '?overwrite=True':''));
  self.logger.log('Sending body:', JSON.stringify({
    parent_id: self.parent_id,
    name: self.name
  }));

  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart' + (self.overwrite? '?overwrite=True':''),
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/json'
    },
    method: 'POST',
    body: JSON.stringify({
      parent_id: self.parent_id,
      name: self.name
    })
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];
      response = JSON.parse(body);
      if(response.error_code) {
        self.logger.error('Could not initialise session:', response);
        throw new Error('Could not initialise session: ' + JSON.stringify(response));
      }
      self.session_id = response.id;
      self.emit('start', self.session_id);

      self.logger.log('...upload session initialised!');
      self.logger.log();
    });
};

MultipartUpload.prototype.loadSession = function() {
  this.logger.log('Loading session metadata...');

  var self = this;
  self.logger.log('Pinging: https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id);

  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id,
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/json'
    },
    method: 'GET'
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];
      response = JSON.parse(body);
      if(response.error_code) {
        self.logger.error('Could not load session metadata:', response);
        throw new Error('Could not load session metadata: ' + JSON.stringify(response));
      }
      self.chunk_size = response.part_size;
      self.logger.log('Upload session takes chunks of', self.chunk_size);

      self.logger.log('...session metadata loaded!');
      self.logger.log();
    });
};

MultipartUpload.prototype.upload = function() {
  this.logger.log('Beginning upload...');
  this.logger.log();

  var self = this;

  return new Promise(function(resolve) {
    var chunk = new Buffer(0)
      , part = 1
      , connections = {};

    self.file.on('data', function(datum) {
      // self.logger.log('Stream data received:', datum, datum.length);

      if (chunk.length + datum.length >= self.chunk_size) {
        var delta = chunk.length + datum.length - self.chunk_size;
        chunk = Buffer.concat([chunk, datum.slice(0, datum.length - delta)]);
        var extra = datum.slice(delta);

        self.logger.log('Delta:', delta);
        self.logger.log('Chunk created:', chunk.length + delta);
        self.logger.log('Queueing upload of part', part);
        self.logger.log();

        connections[part] = (function(c, p) {
          return self.uploadChunk(c, p)
            .then(function() {
              self.logger.log('Upload of chunk', p, 'complete');
              delete connections[p];
              if (Object.keys(connections).length < self.max_connections) {
                self.file.resume();
              }
            });
        })(chunk, part);

        part++;
        self.total_chunks++;

        chunk = extra;
      } else {
        chunk = Buffer.concat([chunk, datum]);
      }

      if (Object.keys(connections).length >= self.max_connections) {
        self.file.pause();
      }
    });

    self.file.on('end', function() {
      self.logger.log('File end reached.');
      self.logger.log('Chunk created:', chunk.length);
      self.logger.log('Queueing upload of part', part);
      self.logger.log();
      connections[part] = self.uploadChunk(chunk, part);

      self.logger.log('...uploads queued!');
      self.logger.log();
      var promises = Object.keys(connections).map(function(key) {
        return connections[key];
      });
      resolve(Promise.all(promises).then(function() {
        self.logger.log('...uploads complete!');
        self.logger.log();
      }));
    });
  });
};

MultipartUpload.prototype.uploadChunk = function(chunk, part, attempt) {
  var self = this;

  attempt = attempt || 0;
  if (attempt >= self.max_retries) {
    throw new Error('Could not connect to server.');
  }

  self.logger.log('Uploading chunk:', part);

  if (self.offset > part) {
    return new Promise(function(resolve) {
      self.logger.log('Chunk already uploaded, skipping...');
      self.logger.log();
      resolve();
    });
  }

  self.logger.log('Pinging: ' + 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id);
  self.logger.log('Chunk:', chunk);
  self.logger.log();

  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id,
    qs: {
      part_number: part
    },
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/octet-stream'
    },
    method: 'PUT',
    body: chunk
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];

      var status = http_response.statusCode;
      if (status != 200) {
        throw body;
      }

      self.completed_chunks.push(part);
      for (var i = 1; i <= self.completed_chunks.length + 1; i++) {
        self.logger.log('Offset computation:', i, self.completed_chunks, self.completed_chunks.indexOf(i));
        if (self.completed_chunks.indexOf(i) == -1) {
          self.offset = i - 1;
          break;
        }
      }
      self.emit('progress', {
        completed: self.completed_chunks.length,
        account_id: self.account_id,
        session_id: self.session_id,
        offset: self.offset
      });

      self.logger.log('Received status', status, 'for chunk', part);
      self.logger.log();
    })
    .catch(function(err) {
      return err.name == 'OperationalError';
    }, function(err) {
      self.logger.error('Connection error:', err);
      self.logger.error('Retrying upload of chunk:', part);
      self.logger.error();
      return self.uploadChunk(chunk, part, attempt + 1);
    })
    .catch(function(err) {
      self.logger.error('Fatal error encountered:', err);
      self.emit('error', err, {
        completed: self.completed_chunks.length,
        offset: self.offset
      });
    });
};

MultipartUpload.prototype.finalise = function() {
  this.logger.log('Finalising upload...');
  var self = this;
  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id + '/complete',
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/json'
    },
    method: 'POST'
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];
      response = JSON.parse(body);
      if(response.error_code) {
        self.logger.error('Could not finalise session:', response);
        throw new Error('Could not finalise session: ' + JSON.stringify(response));
      }
      self.logger.log('Uploaded file:', JSON.stringify(response));
      self.emit('success', response);

      self.logger.log('...upload finalised!');
      self.logger.log();
    });
};

MultipartUpload.prototype.abort = function() {
  var self = this;
  self.chain.cancel();
  return MultipartUpload.stopSession(self.account_id, self.session_id)
    .then(function(res) {
      self.logger.log('Aborting...');
      self.emit('abort');
    });
};

MultipartUpload.prototype.complete = function() {
  this.emit('complete');
};

MultipartUpload.resumeSession = function(options) {
  options.resuming = true;
  return new MultipartUpload(options);
};

MultipartUpload.stopSession = function(options) {
  var account_id = options.account_id
    , session_id = options.session_id;

  var self = this;
  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + account_id + '/multipart/' + session_id,
    headers: {
      'Authorization': 'ApiKey ' + API_KEY
    },
    method: 'DELETE'
  })
    .then(function(res) {
      var response = res[0]
        , body = res[1];
      self.logger.log(body);
    });
};

module.exports = MultipartUploadFactory;
<commit_msg>bug: fixing issue of larger multipart uploads being uploaded incorrectly<commit_after>var util = require('util')
  , EventEmitter = require('events').EventEmitter

  , Promise = require('bluebird')
  , request = Promise.promisify(require('request'))

  , stream = require('stream')
  , streamifier = require('streamifier');

var API_KEY = process.env.KLOUDLESS_API_KEY || '';

var Logger = function(options) {
  this.LEVELS = {
    NONE: 0,
    ERROR: 1,
    WARN: 2,
    INFO: 3,
    DEBUG: 4
  };

  options.level = options.level || 'none';
  this.level = this.LEVELS[options.level.toUpperCase()] || 1;
};
Logger.prototype.error = function() {
  if (this.level >= this.LEVELS.ERROR) {
    console.error.apply(console, arguments);
  }
};
Logger.prototype.log = function() {
  if (this.level >= this.LEVELS.DEBUG) {
    console.log.apply(console, arguments);
  }
};

var MultipartUploadFactory = function(key) {
  API_KEY = key;
  return MultipartUpload;
};

var MultipartUpload = function(options){
  EventEmitter.call(this);

  this.account_id = options.account_id;
  this.parent_id = options.parent_id;
  this.name = options.name;
  this.overwrite = options.overwrite;

  this.max_connections = options.max_connections || 5;
  this.max_retries = options.max_retries || 3;

  this.file = options.file;
  if (!(this.file instanceof stream.Readable)) {
    this.file = streamifier.createReadStream(this.file);
  }

  this.session_id = options.session_id;
  this.offset = options.offset || 0;

  this.logger = new Logger({
    level: options.logging || 'none'
  });

  this.completed_chunks = [];

  this.logger.log('Multipart upload started for file', this.name);
  this.logger.log();

  var self = this;
  self.chain = Promise.resolve(true)
    .cancellable()
    .then(function() {
      return Promise.resolve(options.resuming? true : self.initialise());
    })
    .then(function() {
      return self.loadSession();
    })
    .then(function() {
      return self.upload();
    })
    .then(function() {
      return self.finalise();
    })
    .then(function() {
      return self.complete();
    })
    .catch(function(err) {
      self.logger.error('Fatal error encountered:', err);
      self.emit('error', err, {
        completed: self.completed_chunks.length,
        offset: self.offset
      });
    });
};

util.inherits(MultipartUpload, EventEmitter);

MultipartUpload.prototype.initialise = function() {
  this.logger.log('Initialising upload session...');

  var self = this;
  self.logger.log('Pinging: https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart' + (self.overwrite? '?overwrite=True':''));
  self.logger.log('Sending body:', JSON.stringify({
    parent_id: self.parent_id,
    name: self.name
  }));

  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart' + (self.overwrite? '?overwrite=True':''),
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/json'
    },
    method: 'POST',
    body: JSON.stringify({
      parent_id: self.parent_id,
      name: self.name
    })
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];
      response = JSON.parse(body);
      if(response.error_code) {
        self.logger.error('Could not initialise session:', response);
        throw new Error('Could not initialise session: ' + JSON.stringify(response));
      }
      self.session_id = response.id;
      self.emit('start', self.session_id);

      self.logger.log('...upload session initialised!');
      self.logger.log();
    });
};

MultipartUpload.prototype.loadSession = function() {
  this.logger.log('Loading session metadata...');

  var self = this;
  self.logger.log('Pinging: https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id);

  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id,
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/json'
    },
    method: 'GET'
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];
      response = JSON.parse(body);
      if(response.error_code) {
        self.logger.error('Could not load session metadata:', response);
        throw new Error('Could not load session metadata: ' + JSON.stringify(response));
      }
      self.chunk_size = response.part_size;
      self.logger.log('Upload session takes chunks of', self.chunk_size);

      self.logger.log('...session metadata loaded!');
      self.logger.log();
    });
};

MultipartUpload.prototype.upload = function() {
  this.logger.log('Beginning upload...');
  this.logger.log();

  var self = this;

  return new Promise(function(resolve) {
    var chunk = new Buffer(0)
      , part = 1
      , connections = {};

    self.file.on('data', function(datum) {
      // self.logger.log('Stream data received:', datum, datum.length);

      if (chunk.length + datum.length >= self.chunk_size) {
        var delta = chunk.length + datum.length - self.chunk_size;
        chunk = Buffer.concat([chunk, datum.slice(0, datum.length - delta)]);
        var extra = datum.slice(datum.length - delta);

        self.logger.log('Delta:', delta);
        self.logger.log('Chunk created:', chunk.length + delta);
        self.logger.log('Queueing upload of part', part);
        self.logger.log();

        connections[part] = (function(c, p) {
          return self.uploadChunk(c, p)
            .then(function() {
              self.logger.log('Upload of chunk', p, 'complete');
              delete connections[p];
              if (Object.keys(connections).length < self.max_connections) {
                self.file.resume();
              }
            });
        })(chunk, part);

        part++;
        self.total_chunks++;

        chunk = extra;
      } else {
        chunk = Buffer.concat([chunk, datum]);
      }

      if (Object.keys(connections).length >= self.max_connections) {
        self.file.pause();
      }
    });

    self.file.on('end', function() {
      self.logger.log('File end reached.');
      self.logger.log('Chunk created:', chunk.length);
      self.logger.log('Queueing upload of part', part);
      self.logger.log();
      connections[part] = self.uploadChunk(chunk, part);

      self.logger.log('...uploads queued!');
      self.logger.log();
      var promises = Object.keys(connections).map(function(key) {
        return connections[key];
      });
      resolve(Promise.all(promises).then(function() {
        self.logger.log('...uploads complete!');
        self.logger.log();
      }));
    });
  });
};

MultipartUpload.prototype.uploadChunk = function(chunk, part, attempt) {
  var self = this;

  attempt = attempt || 0;
  if (attempt >= self.max_retries) {
    throw new Error('Could not connect to server.');
  }

  self.logger.log('Uploading chunk:', part);

  if (self.offset > part) {
    return new Promise(function(resolve) {
      self.logger.log('Chunk already uploaded, skipping...');
      self.logger.log();
      resolve();
    });
  }

  self.logger.log('Pinging: ' + 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id);
  self.logger.log('Chunk:', chunk);
  self.logger.log();

  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id,
    qs: {
      part_number: part
    },
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/octet-stream'
    },
    method: 'PUT',
    body: chunk
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];

      var status = http_response.statusCode;
      if (status != 200) {
        throw body;
      }

      self.completed_chunks.push(part);
      for (var i = 1; i <= self.completed_chunks.length + 1; i++) {
        self.logger.log('Offset computation:', i, self.completed_chunks, self.completed_chunks.indexOf(i));
        if (self.completed_chunks.indexOf(i) == -1) {
          self.offset = i - 1;
          break;
        }
      }
      self.emit('progress', {
        completed: self.completed_chunks.length,
        account_id: self.account_id,
        session_id: self.session_id,
        offset: self.offset
      });

      self.logger.log('Received status', status, 'for chunk', part);
      self.logger.log();
    })
    .catch(function(err) {
      return err.name == 'OperationalError';
    }, function(err) {
      self.logger.error('Connection error:', err);
      self.logger.error('Retrying upload of chunk:', part);
      self.logger.error();
      return self.uploadChunk(chunk, part, attempt + 1);
    })
    .catch(function(err) {
      self.logger.error('Fatal error encountered:', err);
      self.emit('error', err, {
        completed: self.completed_chunks.length,
        offset: self.offset
      });
    });
};

MultipartUpload.prototype.finalise = function() {
  this.logger.log('Finalising upload...');
  var self = this;
  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + self.account_id + '/multipart/' + self.session_id + '/complete',
    headers: {
      'Authorization': 'ApiKey ' + API_KEY,
      'Content-Type': 'application/json'
    },
    method: 'POST'
  })
    .then(function(res) {
      var http_response = res[0]
        , body = res[1];
      response = JSON.parse(body);
      if(response.error_code) {
        self.logger.error('Could not finalise session:', response);
        throw new Error('Could not finalise session: ' + JSON.stringify(response));
      }
      self.logger.log('Uploaded file:', JSON.stringify(response));
      self.emit('success', response);

      self.logger.log('...upload finalised!');
      self.logger.log();
    });
};

MultipartUpload.prototype.abort = function() {
  var self = this;
  self.chain.cancel();
  return MultipartUpload.stopSession(self.account_id, self.session_id)
    .then(function(res) {
      self.logger.log('Aborting...');
      self.emit('abort');
    });
};

MultipartUpload.prototype.complete = function() {
  this.emit('complete');
};

MultipartUpload.resumeSession = function(options) {
  options.resuming = true;
  return new MultipartUpload(options);
};

MultipartUpload.stopSession = function(options) {
  var account_id = options.account_id
    , session_id = options.session_id;

  var self = this;
  return request({
    url: 'https://api.kloudless.com/v0/accounts/' + account_id + '/multipart/' + session_id,
    headers: {
      'Authorization': 'ApiKey ' + API_KEY
    },
    method: 'DELETE'
  })
    .then(function(res) {
      var response = res[0]
        , body = res[1];
      self.logger.log(body);
    });
};

module.exports = MultipartUploadFactory;
<|endoftext|>