<commit_before>import ContainerConstants from '../constants/ContainerConstants';
import ValidatorUtil from '../../../../../src/js/utils/ValidatorUtil';
import {findNestedPropertyInObject} from '../../../../../src/js/utils/Util';
import {PROP_CONFLICT, PROP_DEPRECATED, PROP_MISSING_ALL, PROP_MISSING_ONE} from '../constants/ServiceErrorTypes';
import OperatorTypes from '../constants/OperatorTypes';

const {DOCKER} = ContainerConstants.type;

const MarathonAppValidators = {

  /**
   * Ensure that the user has provided either a `cmd` or a docker image field.
   *
   * The following checks are ported from the following marathon file:
   *
   * https://github.com/mesosphere/marathon/blob
   *       /08bc0aed722ad9f3e989a597cb264a994387756f/src/main/scala/mesosphere
   *       /marathon/state/AppDefinition.scala
   *
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  containsCmdArgsOrContainer(app) {
    const hasCmd = !ValidatorUtil.isEmpty(app.cmd);
    const hasArgs = !ValidatorUtil.isEmpty(app.args);

    // Dont accept both `args` and `cmd`
    if (hasCmd && hasArgs) {
      const notBothMessage = 'Please specify only one of `cmd` or `args`';
      const type = PROP_CONFLICT;
      const variables = {
        feature1: 'cmd',
        feature2: 'args'
      };

      return [
        {path: ['cmd'], message: notBothMessage, type, variables},
        {path: ['args'], message: notBothMessage, type, variables}
      ];
    }

    // Check if we have either of them
    if (hasCmd || hasArgs) {
      return [];
    }

    // Additional checks if we have container
    if (app.container) {

      // MesosDocker type of container (AppDefinition.scala#L553)
      if (app.container.docker && app.container.docker.image) {
        return [];
      }

      // MesosAppC type of container (Container.scala#L165)
      if (app.container.appc && app.container.appc.image) {

        // An image ID is a string of the format 'hash-value', where 'hash' is
        // the hash algorithm used and 'value' is the hex-encoded digest.
        // Currently the only permitted hash algorithm is sha512.
        // (Validation in Container.scala#L158)
        if (app.container.appc.id &&!/^sha512-./.exec(app.container.appc.id)) {
          return [
            {
              path: ['container', 'appc', 'id'],
              message: 'AppContainer id should start with \'sha512-\'',
              type: 'STRING_PATTERN',
              variables: {
                pattern: '^sha512-'
              }
            }
          ];
        }

        return [];
      }

    }

    // Create one error for every field, instead of showing the error
    // to the root.
    const message = 'You must specify a command, an argument or a container';
    const type = PROP_MISSING_ONE;
    const variables = {
      names: 'cmd, args, container.docker.image'
    };

    return [
      {path: ['cmd'], message, type, variables},
      {path: ['args'], message, type, variables},
      {path: ['container', 'docker', 'image'], message, type, variables}
    ];
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  complyWithResidencyRules(app) {
    const hasAppResidency = !ValidatorUtil.isEmpty(app.residency);
    let hasPersistentVolumes = false;

    // Check if app has at leas one persistent volume
    if (app.container && app.container.volumes) {
      hasPersistentVolumes = app.container.volumes
        .some((volume) => !ValidatorUtil.isEmpty(volume.persistent));
    }

    if (hasAppResidency !== hasPersistentVolumes) {
      const message = 'AppDefinition must contain persistent volumes and ' +
        'define residency';
      const type = PROP_MISSING_ALL;
      const variables = {
        names: 'residency, container.volumes'
      };

      return [
        {path: ['residency'], message, type, variables},
        {path: ['container', 'volumes'], message, type, variables}
      ];
    }

    return [];
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  complyWithIpAddressRules(app) {

    // (AppDefinition.scala#L697)
    if (ValidatorUtil.isEmpty(app.ipAddress)) {
      return [];
    }

    // (AppDefinition.scala#L538)
    if (ValidatorUtil.isEmpty(app.discoveryInfo)) {
      return [];
    }

    // (AppDefinition.scala#L539)
    const network = findNestedPropertyInObject(app, 'container.docker.network');
    if (ValidatorUtil.isEmpty(network)) {
      return [];
    }

    // (AppDefinition.scala#L539)
    if (/^(BRIDGE|USER)$/.exec(app.container.docker.network)) {
      const message = 'ipAddress/discovery is not allowed for Docker ' +
        'containers using BRIDGE or USER networks';
      const type = PROP_CONFLICT;
      const variables = {
        feature1: 'ipAddress or discoveryInfo',
        feature2: 'container.docker.network'
      };

      return [
        {path: ['ipAddress'], message, type, variables},
        {path: ['discoveryInfo'], message, type, variables},
        {path: ['container', 'docker', 'network'], message, type, variables}
      ];
    }

    // No errors
    return [];
  },

  /*
   * This validator ensures that `container.volumes.X.containerPath` does
   * not contain the character '/', according to marathon `^[^/]*$ rule
   *
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  containerVolmesPath(app) {
    const volumes = findNestedPropertyInObject(app, 'container.volumes');
    if (ValidatorUtil.isEmpty(volumes)) {
      return [];
    }

    return volumes.reduce(function (memo, volume, index) {
      const containerPath = volume.containerPath || '';

      // Local or external volumes require the same check
      if (containerPath.indexOf('/') !== -1) {
        memo.push({
          path: ['container', 'volumes', index, 'containerPath'],
          message: 'Should not contain "/"'
        });
      }

      return memo;
    }, []);
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  mustContainImageOnDocker(app) {
    const type = findNestedPropertyInObject(app, 'container.type');
    const image = findNestedPropertyInObject(app, 'container.docker.image');

    if ((type === DOCKER) && ValidatorUtil.isEmpty(image)) {
      return [
        {
          path: ['container', 'docker', 'image'],
          message: 'Must be specified when using the Docker Engine runtime. You can change runtimes under "Advanced Settings"',
          type: 'PROP_IS_MISSING',
          variables: {}
        }
      ];
    }

    return [];
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  mustNotContainUris(app) {
    if (ValidatorUtil.isDefined(app.uris) &&
        ValidatorUtil.isDefined(app.fetch)) {
      const message = '`uris` are deprecated. Please use `fetch` instead';
      const type = PROP_DEPRECATED;
      const variables = {
        name: 'uris'
      };

      return [
        {path: ['uris'], message, type, variables}
      ];
    }

    // No errors
    return [];
  },

  validateConstraints(app) {
    const constraints = findNestedPropertyInObject(app, 'constraints');
    if (constraints != null && !Array.isArray(constraints)) {
      // No errors
      return [{
        path: ['constraints'],
        message: 'constrains needs to be an array of 2 or 3 element arrays'
      }];
    }

    const message = 'You must specify a value for operator {{operator}}';
    const type = PROP_MISSING_ONE;
    const variables = {name: 'value'};

    return constraints.reduce((errors, [fieldName, operator, value], index) => {
      if (OperatorTypes.isRequired[operator] && ValidatorUtil.isEmpty(value)) {
        errors.push({
          path: ['constraints', index, 'value'],
          message: message.replace('{{operator}}', operator),
          type,
          variables
        });
      }

      return errors;
    }, []);
  }
};

module.exports = MarathonAppValidators;
<commit_msg>fix(validators): add default value for constraints<commit_after>import ContainerConstants from '../constants/ContainerConstants';
import ValidatorUtil from '../../../../../src/js/utils/ValidatorUtil';
import {findNestedPropertyInObject} from '../../../../../src/js/utils/Util';
import {PROP_CONFLICT, PROP_DEPRECATED, PROP_MISSING_ALL, PROP_MISSING_ONE} from '../constants/ServiceErrorTypes';
import OperatorTypes from '../constants/OperatorTypes';

const {DOCKER} = ContainerConstants.type;

const MarathonAppValidators = {

  /**
   * Ensure that the user has provided either a `cmd` or a docker image field.
   *
   * The following checks are ported from the following marathon file:
   *
   * https://github.com/mesosphere/marathon/blob
   *       /08bc0aed722ad9f3e989a597cb264a994387756f/src/main/scala/mesosphere
   *       /marathon/state/AppDefinition.scala
   *
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  containsCmdArgsOrContainer(app) {
    const hasCmd = !ValidatorUtil.isEmpty(app.cmd);
    const hasArgs = !ValidatorUtil.isEmpty(app.args);

    // Dont accept both `args` and `cmd`
    if (hasCmd && hasArgs) {
      const notBothMessage = 'Please specify only one of `cmd` or `args`';
      const type = PROP_CONFLICT;
      const variables = {
        feature1: 'cmd',
        feature2: 'args'
      };

      return [
        {path: ['cmd'], message: notBothMessage, type, variables},
        {path: ['args'], message: notBothMessage, type, variables}
      ];
    }

    // Check if we have either of them
    if (hasCmd || hasArgs) {
      return [];
    }

    // Additional checks if we have container
    if (app.container) {

      // MesosDocker type of container (AppDefinition.scala#L553)
      if (app.container.docker && app.container.docker.image) {
        return [];
      }

      // MesosAppC type of container (Container.scala#L165)
      if (app.container.appc && app.container.appc.image) {

        // An image ID is a string of the format 'hash-value', where 'hash' is
        // the hash algorithm used and 'value' is the hex-encoded digest.
        // Currently the only permitted hash algorithm is sha512.
        // (Validation in Container.scala#L158)
        if (app.container.appc.id &&!/^sha512-./.exec(app.container.appc.id)) {
          return [
            {
              path: ['container', 'appc', 'id'],
              message: 'AppContainer id should start with \'sha512-\'',
              type: 'STRING_PATTERN',
              variables: {
                pattern: '^sha512-'
              }
            }
          ];
        }

        return [];
      }

    }

    // Create one error for every field, instead of showing the error
    // to the root.
    const message = 'You must specify a command, an argument or a container';
    const type = PROP_MISSING_ONE;
    const variables = {
      names: 'cmd, args, container.docker.image'
    };

    return [
      {path: ['cmd'], message, type, variables},
      {path: ['args'], message, type, variables},
      {path: ['container', 'docker', 'image'], message, type, variables}
    ];
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  complyWithResidencyRules(app) {
    const hasAppResidency = !ValidatorUtil.isEmpty(app.residency);
    let hasPersistentVolumes = false;

    // Check if app has at leas one persistent volume
    if (app.container && app.container.volumes) {
      hasPersistentVolumes = app.container.volumes
        .some((volume) => !ValidatorUtil.isEmpty(volume.persistent));
    }

    if (hasAppResidency !== hasPersistentVolumes) {
      const message = 'AppDefinition must contain persistent volumes and ' +
        'define residency';
      const type = PROP_MISSING_ALL;
      const variables = {
        names: 'residency, container.volumes'
      };

      return [
        {path: ['residency'], message, type, variables},
        {path: ['container', 'volumes'], message, type, variables}
      ];
    }

    return [];
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  complyWithIpAddressRules(app) {

    // (AppDefinition.scala#L697)
    if (ValidatorUtil.isEmpty(app.ipAddress)) {
      return [];
    }

    // (AppDefinition.scala#L538)
    if (ValidatorUtil.isEmpty(app.discoveryInfo)) {
      return [];
    }

    // (AppDefinition.scala#L539)
    const network = findNestedPropertyInObject(app, 'container.docker.network');
    if (ValidatorUtil.isEmpty(network)) {
      return [];
    }

    // (AppDefinition.scala#L539)
    if (/^(BRIDGE|USER)$/.exec(app.container.docker.network)) {
      const message = 'ipAddress/discovery is not allowed for Docker ' +
        'containers using BRIDGE or USER networks';
      const type = PROP_CONFLICT;
      const variables = {
        feature1: 'ipAddress or discoveryInfo',
        feature2: 'container.docker.network'
      };

      return [
        {path: ['ipAddress'], message, type, variables},
        {path: ['discoveryInfo'], message, type, variables},
        {path: ['container', 'docker', 'network'], message, type, variables}
      ];
    }

    // No errors
    return [];
  },

  /*
   * This validator ensures that `container.volumes.X.containerPath` does
   * not contain the character '/', according to marathon `^[^/]*$ rule
   *
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  containerVolmesPath(app) {
    const volumes = findNestedPropertyInObject(app, 'container.volumes');
    if (ValidatorUtil.isEmpty(volumes)) {
      return [];
    }

    return volumes.reduce(function (memo, volume, index) {
      const containerPath = volume.containerPath || '';

      // Local or external volumes require the same check
      if (containerPath.indexOf('/') !== -1) {
        memo.push({
          path: ['container', 'volumes', index, 'containerPath'],
          message: 'Should not contain "/"'
        });
      }

      return memo;
    }, []);
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  mustContainImageOnDocker(app) {
    const type = findNestedPropertyInObject(app, 'container.type');
    const image = findNestedPropertyInObject(app, 'container.docker.image');

    if ((type === DOCKER) && ValidatorUtil.isEmpty(image)) {
      return [
        {
          path: ['container', 'docker', 'image'],
          message: 'Must be specified when using the Docker Engine runtime. You can change runtimes under "Advanced Settings"',
          type: 'PROP_IS_MISSING',
          variables: {}
        }
      ];
    }

    return [];
  },

  /**
   * @param {Object} app - The data to validate
   * @returns {Array} Returns an array with validation errors
   */
  mustNotContainUris(app) {
    if (ValidatorUtil.isDefined(app.uris) &&
        ValidatorUtil.isDefined(app.fetch)) {
      const message = '`uris` are deprecated. Please use `fetch` instead';
      const type = PROP_DEPRECATED;
      const variables = {
        name: 'uris'
      };

      return [
        {path: ['uris'], message, type, variables}
      ];
    }

    // No errors
    return [];
  },

  validateConstraints(app) {
    const constraints = findNestedPropertyInObject(app, 'constraints') || [];
    if (constraints != null && !Array.isArray(constraints)) {
      // No errors
      return [{
        path: ['constraints'],
        message: 'constrains needs to be an array of 2 or 3 element arrays'
      }];
    }

    const message = 'You must specify a value for operator {{operator}}';
    const type = PROP_MISSING_ONE;
    const variables = {name: 'value'};

    return constraints.reduce((errors, [fieldName, operator, value], index) => {
      if (OperatorTypes.isRequired[operator] && ValidatorUtil.isEmpty(value)) {
        errors.push({
          path: ['constraints', index, 'value'],
          message: message.replace('{{operator}}', operator),
          type,
          variables
        });
      }

      return errors;
    }, []);
  }
};

