<commit_before>(function () { "use strict";

var assert = require('assert');
var _ = require('lodash');
var Q = require('q');

function BusComponent () {
	this.bus = null;
	this.componentName = null;
	this.wantsUnplug = false;
	this.callbackFilters = [];
}

BusComponent.objCount = 0;

BusComponent.prototype.setBus = function(bus, componentName) {
	assert.ok(bus);
	assert.ok(!this.bus);

	this.bus = bus;
	this.componentName = componentName;
	this.unansweredBusRequests = 0;
	this.wantsUnplug = false;
	this.initPromise = null;

	this.registerProviders();

	return Q(this.onBusConnect());
};

BusComponent.prototype.setBusFromParent = function(component) {
	assert.ok(component.bus);

	return this.setBus(component.bus, component.componentName + '-' + (BusComponent.objCount++));
};

BusComponent.prototype.unplugBus = function() {
	assert.ok(this.bus);

	this.wantsUnplug = true;

	if (this.unansweredBusRequests == 0) {
		this.unregisterProviders();
		this.bus = null;
		this.componentName = null;
		this.initPromise = null;
	}
};

BusComponent.prototype.imprint = function(obj) {
	obj = _.clone(obj);
	assert.ok(!obj.senderComponentName);

	obj.senderComponentName = this.componentName;

	return obj;
};

for (var requestType_ in {request:0, requestImmediate:0, requestNearest:0, requestLocal:0, requestGlobal:0})
(function() { var requestType = requestType_;

BusComponent.prototype[requestType] = function(req, onReply) {
	assert.ok(!onReply); // temporary
	assert.ok(this.bus);
	assert.ok(req);

	var deferred = Q.defer();

	onReply = function(result) {
		return deferred.resolve(result);
	};

	for (var i = 0; i < this.callbackFilters.length; ++i)
		onReply = this.callbackFilters[i](onReply);

	this.unansweredBusRequests++;
	this.bus[requestType](this.imprint(req), _.bind(function(returnValue) {
		this.unansweredBusRequests--;
		if (this.wantsUnplug)
			this.unplugBus();

		return onReply(returnValue);
	}, this));

	return deferred.promise;
};

})();

BusComponent.prototype.removeListener = function(event, listener) {
	assert.ok(this.bus);
	return this.bus.removeListener(event, listener);
};

BusComponent.prototype.on = function(event, listener) {
	assert.ok(this.bus);
	return this.bus.on(event, listener);
};

BusComponent.prototype.once = function(event, listener) {
	assert.ok(this.bus);
	return this.bus.once(event, listener);
};

BusComponent.prototype.emit = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emit(name, data);
};

BusComponent.prototype.emitImmediate = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emitImmediate(name, data);
};

BusComponent.prototype.emitLocal = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emitLocal(name, data);
};

BusComponent.prototype.emitGlobal = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emitGlobal(name, data);
};

BusComponent.prototype.emitError = function(e) {
	if (!this.bus)
		throw e;
	return this.bus.emitImmediate('error', e);
};

function provide(name, args, fn, prefilter) {
	fn.isProvider = true;
	fn.providedRequest = name;

	fn.requestCB = function(data) {
		if (prefilter && prefilter(data))
			return;

		var passArgs = [];
		for (var i = 0; i < args.length; ++i)
			passArgs.push(data[args[i]]);

		var self = this;

		return Q().then(function() {
			return fn.apply(self, passArgs);
		}).catch(function(e) {
			if (e.busTransmitAsJSON)
				return e.toJSON();

			throw e;
		}).then(function(result) {
			return data.reply(result);
		}).catch(function(e) {
			self.emitError(e);
		}).done();
	};

	return fn;
};

function listener(name, fn) {
	fn.isProvider = true;
	fn.providedRequest = name;
	fn.requestCB = fn;

	return fn;
};

BusComponent.prototype.registerProviders = function() {
	for (var i in this) {
		if (this[i] && this[i].isProvider) {
			// create and store a bound version so it can be removed later
			if (!this[i+'-bound'])
				this[i+'-bound'] = _.bind(this[i].requestCB, this);

			var requests = Array.isArray(this[i].providedRequest) ? this[i].providedRequest : [this[i].providedRequest];

			_.each(requests, _.bind(function(r) {
				this.on(r, this[i+'-bound']);
			}, this));
		}
	}
};

BusComponent.prototype.unregisterProviders = function() {
	for (var i in this) {
		if (this[i] && this[i].isProvider) {
			assert.ok(this[i+'-bound']);

			var requests = Array.isArray(this[i].providedRequest) ? this[i].providedRequest : [this[i].providedRequest];

			_.each(requests, _.bind(function(r) {
				this.removeListener(r, this[i+'-bound']);
			}, this));
		}
	}
};

BusComponent.prototype._init = function() { this.initPromise = null; };
BusComponent.prototype.onBusConnect = function() {};

function needsInit (fn) {
	return function() {
		var self = this;
		var arguments_ = arguments;

		if (this.initPromise === null)
			this.initPromise = Q(this._init());

		return this.initPromise.then(function() {
			return fn.apply(self, arguments_);
		});
	};
};

exports.BusComponent = BusComponent;
exports.listener     = listener;
exports.provide      = provide;
exports.needsInit    = needsInit;

})();
<commit_msg><commit_after>(function () { "use strict";

var assert = require('assert');
var _ = require('lodash');
var Q = require('q');

function BusComponent () {
	this.bus = null;
	this.componentName = null;
	this.wantsUnplug = false;
	this.callbackFilters = [];
}

BusComponent.objCount = 0;

BusComponent.prototype.setBus = function(bus, componentName) {
	assert.ok(bus);
	assert.ok(!this.bus);

	this.bus = bus;
	this.componentName = componentName;
	this.unansweredBusRequests = 0;
	this.wantsUnplug = false;
	this.initPromise = null;

	this.registerProviders();

	return Q(this.onBusConnect());
};

BusComponent.prototype.setBusFromParent = function(component) {
	assert.ok(component.bus);

	return this.setBus(component.bus, component.componentName + '-' + (BusComponent.objCount++));
};

BusComponent.prototype.unplugBus = function() {
	assert.ok(this.bus);

	this.wantsUnplug = true;

	if (this.unansweredBusRequests == 0) {
		this.unregisterProviders();
		this.bus = null;
		this.componentName = null;
		this.initPromise = null;
	}
};

BusComponent.prototype.imprint = function(obj) {
	obj = _.clone(obj);
	assert.ok(!obj.senderComponentName);

	obj.senderComponentName = this.componentName;

	return obj;
};

for (var requestType_ in {request:0, requestImmediate:0, requestNearest:0, requestLocal:0, requestGlobal:0})
(function() { var requestType = requestType_;

BusComponent.prototype[requestType] = function(req, onReply) {
	assert.ok(!onReply); // temporary
	assert.ok(this.bus);
	assert.ok(req);

	var deferred = Q.defer();

	onReply = function(result) {
		return deferred.resolve(result);
	};

	for (var i = 0; i < this.callbackFilters.length; ++i)
		onReply = this.callbackFilters[i](onReply);

	this.unansweredBusRequests++;
	this.bus[requestType](this.imprint(req), _.bind(function(returnValue) {
		this.unansweredBusRequests--;
		if (this.wantsUnplug)
			this.unplugBus();

		return onReply(returnValue);
	}, this));

	return deferred.promise;
};

})();

BusComponent.prototype.removeListener = function(event, listener) {
	assert.ok(this.bus);
	return this.bus.removeListener(event, listener);
};

BusComponent.prototype.on = function(event, listener) {
	assert.ok(this.bus);
	return this.bus.on(event, listener);
};

BusComponent.prototype.once = function(event, listener) {
	assert.ok(this.bus);
	return this.bus.once(event, listener);
};

BusComponent.prototype.emit = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emit(name, data);
};

BusComponent.prototype.emitImmediate = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emitImmediate(name, data);
};

BusComponent.prototype.emitLocal = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emitLocal(name, data);
};

BusComponent.prototype.emitGlobal = function(name, data) {
	if (!this.bus)
		throw new Error('Cannot emit event "' + name + '" without bus connection');
	return this.bus.emitGlobal(name, data);
};

BusComponent.prototype.emitError = function(e) {
	if (!this.bus)
		throw e;
	return this.bus.emitImmediate('error', e);
};

function provide(name, args, fn, prefilter) {
	fn.isProvider = true;
	fn.providedRequest = name;

	fn.requestCB = function(data) {
		if (prefilter && prefilter(data))
			return;

		var passArgs = [];
		for (var i = 0; i < args.length; ++i)
			passArgs.push(data[args[i]]);

		var self = this;

		return Q().then(function() {
			return fn.apply(self, passArgs);
		}).catch(function(e) {
			if (e.busTransmitAsJSON)
				return e.toJSON();

			throw e;
		}).then(function(result) {
			return data.reply(result);
		}).catch(function(e) {
			self.emitError(e);
		}).done();
	};

	for (var i in fn)
		if (fn.hasOwnProperty(i) && i !== 'requestCB' && typeof fn.requestCB[i] === 'undefined')
			fn.requestCB[i] = fn[i];

	return fn;
};