module.exports = MarathonAppValidators;
<|endoftext|>


<commit_before>const $ = require('jquery');
require('../node_modules/echarts-2.2.7/dist/echarts-all.js');
const ec3barline = require('./ec3_barlinepie.js')

function Ec3MapWidget(slice) {
  function refresh() {
    //TODO is and extraFilters False?
    $.getJSON(slice.jsonEndpoint({ extraFilters: false }), function(payload) {
        const fd = payload.form_data;
        let chart_options = ec3barline(slice).getOptions(payload)
        //regist the custom map type
        var ec3_map_type = fd.aiec3_map_type||'svg';
        var ec3_map_name = fd.aiec3_map_type_name||'customMapName';
        if (ec3_map_type === 'svg'){
          console.log('enter SVG....');
          echarts.util.mapData.params.params[ec3_map_name] = {
            getGeoJson: function(callback) {
              $.ajax({
                url: fd.aiec3_map_file,
                dataType: 'xml',
                type: "get",
                success: function(xml) {
                  callback(xml);
                }
              });
            }
          };
        } else if (ec3_map_type === 'json'){
          console.log('enter JSON....,ec3_map_type:'+ec3_map_type
            +",ec3_map_name:"+ec3_map_name);
          echarts.util.mapData.params.params[ec3_map_name] = {
            getGeoJson: function(callback) {
              $.getJSON(fd.aiec3_map_file,callback);
            }
          };
        }
        let chart = echarts.init(document.getElementById(slice.containerId));
        chart.setOption(chart_options);

        //TODO support multi series
        let c = 0;
        function showTip() {
          console.log('call showTip...');
          let areaName = chart_options.series[0].data[c].name;
          chart.component.tooltip.showTip({seriesIndex: 0, name: areaName});
          if (fd.aiec3_map_connected) {
            let vals = []
            vals.push(areaName);
            if (fd.aiec3_map_connect_field != ''){
              slice.setFilter(fd.aiec3_map_connect_field, vals);
            }
          }
          c = c + 1;
        }

        if (fd.aiec3_map_interval > 0) {
          setInterval(showTip, fd.aiec3_map_interval*1000);
        } else if (fd.aiec3_map_connected){
          chart.on('click', function (param){
            console.log('click params: ' + JSON.stringify(param));
            let vals = []
            vals.push(param.name);
            if (fd.aiec3_map_connect_field != ''){
              slice.setFilter(fd.aiec3_map_connect_field, vals);
            }
          });
        }

        slice.done(payload);
      })
      .fail(function(xhr) {
        slice.error(xhr.responseText, xhr);
      });
  }
  return {
    render: refresh,
    resize: refresh,
  };
}

module.exports = Ec3MapWidget;
<commit_msg>fix loop bug<commit_after>const $ = require('jquery');
require('../node_modules/echarts-2.2.7/dist/echarts-all.js');
const ec3barline = require('./ec3_barlinepie.js')

function Ec3MapWidget(slice) {
  function refresh() {
    //TODO is and extraFilters False?
    $.getJSON(slice.jsonEndpoint({ extraFilters: false }), function(payload) {
        const fd = payload.form_data;
        let chart_options = ec3barline(slice).getOptions(payload)
        //regist the custom map type
        var ec3_map_type = fd.aiec3_map_type||'svg';
        var ec3_map_name = fd.aiec3_map_type_name||'customMapName';
        if (ec3_map_type === 'svg'){
          console.log('enter SVG....');
          echarts.util.mapData.params.params[ec3_map_name] = {
            getGeoJson: function(callback) {
              $.ajax({
                url: fd.aiec3_map_file,
                dataType: 'xml',
                type: "get",
                success: function(xml) {
                  callback(xml);
                }
              });
            }
          };
        } else if (ec3_map_type === 'json'){
          console.log('enter JSON....,ec3_map_type:'+ec3_map_type
            +",ec3_map_name:"+ec3_map_name);
          echarts.util.mapData.params.params[ec3_map_name] = {
            getGeoJson: function(callback) {
              $.getJSON(fd.aiec3_map_file,callback);
            }
          };
        }
        let chart = echarts.init(document.getElementById(slice.containerId));
        chart.setOption(chart_options);

        //TODO support multi series
        let c = 0;
        function showTip() {
          console.log('call showTip...');
          let areaName = chart_options.series[0].data[c].name;
          chart.component.tooltip.showTip({seriesIndex: 0, name: areaName});
          if (fd.aiec3_map_connected) {
            let vals = []
            vals.push(areaName);
            if (fd.aiec3_map_connect_field != ''){
              slice.setFilter(fd.aiec3_map_connect_field, vals);
            }
          }
          c = c + 1 >= chart_options.series[0].data.length ? 0 : c + 1;
        }

        if (fd.aiec3_map_interval > 0) {
          setInterval(showTip, fd.aiec3_map_interval*1000);
        } else if (fd.aiec3_map_connected){
          chart.on('click', function (param){
            console.log('click params: ' + JSON.stringify(param));
            let vals = []
            vals.push(param.name);
            if (fd.aiec3_map_connect_field != ''){
              slice.setFilter(fd.aiec3_map_connect_field, vals);
            }
          });
        }

        slice.done(payload);
      })
      .fail(function(xhr) {
        slice.error(xhr.responseText, xhr);
      });
  }
  return {
    render: refresh,
    resize: refresh,
  };
}

module.exports = Ec3MapWidget;
<|endoftext|>


<commit_before>Bloob.Mouse = function(world, camera, scene) {
	this.world = world;
	this.camera = camera;
	this.scene = scene;
	this.input = new Scarlet.Input(this.camera.canvasId);
	this.input.initMouse();
	this.input.bind(Scarlet.KEY.MOUSE1, Bloob.Mouse.LeftButton);
	this.input.bind(Scarlet.KEY.MOUSE2, Bloob.Mouse.RightButton);

	this.body = {};
	this.pmId = -1;

	this.drag = Bloob.Utils.bind(function drag(body) {
		var mousePositionInWorldCoordinates = this.getMousePositionInWorld();
		var pointMass = body.pointMasses[this.pmId];
		var diff = VectorTools.calculateSpringForceVelAVelB(
			pointMass.Position,
			pointMass.Velocity,
			mousePositionInWorldCoordinates,
			Vector2.Zero.copy(), // velB,
			0.0, // springD,
			100.0, // springK,
			10.0 // damping
		);
		pointMass.Force.addSelf(diff);
	}, this);

	this.onLeftClick(Bloob.Mouse.InteractionTechniques["DRAG_BODY"], "DRAG_BODY");
	this.onRightClick(Bloob.Mouse.InteractionTechniques["DRAG_BODY"], "DRAG_BODY");

	this.initDatGui();
};

Bloob.Mouse.LeftButton = "leftclick";
Bloob.Mouse.RightButton = "rightclick";

Bloob.Mouse.prototype.update = function(timePassed) {
	this.leftTechnique.fireCallback(this, timePassed, Bloob.Mouse.LeftButton);
	this.rightTechnique.fireCallback(this, timePassed, Bloob.Mouse.RightButton);
	this.input.clearPressed();
};

Bloob.Mouse.prototype.getMousePositionInWorld = function() {
	return new Vector2(
		this.camera.scaleX.invert(this.input.mouse.x),
		this.camera.scaleY.invert(this.input.mouse.y)
	);
};

Bloob.Mouse.prototype.onLeftClick = function(interactionTechnique, interactionTechniqueName) {
	this.leftTechnique = interactionTechnique;
	if(typeof interactionTechniqueName !== "undefined")
		this.leftTechniqueName = interactionTechniqueName;
	return this;
};

Bloob.Mouse.prototype.onRightClick = function(interactionTechnique, interactionTechniqueName) {
	this.rightTechnique = interactionTechnique;
	if(typeof interactionTechniqueName !== "undefined")
		this.rightTechniqueName = interactionTechniqueName;
	return this;
};

// Interaction techniques:
Bloob.Mouse.InteractionTechniques = {
	"NOTHING": new Bloob.InteractionHandler(),
	"DRAG_BODY": new Bloob.InteractionHandler(function(timePassed, button) {
		if(this.input.pressed(button)) {
			var answer = this.world.getClosestPointMass(this.getMousePositionInWorld());
			this.body = this.world.getBody(answer.bodyId);
			this.body.addExternalForce(this.drag);
			this.pmId = answer.pointMassId;
		};
		if(this.input.released(button))
			this.body.removeExternalForce(this.drag);
	}),
	"SPAWN_CUBE": new Bloob.InteractionHandler(function(timePassed, button) {})
		.context(
			Bloob.BodyFactory.createBluePrint(SpringBody)
				.shape(Bloob.ShapeBuilder.Shapes.Cube)
				.pointMasses(1)
				.rotate(0)
				.scale(Vector2.One.copy())
				.isKinematic(false)
				.edgeSpringK(300.0)
				.edgeSpringDamp(5.0)
				.shapeSpringK(150.0)
				.shapeSpringDamp(5.0)
				.addInternalSpring(0, 2, 300, 10)
				.addInternalSpring(1, 3, 300, 10))
		.onPressed(function(timePassed, mouse) {
			this
				// TODO: set world with context
				.world(mouse.world)
				.translate(mouse.getMousePositionInWorld())
				.build();
			}
		),
	"SELECT_BODY": new Bloob.InteractionHandler(function(timePassed, button) {
		if(this.input.pressed(button)) {
			var answer = this.world.getClosestPointMass(this.getMousePositionInWorld());
			var body = this.world.getBody(answer.bodyId);
			Scarlet.log(body.aName);
		}
	}),
	// TODO: use scenes for pausing
	"PAUSE_GAME": new Bloob.InteractionHandler(function(timePassed, button) {
		if(this.input.pressed(button)) {
			if(typeof this.pause === "undefined") {
				this.pause = false;
			};
			if(this.pause) {
				Scarlet.log("Restart");
			}
			else
			{
				this.scene.stop();
			};
			this.pause = !this.pause;
		}
	}),
	"CALL_CALLBACK": new Bloob.InteractionHandler(function(timePassed, button) {
		if(this.input.pressed(button))
			if(typeof this.callback !== "undefined")
				this.callback();
	})
};

Bloob.Mouse.prototype.initDatGui = function() {
	var mouseFolder = gui.addFolder('Mouse');
	mouseFolder.open();

	// Get all interaction technique names.
	var interactionTechniqueNames = [];
	for (var technique in Bloob.Mouse.InteractionTechniques) {
		interactionTechniqueNames.push(technique);
	};

	// Enable all techniques to each button.
	var that = this;
	mouseFolder.add(this, 'leftTechniqueName', interactionTechniqueNames).listen().onChange(function(techniqueName) { that.onLeftClick(Bloob.Mouse.InteractionTechniques[techniqueName]); });
	mouseFolder.add(this, 'rightTechniqueName', interactionTechniqueNames).listen().onChange(function(techniqueName) { that.onRightClick(Bloob.Mouse.InteractionTechniques[techniqueName]); });

	return this;
};
<commit_msg>refactored drag_body and pause_game<commit_after>Bloob.Mouse = function(world, camera, scene) {
	this.world = world;
	this.camera = camera;
	this.scene = scene;
	this.input = new Scarlet.Input(this.camera.canvasId);
	this.input.initMouse();
	this.input.bind(Scarlet.KEY.MOUSE1, Bloob.Mouse.LeftButton);
	this.input.bind(Scarlet.KEY.MOUSE2, Bloob.Mouse.RightButton);

	this.onLeftClick(Bloob.Mouse.InteractionTechniques["DRAG_BODY"], "DRAG_BODY");
	this.onRightClick(Bloob.Mouse.InteractionTechniques["DRAG_BODY"], "DRAG_BODY");

	this.initDatGui();
};

Bloob.Mouse.LeftButton = "leftclick";
Bloob.Mouse.RightButton = "rightclick";

Bloob.Mouse.prototype.update = function(timePassed) {
	this.leftTechnique.fireCallback(this, timePassed, Bloob.Mouse.LeftButton);
	this.rightTechnique.fireCallback(this, timePassed, Bloob.Mouse.RightButton);
	this.input.clearPressed();
};

Bloob.Mouse.prototype.getMousePositionInWorld = function() {
	return new Vector2(
		this.camera.scaleX.invert(this.input.mouse.x),
		this.camera.scaleY.invert(this.input.mouse.y)
	);
};

Bloob.Mouse.prototype.onLeftClick = function(interactionTechnique, interactionTechniqueName) {
	this.leftTechnique = interactionTechnique;
	if(typeof interactionTechniqueName !== "undefined")
		this.leftTechniqueName = interactionTechniqueName;
	return this;
};

Bloob.Mouse.prototype.onRightClick = function(interactionTechnique, interactionTechniqueName) {
	this.rightTechnique = interactionTechnique;
	if(typeof interactionTechniqueName !== "undefined")
		this.rightTechniqueName = interactionTechniqueName;
	return this;
};

// Interaction techniques:
Bloob.Mouse.InteractionTechniques = {
	"NOTHING": new Bloob.InteractionHandler(),
	"DRAG_BODY": (function() {
		var context = {};
		context.body = {};
		context.pmId = -1;

		var handler = new Bloob.InteractionHandler(function(timePassed, button) {})
			.context(context)
			.onPressed(function(timePassed, mouse) {
				// TODO: only init this once in context
				var world = mouse.world;

				// create force callback
				this.drag = Bloob.Utils.bind(function drag(body) {
					var mousePositionInWorldCoordinates = mouse.getMousePositionInWorld();
					var pointMass = body.pointMasses[this.pmId];
					var diff = VectorTools.calculateSpringForceVelAVelB(
						pointMass.Position,
						pointMass.Velocity,
						mousePositionInWorldCoordinates,
						Vector2.Zero.copy(), // velB,
						0.0, // springD,
						100.0, // springK,
						10.0 // damping
					);
					pointMass.Force.addSelf(diff);
				}, this);

				var answer = world.getClosestPointMass(mouse.getMousePositionInWorld());
				this.body = world.getBody(answer.bodyId);
				this.body.addExternalForce(this.drag);
				this.pmId = answer.pointMassId;
			})
			.onReleased(function(timePassed, mouse) {
				this.body.removeExternalForce(this.drag);
			});

		return handler;
	})(),
	"SPAWN_CUBE": new Bloob.InteractionHandler()
		.onPressed(function(timePassed, mouse) {
			Bloob.BodyFactory.createBluePrint(SpringBody)
				.world(mouse.world)
				.shape(Bloob.ShapeBuilder.Shapes.Cube)
				.pointMasses(1)
				.translate(mouse.getMousePositionInWorld())
				.rotate(0)
				.scale(Vector2.One.copy())
				.isKinematic(false)
				.edgeSpringK(300.0)
				.edgeSpringDamp(5.0)
				.shapeSpringK(150.0)
				.shapeSpringDamp(5.0)
				.addInternalSpring(0, 2, 300, 10)
				.addInternalSpring(1, 3, 300, 10)
				.build();
			}
		),
	"SELECT_BODY": new Bloob.InteractionHandler()
		.onPressed(function(timePassed, mouse) {
			var answer = mouse.world.getClosestPointMass(mouse.getMousePositionInWorld());
			var body = mouse.world.getBody(answer.bodyId);
			Scarlet.log(body.aName);
		}
	),
	"PAUSE_GAME": new Bloob.InteractionHandler()
		.context({"pause": false})
		.onPressed(function(timePassed, mouse) {
			if(this.pause) {
				Scarlet.log("Restart");
			}
			else
			{
				Scarlet.log("STOP!!!!");
				mouse.scene.stop();
			};
			this.pause = !this.pause;
		}
	),
	"CALL_CALLBACK": new Bloob.InteractionHandler(function(timePassed, button) {
		if(this.input.pressed(button))
			if(typeof this.callback !== "undefined")
				this.callback();
	})
};

Bloob.Mouse.prototype.initDatGui = function() {
	var mouseFolder = gui.addFolder('Mouse');
	mouseFolder.open();

	// Get all interaction technique names.
	var interactionTechniqueNames = [];
	for (var technique in Bloob.Mouse.InteractionTechniques) {
		interactionTechniqueNames.push(technique);
	};

	// Enable all techniques to each button.
	var that = this;
	mouseFolder.add(this, 'leftTechniqueName', interactionTechniqueNames).listen().onChange(function(techniqueName) { that.onLeftClick(Bloob.Mouse.InteractionTechniques[techniqueName]); });
	mouseFolder.add(this, 'rightTechniqueName', interactionTechniqueNames).listen().onChange(function(techniqueName) { that.onRightClick(Bloob.Mouse.InteractionTechniques[techniqueName]); });

	return this;
};
<|endoftext|>


<commit_before>$(document).ready(function() {
     audio = $("#theaudio")[0];
     renders = $('#renders');
     rendersMask = $('#rendersMask');
     capcal = $('#capcal');
     capcalTotal = $('#capcalTotal');
     renderTotal = $('#renderTotal');
     zoomFactor = "";
     waveform = $('#bigWave img');
     plButton = $("#control .plButton");
     timecode = $("#timecode");
     zooms = $(".zoom");
     // What point in seconds the left-hand side of the
     // image refers to.
     beginningOfView = 0;
     // The 900 pitch values currently on screen
     pitchvals = new Array(900);
	 waveform.click(function(e) {
	 	var offset_l = $(this).offset().left - $(window).scrollLeft();
		var left = Math.round( (e.clientX - offset_l) );
	    mouPlay(left);
	});
     $(".zoom").click(function() {
         var level = $(this).data("length")
         zoom(level);
     });
     loaddata();

     $("#showRhythm").click(function(e) {
        drawwaveform();
     });

     soundManager.onready(function() {
         pagesound = soundManager.createSound({
               url: audiourl
         });

     });
});

function plotsa(context) {
    // Sa and sa+1 line.
    context.beginPath();
    // sa+1, dotted
    context.moveTo(0, 128);
    for (var i = 0; i < 900; i+=10) {
        context.moveTo(i, 128.5);
        context.lineTo(i+5, 128.5);
    }
    // sa, solid
    context.moveTo(0, 192.5);
    context.lineTo(900, 192.5);
    context.strokeStyle = "#eee";
    context.lineWidth = 1;
    context.stroke();
    context.closePath()
}