function listener(name, fn) {
	fn.isProvider = true;
	fn.providedRequest = name;
	fn.requestCB = fn;

	return fn;
};

BusComponent.prototype.registerProviders = function() {
	for (var i in this) {
		if (this[i] && this[i].isProvider) {
			// create and store a bound version so it can be removed later
			if (!this[i+'-bound'])
				this[i+'-bound'] = _.bind(this[i].requestCB, this);

			var requests = Array.isArray(this[i].providedRequest) ? this[i].providedRequest : [this[i].providedRequest];

			_.each(requests, _.bind(function(r) {
				this.on(r, this[i+'-bound']);
			}, this));
		}
	}
};

BusComponent.prototype.unregisterProviders = function() {
	for (var i in this) {
		if (this[i] && this[i].isProvider) {
			assert.ok(this[i+'-bound']);

			var requests = Array.isArray(this[i].providedRequest) ? this[i].providedRequest : [this[i].providedRequest];

			_.each(requests, _.bind(function(r) {
				this.removeListener(r, this[i+'-bound']);
			}, this));
		}
	}
};

BusComponent.prototype._init = function() { this.initPromise = null; };
BusComponent.prototype.onBusConnect = function() {};

function needsInit (fn) {
	return function() {
		var self = this;
		var arguments_ = arguments;

		if (this.initPromise === null)
			this.initPromise = Q(this._init());

		return this.initPromise.then(function() {
			return fn.apply(self, arguments_);
		});
	};
};

exports.BusComponent = BusComponent;
exports.listener     = listener;
exports.provide      = provide;
exports.needsInit    = needsInit;

})();
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// 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.

import TransitionManager from './transition-manager';
import log from '../utils/log';
import assert from '../utils/assert';

const NO_TRANSITION_PROPS = {
  transitionDuration: 0
};

// EVENT HANDLING PARAMETERS
const PITCH_MOUSE_THRESHOLD = 5;
const PITCH_ACCEL = 1.2;
const ZOOM_ACCEL = 0.01;

const EVENT_TYPES = {
  WHEEL: ['wheel'],
  PAN: ['panstart', 'panmove', 'panend'],
  PINCH: ['pinchstart', 'pinchmove', 'pinchend'],
  DOUBLE_TAP: ['doubletap'],
  KEYBOARD: ['keydown']
};

export default class Controller {
  constructor(ControllerState, options = {}) {
    assert(ControllerState);
    this.ControllerState = ControllerState;
    this.controllerState = null;
    this.controllerStateProps = null;
    this.eventManager = null;
    this.transitionManager = new TransitionManager(ControllerState, options);
    this._events = null;
    this._state = {
      isDragging: false
    };
    this.events = [];
    this.onViewStateChange = null;
    this.onStateChange = null;
    this.invertPan = false;

    this.handleEvent = this.handleEvent.bind(this);

    this.setProps(options);
  }

  finalize() {
    for (const eventName in this._events) {
      if (this._events[eventName]) {
        this.eventManager.off(eventName, this.handleEvent);
      }
    }
    this.transitionManager.finalize();
  }

  /**
   * Callback for events
   * @param {hammer.Event} event
   */
  handleEvent(event) {
    const {ControllerState} = this;
    this.controllerState = new ControllerState(
      Object.assign({}, this.controllerStateProps, this._state)
    );

    switch (event.type) {
      case 'panstart':
        return this._onPanStart(event);
      case 'panmove':
        return this._onPan(event);
      case 'panend':
        return this._onPanEnd(event);
      case 'pinchstart':
        return this._onPinchStart(event);
      case 'pinchmove':
        return this._onPinch(event);
      case 'pinchend':
        return this._onPinchEnd(event);
      case 'doubletap':
        return this._onDoubleTap(event);
      case 'wheel':
        return this._onWheel(event);
      case 'keydown':
        return this._onKeyDown(event);
      default:
        return false;
    }
  }

  /* Event utils */
  // Event object: http://hammerjs.github.io/api/#event-object
  getCenter(event) {
    const {x, y} = this.controllerStateProps;
    const {offsetCenter} = event;
    return [offsetCenter.x - x, offsetCenter.y - y];
  }

  isPointInBounds(pos) {
    const {width, height} = this.controllerStateProps;

    return pos[0] >= 0 && pos[0] <= width && pos[1] >= 0 && pos[1] <= height;
  }

  isFunctionKeyPressed(event) {
    const {srcEvent} = event;
    return Boolean(srcEvent.metaKey || srcEvent.altKey || srcEvent.ctrlKey || srcEvent.shiftKey);
  }

  isDragging() {
    return this._state.isDragging;
  }

  /**
   * Extract interactivity options
   */
  /* eslint-disable complexity, max-statements */
  setProps(props) {
    if ('onViewportChange' in props) {
      log.removed('onViewportChange')();
    }
    if ('onViewStateChange' in props) {
      this.onViewStateChange = props.onViewStateChange;
    }
    if ('onStateChange' in props) {
      this.onStateChange = props.onStateChange;
    }
    this.controllerStateProps = props;

    if ('eventManager' in props && this.eventManager !== props.eventManager) {
      // EventManager has changed
      this.eventManager = props.eventManager;
      this._events = {};
      this.toggleEvents(this.events, true);
    }

    this.transitionManager.processViewStateChange(this.controllerStateProps);

    // TODO - make sure these are not reset on every setProps
    const {
      scrollZoom = true,
      dragPan = true,
      dragRotate = true,
      doubleClickZoom = true,
      touchZoom = true,
      touchRotate = false,
      keyboard = true
    } = props;

    // Register/unregister events
    const isInteractive = Boolean(this.onViewStateChange);
    this.toggleEvents(EVENT_TYPES.WHEEL, isInteractive && scrollZoom);
    this.toggleEvents(EVENT_TYPES.PAN, isInteractive && (dragPan || dragRotate));
    this.toggleEvents(EVENT_TYPES.PINCH, isInteractive && (touchZoom || touchRotate));
    this.toggleEvents(EVENT_TYPES.DOUBLE_TAP, isInteractive && doubleClickZoom);
    this.toggleEvents(EVENT_TYPES.KEYBOARD, isInteractive && keyboard);

    // Interaction toggles
    this.scrollZoom = scrollZoom;
    this.dragPan = dragPan;
    this.dragRotate = dragRotate;
    this.doubleClickZoom = doubleClickZoom;
    this.touchZoom = touchZoom;
    this.touchRotate = touchRotate;
    this.keyboard = keyboard;
  }
  /* eslint-enable complexity, max-statements */

  toggleEvents(eventNames, enabled) {
    if (this.eventManager) {
      eventNames.forEach(eventName => {
        if (this._events[eventName] !== enabled) {
          this._events[eventName] = enabled;
          if (enabled) {
            this.eventManager.on(eventName, this.handleEvent);
          } else {
            this.eventManager.off(eventName, this.handleEvent);
          }
        }
      });
    }
  }

  // DEPRECATED

  setOptions(props) {
    return this.setProps(props);
  }

  // Private Methods

  /* Callback util */
  // formats map state and invokes callback function
  updateViewport(newControllerState, extraProps = {}, interactionState = {}) {
    const viewState = Object.assign({}, newControllerState.getViewportProps(), extraProps);

    // TODO - to restore diffing, we need to include interactionState
    const changed = this.controllerState !== newControllerState;
    // const oldViewState = this.controllerState.getViewportProps();
    // const changed = Object.keys(viewState).some(key => oldViewState[key] !== viewState[key]);

    if (changed) {
      const oldViewState = this.controllerState ? this.controllerState.getViewportProps() : null;
      if (this.onViewStateChange) {
        this.onViewStateChange({viewState, interactionState, oldViewState});
      }
    }

    Object.assign(
      this._state,
      Object.assign({}, newControllerState.getInteractiveState(), interactionState)
    );
    if (this.onStateChange) {
      this.onStateChange(this._state);
    }
    // this.setState(Object.assign({}, newControllerState.getInteractiveState(), extraState));
  }

  /* Event handlers */
  // Default handler for the `panstart` event.
  _onPanStart(event) {
    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }
    const newControllerState = this.controllerState.panStart({pos}).rotateStart({pos});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `panmove` event.
  _onPan(event) {
    let alternateMode = this.isFunctionKeyPressed(event) || event.rightButton;
    alternateMode = this.invertPan ? !alternateMode : alternateMode;
    return alternateMode ? this._onPanMove(event) : this._onPanRotate(event);
  }