function spectrogram(context, view) {
    plotsa(context);
    var waszero = false;
    context.moveTo(0, 10);
    context.lineTo(10, 10);
    context.moveTo(0,0);
    var skip = secondsPerView / 4;
    // Start is in samples
    var start = (beginningOfView / secondsPerView) * 900 * skip;
    // Number of pixels we draw
    var end = Math.min(900+start, start+(view.length-start)/skip);
    var remaining = view.length-start;
    //console.debug("length " + view.length);
    //console.debug(remaining + " samples remaining");
    //console.debug("with skip, " + (remaining/skip) + " rem");
    //console.debug("skip " + skip);
    //console.debug("draw from " + start + " to " + end);
    context.beginPath();
    for (var i = start; i < end; i++) {
        // Find our point
        var xpos = i-start;
        var dataindex = start + xpos*skip;
        var data = view[dataindex];
        // Invert on canvas
        var tmp = 255-data;
        //console.debug(" at ("+xpos+","+tmp+") data " + dataindex);
        // We choose 0 if the pitch is unknown, or 255 if it's
        // higher than the 3 octaves above tonic. If so, we don't
        // want to draw something, just skip until the next value
        if (tmp == 0 || tmp == 255) {
            waszero = true;
            context.moveTo(xpos, tmp);
        } else {
            if (waszero) {
                waszero = false;
                context.moveTo(xpos, tmp);
            } else {
                context.lineTo(xpos, tmp);
            }
        }
        // Set the pitchvals so we can draw the histogram
        pitchvals[xpos] = tmp;
    }

    context.strokeStyle = "#e71d25";
    context.lineWidth = 2;
    context.stroke();
    context.closePath();
}

function plothistogram(pitch) {
    pitch = pitch || 0;
    var histogram = $("#histogramcanvas")[0];
    histogram.width = 200;
    histogram.height = 256;
    var context = histogram.getContext("2d");
    //context.moveTo(180, 10);
    //context.lineTo(200, 10);
    //context.moveTo(200, 256);
    var max = 0;
    var data = histogramdata;
    for (var i = 0; i < data.length; i++) {
        if (data[i] > max) {
            max = data[i];
        }
    }
    var factor = 150 / max;
    context.beginPath();
    for (var i = 0; i < data.length; i++) {
        v = data[i] * factor;
        context.lineTo(200-v, 256-i);
    }
    context.lineWidth = 2;
    context.strokeStyle = "#e71d25";
    context.stroke();
    context.closePath();

    // Pitch
    if (pitch > 0 && pitch < 255) {
        pitch = Math.floor(pitch) + 0.5;
        context.beginPath();
        context.moveTo(0, pitch);
        context.lineTo(200, pitch);
        context.lineWidth = 1;
        context.strokeStyle = "#000";
        context.stroke();
        context.closePath();
    }
}

function plotpitch() {
    // In order to account for slow internet connections,
    // we always load one image ahead of what we need.
    // TODO: We need to know when the final image is.
    var spec = new Image();
    spec.src = specurl;
    var view = new Uint8Array(pitchdata);
    var canvas = $("#pitchcanvas")[0];
    canvas.width = 900;
    canvas.height = 256;
    var context = canvas.getContext("2d");
    spec.onload = function() {
        context.drawImage(spec, 0, 0);
        spectrogram(context, view);
    }
}

function drawwaveform() {
    var wave = new Image();
    wave.src = waveformurl;
    var canvas = $("#rhythmcanvas")[0];
    canvas.width = 900;
    canvas.height = 256;
    var context = canvas.getContext("2d");
    wave.onload = function() {
        context.drawImage(wave, 0, 0);
        plottempo(context, aksharadata);
        plotticks(context, rhythmdata);
    }
}

function sortNumber(a,b) {
    return a - b;
}

function plotticks(context, data) {
    // TODO: it looked like for some of this data
    // we were sorting by asciibetical not numeric
    data.sort(sortNumber);
    from = data[0];
    to = data[data.length-1];
    $("#pulseFrom").html(formatseconds(from));
    $("#pulseTo").html(formatseconds(to));
    var show = $("#showRhythm").is(':checked');

    if ((secondsPerView == 8 || secondsPerView == 4) && show) {
        context.beginPath();
        //console.debug("draw ticks");
        for (var i = 0; i < data.length; i++) {
            var val = data[i];
            var endVal = beginningOfView+secondsPerView;
            if (val > beginningOfView && val < endVal) {
                //console.debug("got a tick at " + val);
                // We draw it.
                var position = (val-beginningOfView) / secondsPerView * 900;
                position = Math.floor(position) + 0.5;
                //console.debug("drawing at " + position);
                context.moveTo(position, 0);
                context.lineTo(position, 255);
            }
        context.lineWidth = 1;
        context.strokeStyle = "#e71d25";
        context.stroke();
        }
        context.closePath();
    }
}

function plottempo(context, data) {
    var low = 1;
    var high = 0;
    for (var i = 0; i < data.length; i++) {
        var val = data[i][1];
        if (val > high) {
            high = val;
        }
        if (val < low) {
            low = val;
        }
    }
    $(".tempoup").html(high*1000 + " ms");
    $(".tempodown").html(low*1000 + " ms");
    high = high * 1.2;
    low = low * 0.8;
    var factor = 128 / (high - low);

    var secPerPixel = 900 / secondsPerView;
    var moved = false;
    context.beginPath();
    for (var i = 0; i < data.length; i++) {
        var time = data[i][0];
        if (time >= beginningOfView) {
            // Data points are every 0.5 seconds
            // TODO: This doesn't draw the point at or near x=0
            var x = (time-beginningOfView) * secPerPixel;
            if (x > 900) {
                break;
            }
            var y = (data[i][1]-low) * factor;
            y = Math.floor(y) + 0.5;
            if (!moved) {
                context.moveTo(0, 256-y);
                moved = true;
            }
            context.lineTo(x, 256-y);
            //console.debug("line at position "+y);
        }
    }
    context.strokeStyle = "#eee";
    context.lineWidth = 1;
    context.stroke();
    context.closePath();
}

function loaddata() {
    // We do pitch track with manual httprequest, because
    // we want typedarray access

    histogramDone = false;
    pitchDone = false;
    var oReq = new XMLHttpRequest();
    oReq.open("GET", pitchtrackurl, true);
    oReq.responseType = "arraybuffer";
    oReq.onload = function(oEvent) {
        if (oReq.status == 200) {
            pitchdata = oReq.response;
            pitchDone = true;
            dodraw();
        }
    };
    oReq.send();
    $.ajax(histogramurl, {dataType: "json", type: "GET",
        success: function(data, textStatus, xhr) {
            histogramdata = data;
            histogramDone = true;
            dodraw();
    }});

    var ticksDone = false;
    var rhythmDone = false;

    $.ajax(rhythmurl, {dataType: "json", type: "GET",
        success: function(data, textStatus, xhr) {
            rhythmdata = data;
            ticksDone = true;
            dodraw();
    }});

    $.ajax(aksharaurl, {dataType: "json", type: "GET",
        success: function(data, textStatus, xhr) {
            aksharadata = data;
            rhythmDone = true;
            dodraw();
    }});

    function dodraw() {
        if (ticksDone && rhythmDone && pitchDone && histogramDone) {
            drawdata();
        }
    }
}

function drawdata() {
    drawwaveform();
    plotpitch();
    plothistogram();
    var start = beginningOfView;
    var skip = secondsPerView / 4;
    $(".timecode1").html(formatseconds(start));
    $(".timecode2").html(formatseconds(start+skip));
    $(".timecode3").html(formatseconds(start+skip*2));
    $(".timecode4").html(formatseconds(start+skip*3));
    $(".timecode5").html(formatseconds(start+skip*4));
}

function mouPlay(desti){
    console.debug("play click!");
	posicio = renders.position();
	distclick = Math.abs(posicio.left)+desti;
	percent = distclick/waveform.width();

	nouPunt = pagesound.duration * percentPunt;
	console.log(nouPunt+" - "+pagesound.duration);

    durseconds = pagesound.duration / 1000;
    part = Math.floor(durseconds % secondsPerView);
    replacepart(part);

	pagesound.pause();
    pagesound.setposition(clicktime);
	pagesound.play();
}

function play() {
    pagesound.play({onfinish: function() {
        window.clearInterval(int);
    }});
    int = window.setInterval(updateProgress, 30);
}
function pause() {
    pagesound.pause();
    window.clearInterval(int);
}

function formatseconds(seconds) {
    progress_seconds = Math.ceil(seconds);
    progress_minutes = Math.floor(seconds/60);
    resto = (progress_seconds-(progress_minutes*60));
    // never show :60 in seconds part
    if (resto >= 60) {
        resto = 0;
        progress_minutes += 1;
    }
    if(resto<10){
		resto = "0"+resto;
    }
    if(progress_minutes<10){
		progress_minutes = "0"+progress_minutes;
    }
    timecodeHtml = ""+progress_minutes+":"+resto;
    return timecodeHtml;
}

function replacepart(pnum) {
    waveformurl = waveformurl.replace(/part=[0-9]+/, "part="+pnum);
    specurl = specurl.replace(/part=[0-9]+/, "part="+pnum);
    drawdata();
}

function updateProgress() {
    var currentTime = pagesound.position / 1000;
    // formatseconds appears to run 1 second ahead of time,
    // so correct for it here
    formattime = formatseconds(currentTime-1);
    progress_percent = (currentTime-beginningOfView) / secondsPerView * 100;
    total_progress_frac = (currentTime/recordinglengthseconds);
	ampleMask = rendersMask.width();
	ampleRenders = renders.width();
	ampleRenderTotal = renderTotal.width();
    leftLargeView = ((ampleRenders*progress_percent)/100);
    leftSmallView = ampleRenderTotal*total_progress_frac;
    capcal.css('left', leftLargeView-5);
    capcalTotal.css('left', leftSmallView-6);

    if (leftLargeView > 900) {
        beginningOfView += secondsPerView;
        pnum = Math.floor(beginningOfView / secondsPerView + 1);
        replacepart(pnum)
    }
    timecode.html(formattime + "<span>"+recordinglengthfmt+"</span>");

    // Current pitch
    var pitchindex = Math.floor(leftLargeView);
    var p = pitchvals[pitchindex];
    plothistogram(p);
};

function zoom(level){
    secondsPerView = level;
    waveformurl = waveformurl.replace(/waveform[0-9]{1,2}/, "waveform"+level);
    specurl = specurl.replace(/spectrum[0-9]{1,2}/, "spectrum"+level);
    // When we go from a zoomed in to a zoomed out size,
    // we need to make sure that `beginningOfView` is on an
    // image boundary
    beginningOfView = Math.floor(beginningOfView / secondsPerView);
    pnum = Math.floor(beginningOfView / secondsPerView + 1);
    waveformurl = waveformurl.replace(/part=[0-9]+/, "part="+pnum);
    specurl = specurl.replace(/part=[0-9]+/, "part="+pnum);
    drawdata();
}