  // Default handler for the `panend` event.
  _onPanEnd(event) {
    const newControllerState = this.controllerState.panEnd().rotateEnd();
    return this.updateViewport(newControllerState, null, {isDragging: false});
  }

  // Default handler for panning to move.
  // Called by `_onPan` when panning without function key pressed.
  _onPanMove(event) {
    if (!this.dragPan) {
      return false;
    }
    const pos = this.getCenter(event);
    const newControllerState = this.controllerState.pan({pos});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for panning to rotate.
  // Called by `_onPan` when panning with function key pressed.
  _onPanRotate(event) {
    if (!this.dragRotate) {
      return false;
    }
    return this._onPanRotateStandard(event);
  }

  // Normal pan to rotate
  _onPanRotateStandard(event) {
    const {deltaX, deltaY} = event;
    const {width, height} = this.controllerState.getViewportProps();

    const deltaScaleX = deltaX / width;
    const deltaScaleY = deltaY / height;

    const newControllerState = this.controllerState.rotate({deltaScaleX, deltaScaleY});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  _onPanRotateMap(event) {
    const {deltaX, deltaY} = event;
    const [, centerY] = this.getCenter(event);
    const startY = centerY - deltaY;
    const {width, height} = this.controllerState.getViewportProps();

    const deltaScaleX = deltaX / width;
    let deltaScaleY = 0;

    if (deltaY > 0) {
      if (Math.abs(height - startY) > PITCH_MOUSE_THRESHOLD) {
        // Move from 0 to -1 as we drag upwards
        deltaScaleY = (deltaY / (startY - height)) * PITCH_ACCEL;
      }
    } else if (deltaY < 0) {
      if (startY > PITCH_MOUSE_THRESHOLD) {
        // Move from 0 to 1 as we drag upwards
        deltaScaleY = 1 - centerY / startY;
      }
    }
    deltaScaleY = Math.min(1, Math.max(-1, deltaScaleY));

    const newControllerState = this.controllerState.rotate({deltaScaleX, deltaScaleY});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `wheel` event.
  _onWheel(event) {
    if (!this.scrollZoom) {
      return false;
    }
    event.preventDefault();

    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }

    const {delta} = event;

    // Map wheel delta to relative scale
    let scale = 2 / (1 + Math.exp(-Math.abs(delta * ZOOM_ACCEL)));
    if (delta < 0 && scale !== 0) {
      scale = 1 / scale;
    }

    const newControllerState = this.controllerState.zoom({pos, scale});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS);
  }

  // Default handler for the `pinchstart` event.
  _onPinchStart(event) {
    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }

    const newControllerState = this.controllerState.zoomStart({pos}).rotateStart({pos});
    // hack - hammer's `rotation` field doesn't seem to produce the correct angle
    this._state.startPinchRotation = event.rotation;
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `pinch` event.
  _onPinch(event) {
    if (!this.touchZoom && !this.touchRotate) {
      return false;
    }

    let newControllerState = this.controllerState;
    if (this.touchZoom) {
      const {scale} = event;
      const pos = this.getCenter(event);
      newControllerState = newControllerState.zoom({pos, scale});
    }
    if (this.touchRotate) {
      const {rotation} = event;
      const {startPinchRotation} = this._state;
      newControllerState = newControllerState.rotate({
        deltaScaleX: -(rotation - startPinchRotation) / 180
      });
    }

    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `pinchend` event.
  _onPinchEnd(event) {
    const newControllerState = this.controllerState.zoomEnd().rotateEnd();
    this._state.startPinchRotation = 0;
    return this.updateViewport(newControllerState, null, {isDragging: false});
  }

  // Default handler for the `doubletap` event.
  _onDoubleTap(event) {
    if (!this.doubleClickZoom) {
      return false;
    }
    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }

    const isZoomOut = this.isFunctionKeyPressed(event);

    const newControllerState = this.controllerState.zoom({pos, scale: isZoomOut ? 0.5 : 2});
    return this.updateViewport(newControllerState, this._getTransitionProps());
  }

  /* eslint-disable complexity */
  // Default handler for the `keydown` event
  _onKeyDown(event) {
    if (!this.keyboard) {
      return false;
    }
    const funcKey = this.isFunctionKeyPressed(event);
    const {controllerState} = this;
    let newControllerState;

    switch (event.srcEvent.keyCode) {
      case 189: // -
        newControllerState = funcKey
          ? controllerState.zoomOut().zoomOut()
          : controllerState.zoomOut();
        break;
      case 187: // +
        newControllerState = funcKey ? controllerState.zoomIn().zoomIn() : controllerState.zoomIn();
        break;
      case 37: // left
        newControllerState = funcKey ? controllerState.rotateLeft() : controllerState.moveLeft();
        break;
      case 39: // right
        newControllerState = funcKey ? controllerState.rotateRight() : controllerState.moveRight();
        break;
      case 38: // up
        newControllerState = funcKey ? controllerState.rotateUp() : controllerState.moveUp();
        break;
      case 40: // down
        newControllerState = funcKey ? controllerState.rotateDown() : controllerState.moveDown();
        break;
      default:
        return false;
    }
    return this.updateViewport(newControllerState, this._getTransitionProps());
  }
  /* eslint-enable complexity */

  _getTransitionProps() {
    // Transitions on double-tap and key-down are only supported by MapController
    return NO_TRANSITION_PROPS;
  }
}
<commit_msg>Don't fire panmove if drag started somewhere else (#2780)<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// 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.

import TransitionManager from './transition-manager';
import log from '../utils/log';
import assert from '../utils/assert';

const NO_TRANSITION_PROPS = {
  transitionDuration: 0
};

// EVENT HANDLING PARAMETERS
const PITCH_MOUSE_THRESHOLD = 5;
const PITCH_ACCEL = 1.2;
const ZOOM_ACCEL = 0.01;

const EVENT_TYPES = {
  WHEEL: ['wheel'],
  PAN: ['panstart', 'panmove', 'panend'],
  PINCH: ['pinchstart', 'pinchmove', 'pinchend'],
  DOUBLE_TAP: ['doubletap'],
  KEYBOARD: ['keydown']
};

export default class Controller {
  constructor(ControllerState, options = {}) {
    assert(ControllerState);
    this.ControllerState = ControllerState;
    this.controllerState = null;
    this.controllerStateProps = null;
    this.eventManager = null;
    this.transitionManager = new TransitionManager(ControllerState, options);
    this._events = null;
    this._state = {
      isDragging: false
    };
    this.events = [];
    this.onViewStateChange = null;
    this.onStateChange = null;
    this.invertPan = false;

    this.handleEvent = this.handleEvent.bind(this);

    this.setProps(options);
  }

  finalize() {
    for (const eventName in this._events) {
      if (this._events[eventName]) {
        this.eventManager.off(eventName, this.handleEvent);
      }
    }
    this.transitionManager.finalize();
  }

  /**
   * Callback for events
   * @param {hammer.Event} event
   */
  handleEvent(event) {
    const {ControllerState} = this;
    this.controllerState = new ControllerState(
      Object.assign({}, this.controllerStateProps, this._state)
    );

    switch (event.type) {
      case 'panstart':
        return this._onPanStart(event);
      case 'panmove':
        return this._onPan(event);
      case 'panend':
        return this._onPanEnd(event);
      case 'pinchstart':
        return this._onPinchStart(event);
      case 'pinchmove':
        return this._onPinch(event);
      case 'pinchend':
        return this._onPinchEnd(event);
      case 'doubletap':
        return this._onDoubleTap(event);
      case 'wheel':
        return this._onWheel(event);
      case 'keydown':
        return this._onKeyDown(event);
      default:
        return false;
    }
  }

  /* Event utils */
  // Event object: http://hammerjs.github.io/api/#event-object
  getCenter(event) {
    const {x, y} = this.controllerStateProps;
    const {offsetCenter} = event;
    return [offsetCenter.x - x, offsetCenter.y - y];
  }

  isPointInBounds(pos) {
    const {width, height} = this.controllerStateProps;

    return pos[0] >= 0 && pos[0] <= width && pos[1] >= 0 && pos[1] <= height;
  }

  isFunctionKeyPressed(event) {
    const {srcEvent} = event;
    return Boolean(srcEvent.metaKey || srcEvent.altKey || srcEvent.ctrlKey || srcEvent.shiftKey);
  }

  isDragging() {
    return this._state.isDragging;
  }

  /**
   * Extract interactivity options
   */
  /* eslint-disable complexity, max-statements */
  setProps(props) {
    if ('onViewportChange' in props) {
      log.removed('onViewportChange')();
    }
    if ('onViewStateChange' in props) {
      this.onViewStateChange = props.onViewStateChange;
    }
    if ('onStateChange' in props) {
      this.onStateChange = props.onStateChange;
    }
    this.controllerStateProps = props;

    if ('eventManager' in props && this.eventManager !== props.eventManager) {
      // EventManager has changed
      this.eventManager = props.eventManager;
      this._events = {};
      this.toggleEvents(this.events, true);
    }

    this.transitionManager.processViewStateChange(this.controllerStateProps);

    // TODO - make sure these are not reset on every setProps
    const {
      scrollZoom = true,
      dragPan = true,
      dragRotate = true,
      doubleClickZoom = true,
      touchZoom = true,
      touchRotate = false,
      keyboard = true
    } = props;

    // Register/unregister events
    const isInteractive = Boolean(this.onViewStateChange);
    this.toggleEvents(EVENT_TYPES.WHEEL, isInteractive && scrollZoom);
    this.toggleEvents(EVENT_TYPES.PAN, isInteractive && (dragPan || dragRotate));
    this.toggleEvents(EVENT_TYPES.PINCH, isInteractive && (touchZoom || touchRotate));
    this.toggleEvents(EVENT_TYPES.DOUBLE_TAP, isInteractive && doubleClickZoom);
    this.toggleEvents(EVENT_TYPES.KEYBOARD, isInteractive && keyboard);

    // Interaction toggles
    this.scrollZoom = scrollZoom;
    this.dragPan = dragPan;
    this.dragRotate = dragRotate;
    this.doubleClickZoom = doubleClickZoom;
    this.touchZoom = touchZoom;
    this.touchRotate = touchRotate;
    this.keyboard = keyboard;
  }
  /* eslint-enable complexity, max-statements */

  toggleEvents(eventNames, enabled) {
    if (this.eventManager) {
      eventNames.forEach(eventName => {
        if (this._events[eventName] !== enabled) {
          this._events[eventName] = enabled;
          if (enabled) {
            this.eventManager.on(eventName, this.handleEvent);
          } else {
            this.eventManager.off(eventName, this.handleEvent);
          }
        }
      });
    }
  }

  // DEPRECATED

  setOptions(props) {
    return this.setProps(props);
  }

  // Private Methods

  /* Callback util */
  // formats map state and invokes callback function
  updateViewport(newControllerState, extraProps = {}, interactionState = {}) {
    const viewState = Object.assign({}, newControllerState.getViewportProps(), extraProps);

    // TODO - to restore diffing, we need to include interactionState
    const changed = this.controllerState !== newControllerState;
    // const oldViewState = this.controllerState.getViewportProps();
    // const changed = Object.keys(viewState).some(key => oldViewState[key] !== viewState[key]);

    if (changed) {
      const oldViewState = this.controllerState ? this.controllerState.getViewportProps() : null;
      if (this.onViewStateChange) {
        this.onViewStateChange({viewState, interactionState, oldViewState});
      }
    }

    Object.assign(
      this._state,
      Object.assign({}, newControllerState.getInteractiveState(), interactionState)
    );
    if (this.onStateChange) {
      this.onStateChange(this._state);
    }
    // this.setState(Object.assign({}, newControllerState.getInteractiveState(), extraState));
  }

  /* Event handlers */
  // Default handler for the `panstart` event.
  _onPanStart(event) {
    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }
    const newControllerState = this.controllerState.panStart({pos}).rotateStart({pos});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `panmove` event.
  _onPan(event) {
    let alternateMode = this.isFunctionKeyPressed(event) || event.rightButton;
    alternateMode = this.invertPan ? !alternateMode : alternateMode;
    return alternateMode ? this._onPanMove(event) : this._onPanRotate(event);
  }

  // Default handler for the `panend` event.
  _onPanEnd(event) {
    const newControllerState = this.controllerState.panEnd().rotateEnd();
    return this.updateViewport(newControllerState, null, {isDragging: false});
  }

  // Default handler for panning to move.
  // Called by `_onPan` when panning without function key pressed.
  _onPanMove(event) {
    if (!this.dragPan) {
      return false;
    }
    const pos = this.getCenter(event);
    if (!this.isDragging()) {
      return false;
    }
    const newControllerState = this.controllerState.pan({pos});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for panning to rotate.
  // Called by `_onPan` when panning with function key pressed.
  _onPanRotate(event) {
    if (!this.dragRotate) {
      return false;
    }
    return this._onPanRotateStandard(event);
  }

  // Normal pan to rotate
  _onPanRotateStandard(event) {
    const {deltaX, deltaY} = event;
    const {width, height} = this.controllerState.getViewportProps();

    const deltaScaleX = deltaX / width;
    const deltaScaleY = deltaY / height;

    const newControllerState = this.controllerState.rotate({deltaScaleX, deltaScaleY});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  _onPanRotateMap(event) {
    const {deltaX, deltaY} = event;
    const [, centerY] = this.getCenter(event);
    const startY = centerY - deltaY;
    const {width, height} = this.controllerState.getViewportProps();

    const deltaScaleX = deltaX / width;
    let deltaScaleY = 0;

    if (deltaY > 0) {
      if (Math.abs(height - startY) > PITCH_MOUSE_THRESHOLD) {
        // Move from 0 to -1 as we drag upwards
        deltaScaleY = (deltaY / (startY - height)) * PITCH_ACCEL;
      }
    } else if (deltaY < 0) {
      if (startY > PITCH_MOUSE_THRESHOLD) {
        // Move from 0 to 1 as we drag upwards
        deltaScaleY = 1 - centerY / startY;
      }
    }
    deltaScaleY = Math.min(1, Math.max(-1, deltaScaleY));

    const newControllerState = this.controllerState.rotate({deltaScaleX, deltaScaleY});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `wheel` event.
  _onWheel(event) {
    if (!this.scrollZoom) {
      return false;
    }
    event.preventDefault();

    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }

    const {delta} = event;

    // Map wheel delta to relative scale
    let scale = 2 / (1 + Math.exp(-Math.abs(delta * ZOOM_ACCEL)));
    if (delta < 0 && scale !== 0) {
      scale = 1 / scale;
    }

    const newControllerState = this.controllerState.zoom({pos, scale});
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS);
  }

  // Default handler for the `pinchstart` event.
  _onPinchStart(event) {
    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }

    const newControllerState = this.controllerState.zoomStart({pos}).rotateStart({pos});
    // hack - hammer's `rotation` field doesn't seem to produce the correct angle
    this._state.startPinchRotation = event.rotation;
    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `pinch` event.
  _onPinch(event) {
    if (!this.touchZoom && !this.touchRotate) {
      return false;
    }

    let newControllerState = this.controllerState;
    if (this.touchZoom) {
      const {scale} = event;
      const pos = this.getCenter(event);
      newControllerState = newControllerState.zoom({pos, scale});
    }
    if (this.touchRotate) {
      const {rotation} = event;
      const {startPinchRotation} = this._state;
      newControllerState = newControllerState.rotate({
        deltaScaleX: -(rotation - startPinchRotation) / 180
      });
    }

    return this.updateViewport(newControllerState, NO_TRANSITION_PROPS, {isDragging: true});
  }

  // Default handler for the `pinchend` event.
  _onPinchEnd(event) {
    const newControllerState = this.controllerState.zoomEnd().rotateEnd();
    this._state.startPinchRotation = 0;
    return this.updateViewport(newControllerState, null, {isDragging: false});
  }

  // Default handler for the `doubletap` event.
  _onDoubleTap(event) {
    if (!this.doubleClickZoom) {
      return false;
    }
    const pos = this.getCenter(event);
    if (!this.isPointInBounds(pos)) {
      return false;
    }

    const isZoomOut = this.isFunctionKeyPressed(event);

    const newControllerState = this.controllerState.zoom({pos, scale: isZoomOut ? 0.5 : 2});
    return this.updateViewport(newControllerState, this._getTransitionProps());
  }

  /* eslint-disable complexity */
  // Default handler for the `keydown` event
  _onKeyDown(event) {
    if (!this.keyboard) {
      return false;
    }
    const funcKey = this.isFunctionKeyPressed(event);
    const {controllerState} = this;
    let newControllerState;

    switch (event.srcEvent.keyCode) {
      case 189: // -
        newControllerState = funcKey
          ? controllerState.zoomOut().zoomOut()
          : controllerState.zoomOut();
        break;
      case 187: // +
        newControllerState = funcKey ? controllerState.zoomIn().zoomIn() : controllerState.zoomIn();
        break;
      case 37: // left
        newControllerState = funcKey ? controllerState.rotateLeft() : controllerState.moveLeft();
        break;
      case 39: // right
        newControllerState = funcKey ? controllerState.rotateRight() : controllerState.moveRight();
        break;
      case 38: // up
        newControllerState = funcKey ? controllerState.rotateUp() : controllerState.moveUp();
        break;
      case 40: // down
        newControllerState = funcKey ? controllerState.rotateDown() : controllerState.moveDown();
        break;
      default:
        return false;
    }
    return this.updateViewport(newControllerState, this._getTransitionProps());
  }
  /* eslint-enable complexity */

  _getTransitionProps() {
    // Transitions on double-tap and key-down are only supported by MapController
    return NO_TRANSITION_PROPS;
  }
}
<|endoftext|>


<commit_before>openerp.base = function(instance) {

    instance.base = {apps:{}};

    instance.base.apps.remote_instance = null;

    instance.base.apps.get_client = function() {
        // return the client via a deferred, resolved or rejected depending if the remote host is available or not.
        var check_client_available = function(client) {
            var d = $.Deferred();
            var i = new Image();
            i.onerror = function() {
                d.reject(client);
            };
            i.onload = function() {
                client.session.session_bind(client.origin).then(function() {
                    // check if client can authenticate
                    client.authenticate().then(
                       function() {     /* done */
                        d.resolve(client);
                    }, function() {     /* fail */
                        if (client.login === 'anonymous') {
                            d.reject(client);
                        } else {
                            sessionStorage.removeItem('apps.login');
                            sessionStorage.removeItem('apps.access_token');
                            client.bind(client.dbname, 'anonymous', 'anonymous');
                            client.authenticate().then(
                               function() {     /* done */
                                d.resolve(client);
                            }, function() {     /* fail */
                                d.reject(client);
                            });
                        }
                    });
                });

            };
            i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif', client.origin);
            return d.promise();
        };
        if (instance.base.apps._client) {
            return check_client_available(instance.base.apps._client);
        } else {
            var ICP = new instance.web.Model('ir.config_parameter');
            return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
                var link = $(_.str.sprintf('', u))[0];
                var host = _.str.sprintf('%s//%s', link.protocol, link.host);
                var dbname = link.pathname;
                if (dbname[0] === '/') {
                    dbname = dbname.substr(1);
                }

                var login = (sessionStorage ? sessionStorage.getItem('apps.login') : null) || 'anonymous';
                var passwd = (sessionStorage ? sessionStorage.getItem('apps.access_token') : null) || 'anonymous';

                if (_.isNull(instance.base.apps.remote_instance)) {
                    instance.base.apps.remote_instance = new openerp.init();
                }
                var client = new instance.base.apps.remote_instance.web.EmbeddedClient(null, host,
                                                                         dbname, login, passwd);
                instance.base.apps._client = client;
                return check_client_available(client);
            });
        }

    };