function playrecord(){
	if(plButton.hasClass("stop")){
		pause();
		plButton.removeClass("stop");
	}else{
		play();
		plButton.addClass("stop");
	}
}

<commit_msg>fix position detection<commit_after>$(document).ready(function() {
     audio = $("#theaudio")[0];
     renders = $('#renders');
     rendersMask = $('#rendersMask');
     capcal = $('#capcal');
     capcalTotal = $('#capcalTotal');
     renderTotal = $('#renderTotal');
     zoomFactor = "";
     waveform = $('#renderTotal img');
     plButton = $("#control .plButton");
     timecode = $("#timecode");
     zooms = $(".zoom");
     // What point in seconds the left-hand side of the
     // image refers to.
     beginningOfView = 0;
     // The 900 pitch values currently on screen
     pitchvals = new Array(900);
	 waveform.click(function(e) {
	 	var offset_l = $(this).offset().left - $(window).scrollLeft();
		var left = Math.round( (e.clientX - offset_l) );
	    mouPlay(left);
	});
     $(".zoom").click(function() {
         var level = $(this).data("length")
         zoom(level);
     });
     loaddata();

     $("#showRhythm").click(function(e) {
        drawwaveform();
     });

     soundManager.onready(function() {
         pagesound = soundManager.createSound({
               url: audiourl
         });

     });
});

function plotsa(context) {
    // Sa and sa+1 line.
    context.beginPath();
    // sa+1, dotted
    context.moveTo(0, 128);
    for (var i = 0; i < 900; i+=10) {
        context.moveTo(i, 128.5);
        context.lineTo(i+5, 128.5);
    }
    // sa, solid
    context.moveTo(0, 192.5);
    context.lineTo(900, 192.5);
    context.strokeStyle = "#eee";
    context.lineWidth = 1;
    context.stroke();
    context.closePath()
}

function spectrogram(context, view) {
    plotsa(context);
    var waszero = false;
    context.moveTo(0, 10);
    context.lineTo(10, 10);
    context.moveTo(0,0);
    var skip = secondsPerView / 4;
    // Start is in samples
    var start = (beginningOfView / secondsPerView) * 900 * skip;
    // Number of pixels we draw
    var end = Math.min(900+start, start+(view.length-start)/skip);
    var remaining = view.length-start;
    //console.debug("length " + view.length);
    //console.debug(remaining + " samples remaining");
    //console.debug("with skip, " + (remaining/skip) + " rem");
    //console.debug("skip " + skip);
    //console.debug("draw from " + start + " to " + end);
    context.beginPath();
    for (var i = start; i < end; i++) {
        // Find our point
        var xpos = i-start;
        var dataindex = start + xpos*skip;
        var data = view[dataindex];
        // Invert on canvas
        var tmp = 255-data;
        //console.debug(" at ("+xpos+","+tmp+") data " + dataindex);
        // We choose 0 if the pitch is unknown, or 255 if it's
        // higher than the 3 octaves above tonic. If so, we don't
        // want to draw something, just skip until the next value
        if (tmp == 0 || tmp == 255) {
            waszero = true;
            context.moveTo(xpos, tmp);
        } else {
            if (waszero) {
                waszero = false;
                context.moveTo(xpos, tmp);
            } else {
                context.lineTo(xpos, tmp);
            }
        }
        // Set the pitchvals so we can draw the histogram
        pitchvals[xpos] = tmp;
    }

    context.strokeStyle = "#e71d25";
    context.lineWidth = 2;
    context.stroke();
    context.closePath();
}

function plothistogram(pitch) {
    pitch = pitch || 0;
    var histogram = $("#histogramcanvas")[0];
    histogram.width = 200;
    histogram.height = 256;
    var context = histogram.getContext("2d");
    //context.moveTo(180, 10);
    //context.lineTo(200, 10);
    //context.moveTo(200, 256);
    var max = 0;
    var data = histogramdata;
    for (var i = 0; i < data.length; i++) {
        if (data[i] > max) {
            max = data[i];
        }
    }
    var factor = 150 / max;
    context.beginPath();
    for (var i = 0; i < data.length; i++) {
        v = data[i] * factor;
        context.lineTo(200-v, 256-i);
    }
    context.lineWidth = 2;
    context.strokeStyle = "#e71d25";
    context.stroke();
    context.closePath();

    // Pitch
    if (pitch > 0 && pitch < 255) {
        pitch = Math.floor(pitch) + 0.5;
        context.beginPath();
        context.moveTo(0, pitch);
        context.lineTo(200, pitch);
        context.lineWidth = 1;
        context.strokeStyle = "#000";
        context.stroke();
        context.closePath();
    }
}

function plotpitch() {
    // In order to account for slow internet connections,
    // we always load one image ahead of what we need.
    // TODO: We need to know when the final image is.
    var spec = new Image();
    spec.src = specurl;
    var view = new Uint8Array(pitchdata);
    var canvas = $("#pitchcanvas")[0];
    canvas.width = 900;
    canvas.height = 256;
    var context = canvas.getContext("2d");
    spec.onload = function() {
        context.drawImage(spec, 0, 0);
        spectrogram(context, view);
    }
}

function drawwaveform() {
    var wave = new Image();
    wave.src = waveformurl;
    var canvas = $("#rhythmcanvas")[0];
    canvas.width = 900;
    canvas.height = 256;
    var context = canvas.getContext("2d");
    wave.onload = function() {
        context.drawImage(wave, 0, 0);
        plottempo(context, aksharadata);
        plotticks(context, rhythmdata);
    }
}

function sortNumber(a,b) {
    return a - b;
}

function plotticks(context, data) {
    // TODO: it looked like for some of this data
    // we were sorting by asciibetical not numeric
    data.sort(sortNumber);
    from = data[0];
    to = data[data.length-1];
    $("#pulseFrom").html(formatseconds(from));
    $("#pulseTo").html(formatseconds(to));
    var show = $("#showRhythm").is(':checked');

    if ((secondsPerView == 8 || secondsPerView == 4) && show) {
        context.beginPath();
        //console.debug("draw ticks");
        for (var i = 0; i < data.length; i++) {
            var val = data[i];
            var endVal = beginningOfView+secondsPerView;
            if (val > beginningOfView && val < endVal) {
                //console.debug("got a tick at " + val);
                // We draw it.
                var position = (val-beginningOfView) / secondsPerView * 900;
                position = Math.floor(position) + 0.5;
                //console.debug("drawing at " + position);
                context.moveTo(position, 0);
                context.lineTo(position, 255);
            }
        context.lineWidth = 1;
        context.strokeStyle = "#e71d25";
        context.stroke();
        }
        context.closePath();
    }
}

function plottempo(context, data) {
    var low = 1;
    var high = 0;
    for (var i = 0; i < data.length; i++) {
        var val = data[i][1];
        if (val > high) {
            high = val;
        }
        if (val < low) {
            low = val;
        }
    }
    $(".tempoup").html(high*1000 + " ms");
    $(".tempodown").html(low*1000 + " ms");
    high = high * 1.2;
    low = low * 0.8;
    var factor = 128 / (high - low);

    var secPerPixel = 900 / secondsPerView;
    var moved = false;
    context.beginPath();
    for (var i = 0; i < data.length; i++) {
        var time = data[i][0];
        if (time >= beginningOfView) {
            // Data points are every 0.5 seconds
            // TODO: This doesn't draw the point at or near x=0
            var x = (time-beginningOfView) * secPerPixel;
            if (x > 900) {
                break;
            }
            var y = (data[i][1]-low) * factor;
            y = Math.floor(y) + 0.5;
            if (!moved) {
                context.moveTo(0, 256-y);
                moved = true;
            }
            context.lineTo(x, 256-y);
            //console.debug("line at position "+y);
        }
    }
    context.strokeStyle = "#eee";
    context.lineWidth = 1;
    context.stroke();
    context.closePath();
}

function loaddata() {
    // We do pitch track with manual httprequest, because
    // we want typedarray access

    histogramDone = false;
    pitchDone = false;
    var oReq = new XMLHttpRequest();
    oReq.open("GET", pitchtrackurl, true);
    oReq.responseType = "arraybuffer";
    oReq.onload = function(oEvent) {
        if (oReq.status == 200) {
            pitchdata = oReq.response;
            pitchDone = true;
            dodraw();
        }
    };
    oReq.send();
    $.ajax(histogramurl, {dataType: "json", type: "GET",
        success: function(data, textStatus, xhr) {
            histogramdata = data;
            histogramDone = true;
            dodraw();
    }});

    var ticksDone = false;
    var rhythmDone = false;

    $.ajax(rhythmurl, {dataType: "json", type: "GET",
        success: function(data, textStatus, xhr) {
            rhythmdata = data;
            ticksDone = true;
            dodraw();
    }});

    $.ajax(aksharaurl, {dataType: "json", type: "GET",
        success: function(data, textStatus, xhr) {
            aksharadata = data;
            rhythmDone = true;
            dodraw();
    }});

    function dodraw() {
        if (ticksDone && rhythmDone && pitchDone && histogramDone) {
            drawdata();
        }
    }
}

function drawdata() {
    drawwaveform();
    plotpitch();
    plothistogram();
    var start = beginningOfView;
    var skip = secondsPerView / 4;
    $(".timecode1").html(formatseconds(start));
    $(".timecode2").html(formatseconds(start+skip));
    $(".timecode3").html(formatseconds(start+skip*2));
    $(".timecode4").html(formatseconds(start+skip*3));
    $(".timecode5").html(formatseconds(start+skip*4));
}

function mouPlay(desti){
    console.debug("play click!");
	percent = desti/waveform.width();

	clicktime = pagesound.duration * percent;
	console.log(clicktime+" - "+pagesound.duration);

    posseconds = clicktime / 1000;
    part = Math.ceil(posseconds % secondsPerView);

	pagesound.pause();
    pagesound.setPosition(clicktime);
    replacepart(part);
	pagesound.play();
}

function play() {
    pagesound.play({onfinish: function() {
        window.clearInterval(int);
    }});
    int = window.setInterval(updateProgress, 30);
}
function pause() {
    pagesound.pause();
    window.clearInterval(int);
}

function formatseconds(seconds) {
    progress_seconds = Math.ceil(seconds);
    progress_minutes = Math.floor(seconds/60);
    resto = (progress_seconds-(progress_minutes*60));
    // never show :60 in seconds part
    if (resto >= 60) {
        resto = 0;
        progress_minutes += 1;
    }
    if(resto<10){
		resto = "0"+resto;
    }
    if(progress_minutes<10){
		progress_minutes = "0"+progress_minutes;
    }
    timecodeHtml = ""+progress_minutes+":"+resto;
    return timecodeHtml;
}