    instance.base.apps.Apps = instance.web.Widget.extend({
        template: 'EmptyComponent',

        remote_action_id: 'loempia.action_embed',
        failback_action_id: 'base.open_module_tree',

        init: function(parent, action) {
            this._super(parent, action);
            var options = action.params || {};

            if (options.apps_user) {
                sessionStorage.setItem('apps.login', options.apps_user);
            }
            if (options.apps_access_token) {
                sessionStorage.setItem('apps.access_token', options.apps_access_token);
            }

            this.params = options;      // NOTE read by embedded client action
        },

        clean: function() {
            instance.base.apps.get_client().always(function(client) { client.destroy(); });
        },

        destroy: function() {
            this.clean();
            return this._super();
        },

        start: function() {
            var self = this;
            return instance.base.apps.get_client().
                done(function(client) {
                    client.replace(self.$el).
                        done(function() {
                            client.$el.removeClass('openerp');
                            client.do_action(self.remote_action_id);
                        });
                }).
                fail(function(client) {
                    self.do_warn('Apps Server not reachable.', 'Showing local modules.', true);
                    self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
                        self.do_action(action);
                        instance.webclient.menu.open_action(action.id);
                    });
                });
        },

        0:0
    });

    instance.base.apps.UpdatesAvailable = instance.base.apps.Apps.extend({
        remote_action_id: 'loempia.action_embed_updates'
    });

    instance.web.client_actions.add("apps", "instance.base.apps.Apps");
    instance.web.client_actions.add("apps.updates", "instance.base.apps.UpdatesAvailable");

};
<commit_msg>move get_client in Apps<commit_after>openerp.base = function(instance) {

    instance.base.apps_remote = null;
    instance.base.apps_client = null;

    instance.base.Apps = instance.web.Widget.extend({
        template: 'EmptyComponent',
        remote_action_id: 'loempia.action_embed',
        failback_action_id: 'base.open_module_tree',

        init: function(parent, action) {
            this._super(parent, action);
            var options = action.params || {};

            if (options.apps_user) {
                sessionStorage.setItem('apps.login', options.apps_user);
            }
            if (options.apps_access_token) {
                sessionStorage.setItem('apps.access_token', options.apps_access_token);
            }

            this.params = options; // NOTE read by embedded client action
        },

        get_client: function() {
            // return the client via a deferred, resolved or rejected depending if the remote host is available or not.
            var check_client_available = function(client) {
                var d = $.Deferred();
                var i = new Image();
                i.onerror = function() {
                    d.reject(client);
                };
                i.onload = function() {
                    client.session.session_bind(client.origin).then(function() {
                        // check if client can authenticate
                        client.authenticate().then(
                           function() {     /* done */
                            d.resolve(client);
                        }, function() {     /* fail */
                            if (client.login === 'anonymous') {
                                d.reject(client);
                            } else {
                                sessionStorage.removeItem('apps.login');
                                sessionStorage.removeItem('apps.access_token');
                                client.bind_crendentials(client.dbname, 'anonymous', 'anonymous');
                                client.authenticate().then(
                                   function() {     /* done */
                                    d.resolve(client);
                                }, function() {     /* fail */
                                    d.reject(client);
                                });
                            }
                        });
                    });

                };
                i.src = _.str.sprintf('%s/web/static/src/img/sep-a.gif', client.origin);
                return d.promise();
            };
            if (instance.base.apps_client) {
                return check_client_available(instance.base.apps_client);
            } else {
                var ICP = new instance.web.Model('ir.config_parameter');
                return ICP.call('get_param', ['apps.server', 'https://apps.openerp.com/apps']).then(function(u) {
                    var link = $(_.str.sprintf('', u))[0];
                    var host = _.str.sprintf('%s//%s', link.protocol, link.host);
                    var dbname = link.pathname;
                    if (dbname[0] === '/') {
                        dbname = dbname.substr(1);
                    }
                    var login = (sessionStorage ? sessionStorage.getItem('apps.login') : null) || 'anonymous';
                    var passwd = (sessionStorage ? sessionStorage.getItem('apps.access_token') : null) || 'anonymous';
                    if (_.isNull(instance.base.apps_remote)) {
                        instance.base.apps_remote = new openerp.init();
                    }
                    var client = new instance.base.apps_remote.web.EmbeddedClient(null, host, dbname, login, passwd);
                    instance.base.apps_client = client;
                    return check_client_available(client);
                });
            }
        },

        clean: function() {
            self.get_client().always(function(client) { client.destroy(); });
        },

        destroy: function() {
            this.clean();
            return this._super();
        },

        start: function() {
            var self = this;
            return self.get_client().
                done(function(client) {
                    client.replace(self.$el).
                        done(function() {
                            client.$el.removeClass('openerp');
                            client.do_action(self.remote_action_id);
                        });
                }).
                fail(function(client) {
                    self.do_warn('Apps Server not reachable.', 'Showing local modules.', true);
                    self.rpc('/web/action/load', {action_id: self.failback_action_id}).done(function(action) {
                        self.do_action(action);
                        instance.webclient.menu.open_action(action.id);
                    });
                });
        },
    });

    instance.base.AppsUpdates = instance.base.Apps.extend({
        remote_action_id: 'loempia.action_embed_updates'
    });

    instance.web.client_actions.add("apps", "instance.base.Apps");
    instance.web.client_actions.add("apps.updates", "instance.base.AppsUpdates");

};
<|endoftext|>


<commit_before>import {dispatch} from 'store'
import {resolve} from 'stateUtils'
import _ from 'lodash'
import immutable from 'object-path-immutable'

const actionBuilder = (type, props, prefix) => {
    const operations = []
    const sideEffects = []

    const scopedResolve = (state, path) => resolve(state, [prefix, path])

    return {
        forEach(paths, callback) {
            _.forEach(paths, (path, key) => callback(this, path, key))
            return this
        },

        set(path, value) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const prevValue = resolver.value
                if (prevValue !== value)
                    return immutableState.set(resolver.path, value)
            })
            return this
        },

        sort(path, key) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                return immutableState.set(resolver.path, _.orderBy(resolver.value, key))
            })
            return this
        },

        assign(path, value) {
            operations.push((immutableState, state) =>
                immutableState.assign(scopedResolve(state, path).path, value)
            )
            return this
        },

        merge(path, value) {
            operations.push((immutableState, state) =>
                immutableState.merge(scopedResolve(state, path).path, value)
            )
            return this
        },

        setAll(values) {
            Object.keys(values).forEach(path =>
                this.set(path, values[path]))
            return this
        },

        map(path, callback) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const collection = resolver.value
                if (!Array.isArray(collection)) return immutableState
                return collection
                    .map(callback)
                    .map((value, index) => ({index, value}))
                    .filter(({index, value}) => value !== collection[index])
                    .reduce((immutableState, {index, value}) => immutableState.set([resolver.path, index], value), immutableState)
            })
            return this
        },

        push(path, value) {
            operations.push((immutableState, state) => {
                return immutableState.push(scopedResolve(state, path).path, value)
            })
            return this
        },

        unshift(path, value) {
            operations.push((immutableState, state) => {
                return immutableState.insert(scopedResolve(state, path).path, value, 0)
            })
            return this
        },

        sideEffect(callback) {
            sideEffects.push(callback)
            return this
        },

        pushIfMissing(path, value, uniqueKeyProp) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const collection = resolver.value
                const equals = item => uniqueKeyProp
                    ? item[uniqueKeyProp] === value[uniqueKeyProp]
                    : item === value

                if (collection && collection.find(equals))
                    return immutableState
                else
                    return immutableState.push(resolver.path, value)
            })
            return this
        },

        del(path) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                return resolver.value !== undefined
                    ? immutableState.del(resolver.path)
                    : immutableState
            })
            return this
        },

        delValue(path, value) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const index = resolver.value.indexOf(value)
                return (index !== -1)
                    ? immutableState.del([...resolver.path, index])
                    : immutableState
            })
            return this
        },

        build() {
            const performOperation = (immutableState, operation) => {
                const state = immutableState.value()
                return operation(immutable(state), state) || immutableState
            }
            return {
                type,
                ...props,
                reduce(state) {
                    var nextState = operations.reduce(
                        performOperation,
                        immutable(state)
                    ).value()
                    sideEffects.forEach(sideEffect => sideEffect(scopedResolve(nextState).value))
                    return nextState
                },
                dispatch() {
                    dispatch(this)
                }
            }
        },

        dispatch() {
            dispatch(this.build())
        }
    }
}

export default actionBuilder

export const scopedActionBuilder = prefix =>
    (type, props) => actionBuilder(type, props, prefix)
<commit_msg>Droppped unused actionBuilder.map method<commit_after>import {dispatch} from 'store'
import {resolve} from 'stateUtils'
import _ from 'lodash'
import immutable from 'object-path-immutable'

const actionBuilder = (type, props, prefix) => {
    const operations = []
    const sideEffects = []

    const scopedResolve = (state, path) => resolve(state, [prefix, path])

    return {
        forEach(paths, callback) {
            _.forEach(paths, (path, key) => callback(this, path, key))
            return this
        },

        set(path, value) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const prevValue = resolver.value
                if (prevValue !== value)
                    return immutableState.set(resolver.path, value)
            })
            return this
        },

        sort(path, key) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                return immutableState.set(resolver.path, _.orderBy(resolver.value, key))
            })
            return this
        },

        assign(path, value) {
            operations.push((immutableState, state) =>
                immutableState.assign(scopedResolve(state, path).path, value)
            )
            return this
        },

        merge(path, value) {
            operations.push((immutableState, state) =>
                immutableState.merge(scopedResolve(state, path).path, value)
            )
            return this
        },

        setAll(values) {
            Object.keys(values).forEach(path =>
                this.set(path, values[path]))
            return this
        },

        push(path, value) {
            operations.push((immutableState, state) => {
                return immutableState.push(scopedResolve(state, path).path, value)
            })
            return this
        },

        unshift(path, value) {
            operations.push((immutableState, state) => {
                return immutableState.insert(scopedResolve(state, path).path, value, 0)
            })
            return this
        },

        sideEffect(callback) {
            sideEffects.push(callback)
            return this
        },

        pushIfMissing(path, value, uniqueKeyProp) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const collection = resolver.value
                const equals = item => uniqueKeyProp
                    ? item[uniqueKeyProp] === value[uniqueKeyProp]
                    : item === value

                if (collection && collection.find(equals))
                    return immutableState
                else
                    return immutableState.push(resolver.path, value)
            })
            return this
        },

        del(path) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                return resolver.value !== undefined
                    ? immutableState.del(resolver.path)
                    : immutableState
            })
            return this
        },

        delValue(path, value) {
            operations.push((immutableState, state) => {
                const resolver = scopedResolve(state, path)
                const index = resolver.value.indexOf(value)
                return (index !== -1)
                    ? immutableState.del([...resolver.path, index])
                    : immutableState
            })
            return this
        },

        build() {
            const performOperation = (immutableState, operation) => {
                const state = immutableState.value()
                return operation(immutable(state), state) || immutableState
            }
            return {
                type,
                ...props,
                reduce(state) {
                    var nextState = operations.reduce(
                        performOperation,
                        immutable(state)
                    ).value()
                    sideEffects.forEach(sideEffect => sideEffect(scopedResolve(nextState).value))
                    return nextState
                },
                dispatch() {
                    dispatch(this)
                }
            }
        },

        dispatch() {
            dispatch(this.build())
        }
    }
}

export default actionBuilder

export const scopedActionBuilder = prefix =>
    (type, props) => actionBuilder(type, props, prefix)
<|endoftext|>


<commit_before>import './tooltip.css'
import 'rc-tooltip/assets/bootstrap.css'
import {isMobile} from 'widget/userAgent'
import PropTypes from 'prop-types'
import RcTooltip from 'rc-tooltip'
import React from 'react'

export default class Tooltip extends React.Component {
    render() {
        const {msg, placement = 'top', disabled = false, delay = .75, children, ...otherProps} = this.props
        if (isMobile())
            return children
        return (
            msg && !disabled
                ? (
                    <RcTooltip
                        overlay={msg}
                        placement={placement}
                        mouseEnterDelay={delay}
                        trigger={['hover']}
                        {...otherProps}>
                        {children}
                    </RcTooltip>
                )
                : <React.Fragment>{children}</React.Fragment>)
    }
}

Tooltip.propTypes = {
    bottom: PropTypes.bool,
    bottomLeft: PropTypes.bool,
    bottomRight: PropTypes.bool,
    children: PropTypes.object,
    delay: PropTypes.number,
    disabled: PropTypes.bool,
    left: PropTypes.bool,
    msg: PropTypes.any,
    placement: PropTypes.oneOf(['top', 'topRight', 'right', 'bottomRight', 'bottom', 'bottomLeft', 'left', 'topLeft']),
    right: PropTypes.bool,
    top: PropTypes.bool,
    topLeft: PropTypes.bool,
    topRight: PropTypes.bool
}
<commit_msg>Cleanup.<commit_after>import './tooltip.css'
import 'rc-tooltip/assets/bootstrap.css'
import {isMobile} from 'widget/userAgent'
import PropTypes from 'prop-types'
import RcTooltip from 'rc-tooltip'
import React from 'react'

export default class Tooltip extends React.Component {
    render() {
        const {msg, placement = 'top', disabled = false, delay = .75, children, ...otherProps} = this.props
        return isMobile()
            ? children
            : msg && !disabled
                ? (
                    <RcTooltip
                        overlay={msg}
                        placement={placement}
                        mouseEnterDelay={delay}
                        trigger={['hover']}
                        {...otherProps}>
                        {children}
                    </RcTooltip>
                )
                : (
                    <React.Fragment>{children}</React.Fragment>
                )
    }
}