function replacepart(pnum) {
    waveformurl = waveformurl.replace(/part=[0-9]+/, "part="+pnum);
    specurl = specurl.replace(/part=[0-9]+/, "part="+pnum);
    drawdata();
}

function updateProgress() {
    var currentTime = pagesound.position / 1000;
    // formatseconds appears to run 1 second ahead of time,
    // so correct for it here
    formattime = formatseconds(currentTime-1);
    progress_percent = (currentTime-beginningOfView) / secondsPerView * 100;
    total_progress_frac = (currentTime/recordinglengthseconds);
	ampleMask = rendersMask.width();
	ampleRenders = renders.width();
	ampleRenderTotal = renderTotal.width();
    leftLargeView = ((ampleRenders*progress_percent)/100);
    leftSmallView = ampleRenderTotal*total_progress_frac;
    capcal.css('left', leftLargeView-5);
    capcalTotal.css('left', leftSmallView-6);

    if (leftLargeView > 900) {
        beginningOfView += secondsPerView;
        pnum = Math.floor(beginningOfView / secondsPerView + 1);
        replacepart(pnum)
    }
    timecode.html(formattime + "<span>"+recordinglengthfmt+"</span>");

    // Current pitch
    var pitchindex = Math.floor(leftLargeView);
    var p = pitchvals[pitchindex];
    plothistogram(p);
};

function zoom(level){
    secondsPerView = level;
    waveformurl = waveformurl.replace(/waveform[0-9]{1,2}/, "waveform"+level);
    specurl = specurl.replace(/spectrum[0-9]{1,2}/, "spectrum"+level);
    // When we go from a zoomed in to a zoomed out size,
    // we need to make sure that `beginningOfView` is on an
    // image boundary
    beginningOfView = Math.floor(beginningOfView / secondsPerView);
    pnum = Math.floor(beginningOfView / secondsPerView + 1);
    waveformurl = waveformurl.replace(/part=[0-9]+/, "part="+pnum);
    specurl = specurl.replace(/part=[0-9]+/, "part="+pnum);
    drawdata();
}

function playrecord(){
	if(plButton.hasClass("stop")){
		pause();
		plButton.removeClass("stop");
	}else{
		play();
		plButton.addClass("stop");
	}
}

<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

import { PHASE_START_EVENT_TYPE, PHASE_END_EVENT_TYPE } from 'common/fabricateBossPhaseEvents';
import { captureException } from 'common/errorLogger';
import { findByBossId } from 'raids';

import { EventsParseError } from './EventParser';

export const SELECTION_ALL_PHASES = "ALL";

class PhaseParser extends React.PureComponent {
  static propTypes = {
    fight: PropTypes.shape({
      start_time: PropTypes.number.isRequired,
      end_time: PropTypes.number.isRequired,
      boss: PropTypes.number.isRequired,
    }).isRequired,
    phase: PropTypes.string.isRequired,
    bossPhaseEvents: PropTypes.array,
    events: PropTypes.array.isRequired,
    children: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      isLoading: true,
    };
  }

  componentDidMount() {
    // noinspection JSIgnoredPromiseFromCall
    this.parse();
  }
  componentDidUpdate(prevProps, prevState, prevContext) {
    const phasesChanged = this.props.bossPhaseEvents !== prevProps.bossPhaseEvents || this.props.fight !== prevProps.fight;
    const eventsChanged = this.props.phase !== prevProps.phase || this.props.events !== prevProps.events;
    if (phasesChanged || eventsChanged) {
      this.setState({
        isLoading: true,
      });
      // noinspection JSIgnoredPromiseFromCall
      this.parse(phasesChanged);
    }
  }

  makePhases(){
    const { bossPhaseEvents, fight } = this.props;
    if(!bossPhaseEvents){
      return null;
    }
    const phaseStarts = [...new Set(bossPhaseEvents.filter(e => e.type === PHASE_START_EVENT_TYPE).map(e => e.phase.key))]; //distinct phase starts
    const phaseEnds = [...new Set(bossPhaseEvents.filter(e => e.type === PHASE_END_EVENT_TYPE).map(e => e.phase.key))]; //distinct phase ends
    const phaseKeys = phaseStarts.filter(e => phaseEnds.includes(e)); //only include phases that contain start and end event
    const bossPhases = findByBossId(fight.boss).fight.phases;
    return Object.keys(bossPhases)
    .filter(e => phaseKeys.includes(e)) //only include boss phases that have a valid phase key
    .reduce((obj, key) => {
      return {
        ...obj,
        [key]: bossPhases[key],
      };
    }, {});
  }

  makeEvents(phases) {
    const { bossPhaseEvents, events, phase } = this.props;
    if(!(bossPhaseEvents instanceof Array)
      || !(Object.keys(phases).includes(phase))
      || phase === SELECTION_ALL_PHASES
    ){
      return {start: this.props.fight.start_time, events: bossPhaseEvents ? [...bossPhaseEvents, ...events] : events, end: this.props.fight.end_time};
    }
    const startEvent = bossPhaseEvents.find(e => e.type === PHASE_START_EVENT_TYPE && e.phase.key === phase);
    const endEvent = bossPhaseEvents.find(e => e.type === PHASE_END_EVENT_TYPE && e.phase.key === phase);
    const phaseEvents = events.filter(event =>
        event.timestamp >= startEvent.timestamp
        && event.timestamp <= endEvent.timestamp
      );
    const prePhaseEvents = this.findRelevantPrePhaseEvents(events.filter(event => event.timestamp < startEvent.timestamp));
    return {start: startEvent.timestamp, events: [...prePhaseEvents, startEvent, ...phaseEvents, endEvent], end: endEvent.timestamp};
  }

  //TODO: find events before the phase that are relevant in this phase (aka cooldowns and buffs) and include them in analysis
  findRelevantPrePhaseEvents(events){
    const applyBuffEvents = this.findRelevantBuffEvents(events);

    return applyBuffEvents;
  }

  findRelevantBuffEvents(events){
    return events.filter(e => e.type === "applybuff" || e.type === "applydebuff")
    .filter(e =>
      //only keep prior apply(de)buff events if they dont have an associated remove(de)buff event
      events.find(e2 =>
        e2.type === e.type.replace("apply", "remove")
        && e.ability.guid === e2.ability.guid
        && e2.sourceID === e.sourceID
        && e2.targetID === e.targetID
        && e2.timestamp > e.timestamp
      ) === undefined
    ).map(e => ({
      ...e,
      prepull: true,
    }));
  }

  async parse(phasesChanged = true) {
    try {
      const phases = phasesChanged ? this.makePhases() : this.state.phases; //only update phases if they actually changed
      const eventFilter = this.makeEvents(phases);
      this.setState({
        phases: phases,
        events: eventFilter.events,
        fight: {
          start_time: eventFilter.start,
          end_time: eventFilter.end,
          offset_time: eventFilter.start - this.props.fight.start_time, //time between phase start and fight start (for e.g. timeline)
          boss: this.props.fight.boss,
          ...(this.props.phase !== SELECTION_ALL_PHASES && {phase: this.props.phase}), //if phase is selected, add it to the fight object
        },
        isLoading: false,
      });
    } catch (err) {
      captureException(err);
      throw new EventsParseError(err);
    }

  }

  render() {
    return this.props.children(this.state.isLoading, this.state.phases, this.state.events, this.state.fight);
  }

}

export default PhaseParser;
<commit_msg>fix >100% buff uptimes<commit_after>import React from 'react';
import PropTypes from 'prop-types';

import { PHASE_START_EVENT_TYPE, PHASE_END_EVENT_TYPE } from 'common/fabricateBossPhaseEvents';
import { captureException } from 'common/errorLogger';
import { findByBossId } from 'raids';

import { EventsParseError } from './EventParser';

export const SELECTION_ALL_PHASES = "ALL";

class PhaseParser extends React.PureComponent {
  static propTypes = {
    fight: PropTypes.shape({
      start_time: PropTypes.number.isRequired,
      end_time: PropTypes.number.isRequired,
      boss: PropTypes.number.isRequired,
    }).isRequired,
    phase: PropTypes.string.isRequired,
    bossPhaseEvents: PropTypes.array,
    events: PropTypes.array.isRequired,
    children: PropTypes.func.isRequired,
  };

  constructor(props) {
    super(props);
    this.state = {
      isLoading: true,
    };
  }

  componentDidMount() {
    // noinspection JSIgnoredPromiseFromCall
    this.parse();
  }
  componentDidUpdate(prevProps, prevState, prevContext) {
    const phasesChanged = this.props.bossPhaseEvents !== prevProps.bossPhaseEvents || this.props.fight !== prevProps.fight;
    const eventsChanged = this.props.phase !== prevProps.phase || this.props.events !== prevProps.events;
    if (phasesChanged || eventsChanged) {
      this.setState({
        isLoading: true,
      });
      // noinspection JSIgnoredPromiseFromCall
      this.parse(phasesChanged);
    }
  }

  makePhases(){
    const { bossPhaseEvents, fight } = this.props;
    if(!bossPhaseEvents){
      return null;
    }
    const phaseStarts = [...new Set(bossPhaseEvents.filter(e => e.type === PHASE_START_EVENT_TYPE).map(e => e.phase.key))]; //distinct phase starts
    const phaseEnds = [...new Set(bossPhaseEvents.filter(e => e.type === PHASE_END_EVENT_TYPE).map(e => e.phase.key))]; //distinct phase ends
    const phaseKeys = phaseStarts.filter(e => phaseEnds.includes(e)); //only include phases that contain start and end event
    const bossPhases = findByBossId(fight.boss).fight.phases;
    return Object.keys(bossPhases)
    .filter(e => phaseKeys.includes(e)) //only include boss phases that have a valid phase key
    .reduce((obj, key) => {
      return {
        ...obj,
        [key]: bossPhases[key],
      };
    }, {});
  }

  makeEvents(phases) {
    const { bossPhaseEvents, events, phase } = this.props;
    if(!(bossPhaseEvents instanceof Array)
      || !(Object.keys(phases).includes(phase))
      || phase === SELECTION_ALL_PHASES
    ){
      return {start: this.props.fight.start_time, events: bossPhaseEvents ? [...bossPhaseEvents, ...events] : events, end: this.props.fight.end_time};
    }
    const startEvent = bossPhaseEvents.find(e => e.type === PHASE_START_EVENT_TYPE && e.phase.key === phase);
    const endEvent = bossPhaseEvents.find(e => e.type === PHASE_END_EVENT_TYPE && e.phase.key === phase);
    const phaseEvents = events.filter(event =>
        event.timestamp >= startEvent.timestamp
        && event.timestamp <= endEvent.timestamp
      );
    const prePhaseEvents = this.findRelevantPrePhaseEvents(events.filter(event => event.timestamp < startEvent.timestamp))
    .sort((a,b) => a.timestamp - b.timestamp) //sort events by timestamp
    .map(e => ({
      ...e,
      prepull: true, //pretend previous phases were "prepull"
      timestamp: startEvent.timestamp, //override existing timestamps to the start of the phase to avoid >100% uptimes
    }));
    return {start: startEvent.timestamp, events: [...prePhaseEvents, startEvent, ...phaseEvents, endEvent], end: endEvent.timestamp};
  }

  //TODO: find events before the phase that are relevant in this phase (aka cooldowns and buffs) and include them in analysis
  findRelevantPrePhaseEvents(events){
    const applyBuffEvents = this.findRelevantBuffEvents(events);
    const relevantEvents = [...applyBuffEvents];
    return relevantEvents;
  }

  findRelevantBuffEvents(events){
    return events.filter(e => e.type === "applybuff" || e.type === "applydebuff")
    .filter(e =>
      //only keep prior apply(de)buff events if they dont have an associated remove(de)buff event
      events.find(e2 =>
        e2.type === e.type.replace("apply", "remove")
        && e.ability.guid === e2.ability.guid
        && e2.sourceID === e.sourceID
        && e2.targetID === e.targetID
        && e2.timestamp > e.timestamp
      ) === undefined
    ).map(e => ({
      ...e,
      prepull: true,
    }));
  }

  async parse(phasesChanged = true) {
    try {
      const phases = phasesChanged ? this.makePhases() : this.state.phases; //only update phases if they actually changed
      const eventFilter = this.makeEvents(phases);
      this.setState({
        phases: phases,
        events: eventFilter.events,
        fight: {
          start_time: eventFilter.start,
          end_time: eventFilter.end,
          offset_time: eventFilter.start - this.props.fight.start_time, //time between phase start and fight start (for e.g. timeline)
          boss: this.props.fight.boss,
          ...(this.props.phase !== SELECTION_ALL_PHASES && {phase: this.props.phase}), //if phase is selected, add it to the fight object
        },
        isLoading: false,
      });
    } catch (err) {
      captureException(err);
      throw new EventsParseError(err);
    }

  }

  render() {
    return this.props.children(this.state.isLoading, this.state.phases, this.state.events, this.state.fight);
  }

}

export default PhaseParser;
<|endoftext|>


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

var bcoin = require('../bcoin');
var constants = bcoin.protocol.constants;
var preload = bcoin.protocol.preload;
var utils = bcoin.utils;

function Chain(options) {
  if (!(this instanceof Chain))
    return new Chain(options);

  EventEmitter.call(this);

  this.options = options || {};
  this.prefix = 'bt/chain/';
  this.storage = this.options.storage;
  this.strict = this.options.strict || false;
  this.block = {
    list: [],

    // Bloom filter for all known blocks
    bloom: new bcoin.bloom(8 * 1024 * 1024, 16, 0xdeadbeef)
  };
  this.orphan = {
    map: {},
    count: 0
  };
  this.index = {
    bloom: null,
    hashes: [],
    ts: [],
    heights: []
  };
  this.request = new utils.RequestCache();

  this.fromJSON(preload);

  this.loading = false;
  this._init();
}
util.inherits(Chain, EventEmitter);
module.exports = Chain;

function compareTs(a, b) {
  return a -b;
}

Chain.prototype._init = function _init() {
  if (!this.storage)
    return;

  this.loading = true;
  var self = this;
  var s = this.storage.createReadStream({
    start: this.prefix,
    end: this.prefix + 'z'
  });
  s.on('data', function(data) {
    var hash = data.key.slice(self.prefix.length);
    self._addIndex(hash, data.value.ts, data.value.height);
  });
  s.on('error', function(err) {
    self.emit('error', err);
  });
  s.on('end', function() {
    self.loading = false;
    self.emit('load');
  });
};

Chain.prototype._getRange = function _getRange(hash, ts, futureOnly) {
  var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);
  var start = Math.min(Math.max(0, pos), this.index.ts.length - 1);

  while (start > 0 && this.index.ts[start] > ts)
    start--;

  var curr = this.index.ts[start];
  var wnd = 2 * 3600;

  if (!futureOnly)
    while (start > 0 && this.index.ts[start] + wnd > curr)
      start--;

  var end = Math.min(Math.max(0, pos), this.index.ts.length - 1);
  while (end < this.index.ts.length - 1 && this.index.ts[end] - wnd < ts)
    end++;

  return { start: start, end: end };
};

Chain.prototype._probeIndex = function _probeIndex(hash, ts) {
  if (!this.index.bloom.test(hash, 'hex'))
    return false;
  if (!this.strict)
    return true;

  var start = 0;
  var end = this.index.ts.length;
  if (ts) {
    var range = this._getRange(hash, ts);
    start = range.start;
    end = range.end;
  }

  for (var i = start; i <= end; i++)
    if (this.index.hashes[i] === hash)
      return true;

  return false;
};

Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
  if (this._probeIndex(hash, ts))
    return;

  var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);

  // Avoid duplicates
  if (this.index.hashes[pos] === hash ||
      this.index.hashes[pos - 1] === hash ||
      this.index.hashes[pos + 1] === hash) {
    return;
  }

  this.index.ts.splice(pos, 0, ts);
  this.index.hashes.splice(pos, 0, hash);
  this.index.bloom.add(hash, 'hex');
  assert(pos > 0);
  if (!height)
    height = this.index.heights[pos - 1] + 1;
  this.index.heights.splice(pos, 0, height);

  if (!this.storage)
    return;

  var self = this;
  var obj = { ts: ts, height: height };
  this.storage.put(this.prefix + hash, obj, function(err) {
    if (err)
      self.emit('error', err);
  });
};

Chain.prototype.add = function add(block) {
  if (this.loading) {
    this.once('load', function() {
      this.add(block);
    });
    return;
  }

  var res = false;
  var initial = block;
  do {
    // No need to revalidate orphans
    if (!res && !block.verify())
      break;

    var hash = block.hash('hex');
    var prev = block.prevBlock;

    // If the block is already known to be an orphan
    if (this.orphan.map[prev])
      break;

    // If previous block wasn't ever seen - add current to orphans
    if (!this._probeIndex(hash, block.ts) &&
        !this._probeIndex(prev, block.ts)) {
      this.orphan.count++;
      this.orphan.map[prev] = block;

      var range = this._getRange(hash, block.ts, true);
      var hashes = this.index.hashes.slice(range.start, range.end + 1);
      this.emit('missing', prev, hashes, block);
      break;
    }

    // Validated known block at this point - add it to index
    this._addIndex(hash, block.ts);

    // At least one block was added
    res = true;
    this.block.list.push(block);
    this._bloomBlock(block);

    // Fullfill request
    this.request.fullfill(hash, block);

    if (!this.orphan.map[hash])
      break;

    // We have orphan child for this block - add it to chain
    block = this.orphan.map[hash];
    delete this.orphan.map[hash];
    this.orphan.count--;
  } while (true);

  // Compress old blocks
  this._compress();

  return res;
};

Chain.prototype._compress = function compress() {
  // Keep at least 1000 blocks and at most 2000
  if (this.block.list.length < 2000)
    return;

  // Bloom filter rebuilt is needed
  this.block.list = this.block.list.slice(-1000);
  this.block.bloom.reset();

  for (var i = 0; i < this.block.list.length; i++)
    this._bloomBlock(this.block.list[i]);
};

Chain.prototype._bloomBlock = function _bloomBlock(block) {
  this.block.bloom.add(block.hash(), 'hex');
};

Chain.prototype.has = function has(hash, noProbe, cb) {
  if (typeof noProbe === 'function') {
    cb = noProbe;
    noProbe = false;
  }
  if (this.loading) {
    this.once('load', function() {
      this.has(hash, cb);
    });
    return;
  }
  cb = utils.asyncify(cb);

  if (this.block.bloom.test(hash, 'hex')) {
    if (this.strict) {
      for (var i = 0; i < this.block.list.length; i++)
        if (this.block.list[i].hash('hex') === hash)
          return cb(true);
    } else {
      return cb(true);
    }
  }

  if (!noProbe && this.index.bloom.test(hash, 'hex')) {
    // XXX find hash
    return true;
  }

  return cb(!!this.orphan.map[hash]);
};

Chain.prototype.get = function get(hash, cb) {
  // Cached block found
  if (this.block.bloom.test(hash, 'hex')) {
    for (var i = 0; i < this.block.list.length; i++) {
      if (this.block.list[i].hash('hex') === hash) {
        // NOTE: we return right after the statement - so `block` should be
        // valid at the time of nextTick call
        var block = this.block.list[i];
        bcoin.utils.nextTick(function() {
          cb(block);
        });
        return;
      }
    }
    assert(false);
  }

  if (this.request.add(hash, cb))
    this.emit('missing', hash, null, null);
};

Chain.prototype.isFull = function isFull() {
  // < 40m since last block
  return !this.request.count &&
         (+new Date / 1000) - this.index.ts[this.index.ts.length - 1] < 40 * 60;
};

Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) {
  if (this.loading) {
    this.once('load', function() {
      this.hashesInRange(start, end, cb);
    });
    return;
  }
  cb = utils.asyncify(cb);
  var ts = this.index.ts;

  start = utils.binaryInsert(ts, start, compareTs, true);
  if (start > 0 && ts[start - 1] >= start)
    start--;
  end = utils.binaryInsert(ts, end, compareTs, true);

  return cb(this.index.hashes.slice(start, end));
};

Chain.prototype.getLast = function getLast(cb) {
  if (this.loading) {
    this.once('load', function() {
      this.getLast(cb);
    });
    return;
  }
  cb = utils.asyncify(cb);
  return cb(this.index.hashes[this.index.hashes.length - 1]);
};

Chain.prototype.toJSON = function toJSON() {
  var keep = 1000;

  // Keep only last 1000 consequent blocks, dilate others at:
  // 7 day range for blocks before 2013
  // 12 hour for blocks before 2014
  // 6 hour for blocks in 2014 and after it
  // (or at maximum 250 block range)
  var last = {
    hashes: this.index.hashes.slice(-keep),
    ts: this.index.ts.slice(-keep),
    heights: this.index.heights.slice(-keep)
  };

  var first = {
    hashes: [],
    ts: [],
    heights: []
  };

  var delta1 = 7 * 24 * 3600;
  var delta2 = 12 * 3600;
  var delta3 = 6 * 3600;

  var lastTs = 0;
  var lastHeight = -1000;
  for (var i = 0; i < this.index.ts.length - keep; i++) {
    var ts = this.index.ts[i];
    var delta = ts < 1356984000 ? delta1 :
                ts < 1388520000 ? delta2 : delta3;
    var hdelta = this.index.heights[i] - lastHeight;
    if (ts - lastTs < delta && hdelta < 250)
      continue;

    lastTs = ts;
    lastHeight = this.index.heights[i];
    first.hashes.push(this.index.hashes[i]);
    first.ts.push(this.index.ts[i]);
    first.heights.push(this.index.heights[i]);
  }

  return {
    v: 1,
    type: 'chain',
    hashes: first.hashes.concat(last.hashes),
    ts: first.ts.concat(last.ts),
    heights: first.heights.concat(last.heights)
  };
};