Tooltip.propTypes = {
    bottom: PropTypes.bool,
    bottomLeft: PropTypes.bool,
    bottomRight: PropTypes.bool,
    children: PropTypes.object,
    delay: PropTypes.number,
    disabled: PropTypes.bool,
    left: PropTypes.bool,
    msg: PropTypes.any,
    placement: PropTypes.oneOf(['top', 'topRight', 'right', 'bottomRight', 'bottom', 'bottomLeft', 'left', 'topLeft']),
    right: PropTypes.bool,
    top: PropTypes.bool,
    topLeft: PropTypes.bool,
    topRight: PropTypes.bool
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name        GitHubTopBar
// @namespace   https://github.com/tziporaziegler/GitHubTopBar/
// @description A user script for styling the GitHub top-bar
// @author      Tzipora Ziegler
// @include     https://github.com/*
// @version     1.3
// @run-at document-start
// @require http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
// ==/UserScript==

(function() {
    //Remove Gist and Marketplace tabs
    $('[role=navigation] li:nth-child(n+3)').remove();
})();

function addGlobalStyle(css) {
    var head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

//Change background to lightgrey. Reduce top and bottom padding. Add grey bottom border.
addGlobalStyle (
    '.header{ background-color:#f5f5f5; padding-top:7px; padding-bottom: 7px; border-bottom:1px solid #e5e5e5; }'
);

//Change nav items to black
addGlobalStyle (
    '.header-logo-invertocat .octicon-mark-github, .header-navlink, .header .header-search-input { color:black }'
);

addGlobalStyle (
    '.header-logo-invertocat .octicon-mark-github:hover, .header-navlink:hover, .header-navlink:focus { color:#555555 }'
);

addGlobalStyle (
    '.header-navlink:hover .dropdown-caret, .header-navlink:focus .dropdown-caret { border-top-color: #555555; }'
);

//Fix search bar
addGlobalStyle (
    '.header .header-search-wrapper { background-color:white; border: 1px solid #e5e5e5; }'
);

addGlobalStyle (
    '.header .header-search-wrapper.focus { background-color:#fefefe; border: 1px solid #e5e5e5; }'
);

addGlobalStyle (
    '.header .header-search-scope, .header .header-search-wrapper.focus .header-search-scope { color:#666666; border-right-color:#dddddd; }'
);

//Set the search placeholder color
//Need a separate line for each browser because when a browser doesn’t understand a selector, it invalidates the entire line of selectors
addGlobalStyle (
    /* Chrome/Opera/Safari */
    '::-webkit-input-placeholder { color:#999 !important; }'
);
addGlobalStyle (
    /* Firefox 19+ */
    '::-moz-placeholder { color:#999 !important; }'
);
addGlobalStyle (
    /* IE 10+ */
    ':-ms-input-placeholder { color:#999 !important; }'
);
addGlobalStyle (
    /* Firefox 18- */
    ':-moz-placeholder { color:#999 !important; }'
);
<commit_msg>Update to match GitHub's new classes<commit_after>// ==UserScript==
// @name        GitHubTopBar
// @namespace   https://github.com/tziporaziegler/GitHubTopBar/
// @description A user script for styling the GitHub top-bar
// @author      Tzipora Ziegler
// @include     https://github.com/*
// @version     1.4
// @run-at document-start
// @require http://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js
// ==/UserScript==

(function() {
    //Remove Gist and Marketplace tabs
    $('[role=navigation] li:nth-child(n+3)').remove();
})();

function addGlobalStyle(css) {
    var head = document.getElementsByTagName('head')[0];
    if (!head) { return; }
    var style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = css;
    head.appendChild(style);
}

//Change background to lightgrey. Reduce top and bottom padding. Add grey bottom border.
addGlobalStyle (
    '.Header{ background-color:#f5f5f5; padding-top:7px; padding-bottom: 7px; border-bottom:1px solid #e5e5e5; }'
);

//Change nav items to black
addGlobalStyle (
    '.header-logo-invertocat .octicon-mark-github, .user-nav a, .Header .header-search-input, .HeaderNavlink { color:black }'
);

addGlobalStyle (
    '.header-logo-invertocat .octicon-mark-github:hover, .user-nav a:hover, .user-nav a:focus { color:#555555 }'
);

addGlobalStyle (
    '.user-nav a:hover .dropdown-caret, .user-nav a:focus .dropdown-caret { border-top-color: #555555; }'
);

//Fix search bar
addGlobalStyle (
    '.Header .header-search-wrapper { background-color:white; border: 1px solid #e5e5e5; }'
);

addGlobalStyle (
    '.Header .header-search-wrapper.focus { background-color:#fefefe; border: 1px solid #e5e5e5; }'
);

addGlobalStyle (
    '.header .header-search-scope, .header .header-search-wrapper.focus .header-search-scope { color:#666666; border-right-color:#dddddd; }'
);

//Set the search placeholder color
//Need a separate line for each browser because when a browser doesn’t understand a selector, it invalidates the entire line of selectors
addGlobalStyle (
    /* Chrome/Opera/Safari */
    '::-webkit-input-placeholder { color:#999 !important; }'
);
addGlobalStyle (
    /* Firefox 19+ */
    '::-moz-placeholder { color:#999 !important; }'
);
addGlobalStyle (
    /* IE 10+ */
    ':-ms-input-placeholder { color:#999 !important; }'
);
addGlobalStyle (
    /* Firefox 18- */
    ':-moz-placeholder { color:#999 !important; }'
);
<|endoftext|>


<commit_before>/**
 * Brackets Themse Copyright (c) 2014 Miguel Castillo.
 *
 * Licensed under MIT
 */

define(function() {

    var FileUtils = brackets.getModule("file/FileUtils"),
        cm_path   = FileUtils.getNativeBracketsDirectoryPath() + "/thirdparty/CodeMirror2/addon/";

    function initAddons( ) {
        // Set some default value for codemirror...
        CodeMirror.defaults.highlightSelectionMatches = true;
        CodeMirror.defaults.styleSelectedText = true;
    }

    function init() {
        /**
        *  This is where is all starts to load up...
        */
        var promises = [
            // Load up codemirror addon for active lines
            $.getScript(cm_path + "selection/mark-selection.js").promise(),
            $.getScript(cm_path + "search/match-highlighter.js").promise()
        ];

        return $.when.apply($, promises).done(initAddons).promise();
    }

    return {
        ready: init().done
    };
});

<commit_msg>Disabled code mirror plugins so that sprint 38 doesn't have problems.<commit_after>/**
 * Brackets Themse Copyright (c) 2014 Miguel Castillo.
 *
 * Licensed under MIT
 */

define(function() {

    var FileUtils = brackets.getModule("file/FileUtils"),
        cm_path   = FileUtils.getNativeBracketsDirectoryPath() + "/thirdparty/CodeMirror2/addon/";

    function initAddons( ) {
        // Set some default value for codemirror...
        CodeMirror.defaults.highlightSelectionMatches = true;
        CodeMirror.defaults.styleSelectedText = true;
    }

    function init() {
        /**
        *  This is where is all starts to load up...
        */
        var promises = [
            // Load up codemirror addon for active lines
            //$.getScript(cm_path + "selection/mark-selection.js").promise(),
            //$.getScript(cm_path + "search/match-highlighter.js").promise()
        ];

        return $.when.apply($, promises).done(initAddons).promise();
    }

    return {
        ready: init().done
    };
});

<|endoftext|>


<commit_before>/**
 * Created by fbronzin on 16/03/2017.
 */

/**
 * app.js
 *
 * To start the processing cluster, run: `node app.js`.
 *
 */

var debug = require('debug')('hostview-single-time')
    //, cluster = require('cluster')
    , fs = require('fs-extra')
    , path = require('path')
    , chokidar = require('chokidar')
    , sqldb = require('../app/lib/sqldb')
    , process_sqlite = require('../app/lib/process_sqlite');

// Global configuration
var config = {
    data_dir: '',
    enable_watch: false,     // monitor incoming files ?
    retry: 3,                 // number of retries
    retry_delay: 3600,  // delay between retries (s)
    workers: 1,             // num worker threads
    concurrency: 5,     // num jobs / worker
    db: ''           // postgresql url (or use default)
};

// hostview raw files
var getFileType = function(path) {
    if (path.indexOf("stats.db") > 0) return 'sqlite';
    else if (path.indexOf(".pcap") > 0) return 'pcap';
    else if (path.indexOf(".json") > 0) return 'json';
    else return 'other';
};

var processNewFile = function (job) {
    // in a worker
    var db = new sqldb.DB(config.db);

    debug('Processing file %s', job.data.filename);

    //TODO make sure that this path is correct and is split properly
    // [..., device_id, hostview_ver, year, month]
    var p = path.dirname(job.data.filename).split(path.sep);
    if (p.length < 5) {
        debug('Worker: %d', process.pid ,' Invalid filepath: ' + job.data.filename);
        return done(new Error('Invalid filepath: ' + job.data.filename));
    }

    // extract Hostview id and version from the filepath
    var device_id = p[p.length-2];
    var user_id = p[p.length-1];
    var hv = '0.1';

    //INFO Remove for testing
    /*
     debug('This is the path split ', p);
     debug('device_id ', device_id);
     debug('user_id ', user_id);
     return;
     */

    // make sure the device is recorded in the db and get its id
    //TODO insert into the device also the name of the user as by the path split
    db.getOrInsert('devices', { device_id : device_id, user_id : user_id}, function(err, dev) {
        if (err) {
            debug('Worker: %d', process.pid ,' Error inserting the device ', device_id, ' into the DB: ', err);
            return done(err);
        } else {
            debug('Worker: %d', process.pid ,' Got/Inserted device');
        }

        // add or update files table
        var file = {
            folder: path.dirname(job.data.filename).replace(config.incoming,''),
            basename: path.basename(job.data.filename),
            status: 'processing',
            device_id: dev.id,
            hostview_version: hv
        };
        debug('Worker: %d', process.pid ,' Processing: ', file);

        // this will return error if the file exists already in the database
        // and has been processed (status == 'success')
        db.insertOrUpdateFile(file, function(err, res) {
            if (err) {
                debug('Worker: %d', process.pid ,' Error inserting the device into the DB: ', err);
                return done(err);
            } else {
                debug('Worker: %d', process.pid ,' Inserted/Updated file');
            }

            var processdone = function(err, res) {
                // updates file status to error/success and signals the queue that we're done
                if (err) {
                    debug('Worker: %d', process.pid ,' Process done but with an error: ', err);
                    file.status = 'errored';
                    file.error_info = err+"";
                } else {
                    debug('Worker: %d', process.pid ,' Completed processing: ', res);
                    file.status = 'success';
                }

                db.insertOrUpdateFile(file, function() {
                    // signal done with error (if any)
                    debug('Worker: %d', process.pid ,' Finished with file: ', file);
                });

            }; // processdone

            // for processing
            file.path = job.data.filename;

            try {

                // finally, process the file
                //INFO processing only the sqlite files
                if (job.data.filetype == 'sqlite') {
                    console.log('process_sqlite');
                    process_sqlite.process(file, db, processdone);

                } else {
                    // 'other', just signal success (gets moved to the processed folder)
                    console.log('process_dummy');
                    processdone(null);
                }

            } catch(err) {
                console.error('Unhandled worker error', err);
                processdone(err);
            }

        }); // db.insertOrUpdate
    }); // db.getOrInsertDevice
} // processing

//General environment check
try {
    // Ensure we're in the project directory, so relative paths work as expected
    // no matter where we actually run from.
    process.chdir(__dirname);

} catch (err) {
    console.error(err);
    process.exit(1);
}

// the incoming files monitor
var monitor = undefined;

// master thread
debug('Starting the master process ... stop with Ctrl^C');
debug(JSON.stringify(config,null,2));

process.on('SIGTERM', () => {
    console.log('Received SIGTERM, closing the workers ...');

if (monitor) monitor.close();
monitor = undefined;
});

var enqueue = function(path) {
    var task = { filename : path, filetype : getFileType(path) };
    debug('Master: ', JSON.stringify(task,null,2));

    var prio = 'normal';
    if (task.filetype == 'sqlite') {
        prio = 'critical'; // so that sessions get added asap
    } else if (task.filetype == 'pcap') {
        prio = 'low';
    }
    processNewFile(task);
};

// this will first scan the incoming folder and then start
// watching for new files (or just waits for the processing to finish
// if watcher is disabled)
monitor = chokidar.watch(config.data_dir, {
    persistent: config.enable_watch,
    alwaysStat: true
});

monitor.on('add', function(path, stats) {
    if (stats && stats.isFile()) {
        enqueue(path);
    }
});

//TODO check if it's enough to just launch it with no watch
if (!config.enable_watch) {
    // wait for all the jobs to be processed and exit
    monitor.on('ready', function() {
        monitor.close();
        monitor = undefined;

        console.log('Master waiting for the workers to finish ... ');
        queue.shutdown(function() {
            console.log('Master shutting down!');
            process.exit(0);
        });
    });
} // else keep on watching for new files<commit_msg>Bug fixes app2<commit_after>/**
 * Created by fbronzin on 16/03/2017.
 */

/**
 * app.js
 *
 * To start the processing cluster, run: `node app.js`.
 *
 */

var debug = require('debug')('hostview-single-time')
    //, cluster = require('cluster')
    , fs = require('fs-extra')
    , path = require('path')
    , chokidar = require('chokidar')
    , sqldb = require('../app/lib/sqldb')
    , process_sqlite = require('../app/lib/process_sqlite');

// Global configuration
var config = {
    data_dir: '',
    enable_watch: false,     // monitor incoming files ?
    retry: 3,                 // number of retries
    retry_delay: 3600,  // delay between retries (s)
    workers: 1,             // num worker threads
    concurrency: 5,     // num jobs / worker
    db: ''           // postgresql url (or use default)
};

// hostview raw files
var getFileType = function(path) {
    if (path.indexOf("stats.db") > 0) return 'sqlite';
    else if (path.indexOf(".pcap") > 0) return 'pcap';
    else if (path.indexOf(".json") > 0) return 'json';
    else return 'other';
};

var processNewFile = function (job) {
    // in a worker
    var db = new sqldb.DB(config.db);

    debug('Processing file %s', job.filename);

    //TODO make sure that this path is correct and is split properly
    // [..., device_id, hostview_ver, year, month]
    var p = path.dirname(job.filename).split(path.sep);
    if (p.length < 5) {
        debug('Worker: %d', process.pid ,' Invalid filepath: ' + job.filename);
        return done(new Error('Invalid filepath: ' + job.filename));
    }

    // extract Hostview id and version from the filepath
    var device_id = p[p.length-2];
    var user_id = p[p.length-1];
    var hv = '0.1';

    //INFO Remove for testing
    /*
     debug('This is the path split ', p);
     debug('device_id ', device_id);
     debug('user_id ', user_id);
     return;
     */

    // make sure the device is recorded in the db and get its id
    //TODO insert into the device also the name of the user as by the path split
    db.getOrInsert('devices', { device_id : device_id, user_id : user_id}, function(err, dev) {
        if (err) {
            debug('Worker: %d', process.pid ,' Error inserting the device ', device_id, ' into the DB: ', err);
            return done(err);
        } else {
            debug('Worker: %d', process.pid ,' Got/Inserted device');
        }

        // add or update files table
        var file = {
            folder: path.dirname(job.data.filename).replace(config.incoming,''),
            basename: path.basename(job.data.filename),
            status: 'processing',
            device_id: dev.id,
            hostview_version: hv
        };
        debug('Worker: %d', process.pid ,' Processing: ', file);

        // this will return error if the file exists already in the database
        // and has been processed (status == 'success')
        db.insertOrUpdateFile(file, function(err, res) {
            if (err) {
                debug('Worker: %d', process.pid ,' Error inserting the device into the DB: ', err);
                return done(err);
            } else {
                debug('Worker: %d', process.pid ,' Inserted/Updated file');
            }

            var processdone = function(err, res) {
                // updates file status to error/success and signals the queue that we're done
                if (err) {
                    debug('Worker: %d', process.pid ,' Process done but with an error: ', err);
                    file.status = 'errored';
                    file.error_info = err+"";
                } else {
                    debug('Worker: %d', process.pid ,' Completed processing: ', res);
                    file.status = 'success';
                }

                db.insertOrUpdateFile(file, function() {
                    // signal done with error (if any)
                    debug('Worker: %d', process.pid ,' Finished with file: ', file);
                });

            }; // processdone

            // for processing
            file.path = job.data.filename;

            try {

                // finally, process the file
                //INFO processing only the sqlite files
                if (job.data.filetype == 'sqlite') {
                    console.log('process_sqlite');
                    process_sqlite.process(file, db, processdone);

                } else {
                    // 'other', just signal success (gets moved to the processed folder)
                    console.log('process_dummy');
                    processdone(null);
                }

            } catch(err) {
                console.error('Unhandled worker error', err);
                processdone(err);
            }

        }); // db.insertOrUpdate
    }); // db.getOrInsertDevice
} // processing

//General environment check
try {
    // Ensure we're in the project directory, so relative paths work as expected
    // no matter where we actually run from.
    process.chdir(__dirname);

} catch (err) {
    console.error(err);
    process.exit(1);
}

// the incoming files monitor
var monitor = undefined;

// master thread
debug('Starting the master process ... stop with Ctrl^C');
debug(JSON.stringify(config,null,2));

process.on('SIGTERM', () => {
    console.log('Received SIGTERM, closing the workers ...');

if (monitor) monitor.close();
monitor = undefined;
});

var enqueue = function(path) {
    var task = { filename : path, filetype : getFileType(path) };
    debug('Master: ', JSON.stringify(task,null,2));

    var prio = 'normal';
    if (task.filetype == 'sqlite') {
        prio = 'critical'; // so that sessions get added asap
    } else if (task.filetype == 'pcap') {
        prio = 'low';
    }
    processNewFile(task);
};

// this will first scan the incoming folder and then start
// watching for new files (or just waits for the processing to finish
// if watcher is disabled)
monitor = chokidar.watch(config.data_dir, {
    persistent: config.enable_watch,
    alwaysStat: true
});

monitor.on('add', function(path, stats) {
    if (stats && stats.isFile()) {
        enqueue(path);
    }
});

//TODO check if it's enough to just launch it with no watch
if (!config.enable_watch) {
    // wait for all the jobs to be processed and exit
    monitor.on('ready', function() {
        monitor.close();
        monitor = undefined;

        console.log('Master waiting for the workers to finish ... ');
        queue.shutdown(function() {
            console.log('Master shutting down!');
            process.exit(0);
        });
    });
} // else keep on watching for new files<|endoftext|>