Chain.prototype.fromJSON = function fromJSON(json) {
  assert.equal(json.v, 1);
  assert.equal(json.type, 'chain');
  this.index.hashes = json.hashes.slice();
  this.index.ts = json.ts.slice();
  this.index.heights = json.heights.slice();
  if (this.index.bloom)
    this.index.bloom.reset();
  else
    this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbee0);

  if (this.index.hashes.length === 0)
    this.add(new bcoin.block(constants.genesis));

  for (var i = 0; i < this.index.hashes.length; i++) {
    this.index.bloom.add(this.index.hashes[i], 'hex');
  }
};
<commit_msg>chain: set height properly<commit_after>var assert = require('assert');
var util = require('util');
var EventEmitter = require('events').EventEmitter;

var bcoin = require('../bcoin');
var constants = bcoin.protocol.constants;
var preload = bcoin.protocol.preload;
var utils = bcoin.utils;

function Chain(options) {
  if (!(this instanceof Chain))
    return new Chain(options);

  EventEmitter.call(this);

  this.options = options || {};
  this.prefix = 'bt/chain/';
  this.storage = this.options.storage;
  this.strict = this.options.strict || false;
  this.block = {
    list: [],

    // Bloom filter for all known blocks
    bloom: new bcoin.bloom(8 * 1024 * 1024, 16, 0xdeadbeef)
  };
  this.orphan = {
    map: {},
    count: 0
  };
  this.index = {
    bloom: null,
    hashes: [],
    ts: [],
    heights: []
  };
  this.request = new utils.RequestCache();

  this.fromJSON(preload);

  this.loading = false;
  this._init();
}
util.inherits(Chain, EventEmitter);
module.exports = Chain;

function compareTs(a, b) {
  return a -b;
}

Chain.prototype._init = function _init() {
  if (!this.storage)
    return;

  this.loading = true;
  var self = this;
  var s = this.storage.createReadStream({
    start: this.prefix,
    end: this.prefix + 'z'
  });
  s.on('data', function(data) {
    var hash = data.key.slice(self.prefix.length);
    self._addIndex(hash, data.value.ts, data.value.height);
  });
  s.on('error', function(err) {
    self.emit('error', err);
  });
  s.on('end', function() {
    self.loading = false;
    self.emit('load');
  });
};

Chain.prototype._getRange = function _getRange(hash, ts, futureOnly) {
  var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);
  var start = Math.min(Math.max(0, pos), this.index.ts.length - 1);

  while (start > 0 && this.index.ts[start] > ts)
    start--;

  var curr = this.index.ts[start];
  var wnd = 2 * 3600;

  if (!futureOnly)
    while (start > 0 && this.index.ts[start] + wnd > curr)
      start--;

  var end = Math.min(Math.max(0, pos), this.index.ts.length - 1);
  while (end < this.index.ts.length - 1 && this.index.ts[end] - wnd < ts)
    end++;

  return { start: start, end: end };
};

Chain.prototype._probeIndex = function _probeIndex(hash, ts) {
  if (!this.index.bloom.test(hash, 'hex'))
    return false;

  var start = 0;
  var end = this.index.ts.length;
  if (ts) {
    var range = this._getRange(hash, ts);
    start = range.start;
    end = range.end;
  }

  for (var i = start; i <= end; i++)
    if (this.index.hashes[i] === hash)
      return { height: this.index.heights[i], ts: this.index.ts[i] };

  return false;
};

Chain.prototype._addIndex = function _addIndex(hash, ts, height) {
  if (this._probeIndex(hash, ts))
    return;

  var pos = utils.binaryInsert(this.index.ts, ts, compareTs, true);

  // Avoid duplicates
  if (this.index.hashes[pos] === hash ||
      this.index.hashes[pos - 1] === hash ||
      this.index.hashes[pos + 1] === hash) {
    return;
  }

  this.index.ts.splice(pos, 0, ts);
  this.index.hashes.splice(pos, 0, hash);
  this.index.heights.splice(pos, 0, height);
  this.index.bloom.add(hash, 'hex');

  if (!this.storage)
    return;

  var self = this;
  var obj = { ts: ts, height: height };
  this.storage.put(this.prefix + hash, obj, function(err) {
    if (err)
      self.emit('error', err);
  });
};

Chain.prototype.add = function add(block) {
  if (this.loading) {
    this.once('load', function() {
      this.add(block);
    });
    return;
  }

  var res = false;
  var initial = block;
  do {
    // No need to revalidate orphans
    if (!res && !block.verify())
      break;

    var hash = block.hash('hex');
    var prev = block.prevBlock;

    // If the block is already known to be an orphan
    if (this.orphan.map[prev])
      break;

    var prevProbe = this._probeIndex(prev, block.ts);

    // If previous block wasn't ever seen - add current to orphans
    if (!this._probeIndex(hash, block.ts) && !prevProbe) {
      this.orphan.count++;
      this.orphan.map[prev] = block;

      var range = this._getRange(hash, block.ts, true);
      var hashes = this.index.hashes.slice(range.start, range.end + 1);
      this.emit('missing', prev, hashes, block);
      break;
    }

    // Validated known block at this point - add it to index
    if (prevProbe)
      this._addIndex(hash, block.ts, prevProbe.height + 1);

    // At least one block was added
    res = true;
    this.block.list.push(block);
    this._bloomBlock(block);

    // Fullfill request
    this.request.fullfill(hash, block);

    if (!this.orphan.map[hash])
      break;

    // We have orphan child for this block - add it to chain
    block = this.orphan.map[hash];
    delete this.orphan.map[hash];
    this.orphan.count--;
  } while (true);

  // Compress old blocks
  this._compress();

  return res;
};

Chain.prototype._compress = function compress() {
  // Keep at least 1000 blocks and at most 2000
  if (this.block.list.length < 2000)
    return;

  // Bloom filter rebuilt is needed
  this.block.list = this.block.list.slice(-1000);
  this.block.bloom.reset();

  for (var i = 0; i < this.block.list.length; i++)
    this._bloomBlock(this.block.list[i]);
};

Chain.prototype._bloomBlock = function _bloomBlock(block) {
  this.block.bloom.add(block.hash(), 'hex');
};

Chain.prototype.has = function has(hash, noProbe, cb) {
  if (typeof noProbe === 'function') {
    cb = noProbe;
    noProbe = false;
  }
  if (this.loading) {
    this.once('load', function() {
      this.has(hash, cb);
    });
    return;
  }
  cb = utils.asyncify(cb);

  if (this.block.bloom.test(hash, 'hex')) {
    if (this.strict) {
      for (var i = 0; i < this.block.list.length; i++)
        if (this.block.list[i].hash('hex') === hash)
          return cb(true);
    } else {
      return cb(true);
    }
  }

  if (!noProbe && this.index.bloom.test(hash, 'hex')) {
    // XXX find hash
    return true;
  }

  return cb(!!this.orphan.map[hash]);
};

Chain.prototype.get = function get(hash, cb) {
  // Cached block found
  if (this.block.bloom.test(hash, 'hex')) {
    for (var i = 0; i < this.block.list.length; i++) {
      if (this.block.list[i].hash('hex') === hash) {
        // NOTE: we return right after the statement - so `block` should be
        // valid at the time of nextTick call
        var block = this.block.list[i];
        bcoin.utils.nextTick(function() {
          cb(block);
        });
        return;
      }
    }
    assert(false);
  }

  if (this.request.add(hash, cb))
    this.emit('missing', hash, null, null);
};

Chain.prototype.isFull = function isFull() {
  // < 40m since last block
  return !this.request.count &&
         (+new Date / 1000) - this.index.ts[this.index.ts.length - 1] < 40 * 60;
};

Chain.prototype.hashesInRange = function hashesInRange(start, end, cb) {
  if (this.loading) {
    this.once('load', function() {
      this.hashesInRange(start, end, cb);
    });
    return;
  }
  cb = utils.asyncify(cb);
  var ts = this.index.ts;

  start = utils.binaryInsert(ts, start, compareTs, true);
  if (start > 0 && ts[start - 1] >= start)
    start--;
  end = utils.binaryInsert(ts, end, compareTs, true);

  return cb(this.index.hashes.slice(start, end));
};

Chain.prototype.getLast = function getLast(cb) {
  if (this.loading) {
    this.once('load', function() {
      this.getLast(cb);
    });
    return;
  }
  cb = utils.asyncify(cb);
  return cb(this.index.hashes[this.index.hashes.length - 1]);
};

Chain.prototype.toJSON = function toJSON() {
  var keep = 1000;

  // Keep only last 1000 consequent blocks, dilate others at:
  // 7 day range for blocks before 2013
  // 12 hour for blocks before 2014
  // 6 hour for blocks in 2014 and after it
  // (or at maximum 250 block range)
  var last = {
    hashes: this.index.hashes.slice(-keep),
    ts: this.index.ts.slice(-keep),
    heights: this.index.heights.slice(-keep)
  };

  var first = {
    hashes: [],
    ts: [],
    heights: []
  };

  var delta1 = 7 * 24 * 3600;
  var delta2 = 12 * 3600;
  var delta3 = 6 * 3600;

  var lastTs = 0;
  var lastHeight = -1000;
  for (var i = 0; i < this.index.ts.length - keep; i++) {
    var ts = this.index.ts[i];
    var delta = ts < 1356984000 ? delta1 :
                ts < 1388520000 ? delta2 : delta3;
    var hdelta = this.index.heights[i] - lastHeight;
    if (ts - lastTs < delta && hdelta < 250)
      continue;

    lastTs = ts;
    lastHeight = this.index.heights[i];
    first.hashes.push(this.index.hashes[i]);
    first.ts.push(this.index.ts[i]);
    first.heights.push(this.index.heights[i]);
  }

  return {
    v: 1,
    type: 'chain',
    hashes: first.hashes.concat(last.hashes),
    ts: first.ts.concat(last.ts),
    heights: first.heights.concat(last.heights)
  };
};

Chain.prototype.fromJSON = function fromJSON(json) {
  assert.equal(json.v, 1);
  assert.equal(json.type, 'chain');
  this.index.hashes = json.hashes.slice();
  this.index.ts = json.ts.slice();
  this.index.heights = json.heights.slice();
  if (this.index.bloom)
    this.index.bloom.reset();
  else
    this.index.bloom = new bcoin.bloom(28 * 1024 * 1024, 16, 0xdeadbee0);

  if (this.index.hashes.length === 0)
    this.add(new bcoin.block(constants.genesis));

  for (var i = 0; i < this.index.hashes.length; i++) {
    this.index.bloom.add(this.index.hashes[i], 'hex');
  }
};
<|endoftext|>