<commit_before>/*
 * Licensed under the Apache License, Version 2.0
 * See accompanying LICENSE file.
 */
'use strict';

angular.module('dashboard.streamingdag', ['dashboard.metrics'])

  .service('StreamingDag', ['Metrics', function (Metrics) {

    /** The constructor */
    function StreamingDag(appId, clock, processors, levels, edges, executors) {
      this.appId = appId;
      this.meter = {};
      this.histogram = {};
      this.setData(clock, processors, levels, edges, executors);
    }

    StreamingDag.prototype = {

      /** Update the current dag data */
      setData: function(clock, processors, levels, edges, executors) {
        this.clock = clock;
        this.processors = _flatMap(processors); // TODO: Try and convert to Scala (#458)
        this.processorHierarchyLevels = _flatMap(levels);
        this.edges = _flatMap(edges, function (item) {
          return [item[0] + '_' + item[2], {source: item[0], target: item[2], type: item[1]}];
        });
        this.executors = _flatMap(executors);
      },

      setStallingProcessors: function(ids) {
        angular.forEach(this.processors, function(processor, key) {
          this.processors[key].stalling = processor.id in ids;
        }, this);
      },

      /** Update (or add) specified metrics in an array */
      updateMetricsArray: function (array) {
        for (var i = 0; i < array.length; i++) {
          var value = array[i].value;
          this.updateMetrics(value.$type, value);
        }
      },

      /** Update (or add) specified metrics */
      updateMetrics: function (name, data) {
        switch (name) {
          case 'org.apache.gearpump.metrics.Metrics.Meter':
            _update(Metrics.decodeMeter, this.meter, this);
            break;
          case 'org.apache.gearpump.metrics.Metrics.Histogram':
            _update(Metrics.decodeHistogram, this.histogram, this);
            break;
        }

        function _update(decodeFn, coll, self) {
          var metric = decodeFn(data);
          if (metric.name.appId === self.appId) {
            if (!(metric.name.processorId in self.processors)) {
              return;
            }
            var item = _getOrCreate(coll, metric.name.metric, {});
            var key = metric.name.processorId + '_' + metric.name.taskId;
            item[key] = metric.values;
            item[key].processorId = metric.name.processorId;
            item[key].taskClass = self.processors[metric.name.processorId].taskClass;
            item[key].taskId = metric.name.taskId;
          }
        }
      },

      hasMetrics: function () {
        return Object.keys(this.meter).length + Object.keys(this.histogram).length > 0;
      },

      getNumOfTasks: function() {
        var count = 0;
        angular.forEach(this.getAliveProcessors(), function(processor) {
          count += processor.parallelism;
        }, this);
        return count;
      },

      getAliveProcessors: function() {
        var result = {};
        angular.forEach(this.processors, function(processor, processorId) {
          if (processor.hasOwnProperty('life')) {
            var life = processor.life;
            if (life.hasOwnProperty('death') && this.clock > life.death) {
              return; // dead processors, drop
            }
            // (life.hasOwnProperty('birth') && this.clock < life.birth)
            // future processors, keep
          }
          result[processorId] = processor;
        }, this);
        return result;
      },

      getAliveEdges: function(aliveProcessors) {
        if (!aliveProcessors) {
          aliveProcessors = this.getAliveProcessors();
        }
        var result = {};
        angular.forEach(this.edges, function(edge, edgeId) {
          if (edge.source in aliveProcessors && edge.target in aliveProcessors) {
            result[edgeId] = edge;
          }
        }, this);
        return result;
      },

      getCurrentDag: function () {
        var weights = {};
        var processors = angular.copy(this.getAliveProcessors());
        angular.forEach(processors, function(_, key) {
          var processorId = parseInt(key);
          weights[processorId] = this._calculateProcessorWeight(processorId);
        }, this);

        var bandwidths = {};
        var edges = this.getAliveEdges(processors);
        angular.forEach(edges, function (_, edgeId) {
          bandwidths[edgeId] = this._calculateEdgeBandwidth(edgeId);
        }, this);

        return {
          processors: processors,
          edges: edges,
          hierarchyLevels: angular.copy(this.processorHierarchyLevels),
          weights: weights,
          bandwidths: bandwidths
        };
      },

      /** Weight of a processor equals the sum of its send throughput and receive throughput. */
      _calculateProcessorWeight: function (processorId) {
        return Math.max(d3.sum(this._getProcessorMetrics(processorId, this.meter.sendThroughput, 'movingAverage1m')),
          d3.sum(this._getProcessorMetrics(processorId, this.meter.receiveThroughput, 'movingAverage1m')));
      },

      /** Bandwidth of an edge equals the minimum of average send throughput and average receive throughput. */
      _calculateEdgeBandwidth: function (edgeId) {
        var digits = edgeId.split('_');
        var sourceId = parseInt(digits[0]);
        var targetId = parseInt(digits[1]);
        var sourceOutputs = this.calculateProcessorConnections(sourceId).outputs;
        var targetInputs = this.calculateProcessorConnections(targetId).inputs;
        var sourceSendThroughput = d3.sum(this._getProcessorMetrics(sourceId, this.meter.sendThroughput, 'movingAverage1m'));
        var targetReceiveThroughput = d3.sum(this._getProcessorMetrics(targetId, this.meter.receiveThroughput, 'movingAverage1m'));
        return Math.min(
          sourceOutputs === 0 ? 0 : (sourceSendThroughput / sourceOutputs),
          targetInputs === 0 ? 0 : (targetReceiveThroughput / targetInputs));
      },

      /** Returns the number of inputs and outputs of a processor */
      calculateProcessorConnections: function (processorId) {
        var result = {inputs: 0, outputs: 0};
        angular.forEach(this.edges, function (edge) {
          if (edge.source == processorId) {
            result.outputs++;
          } else if (edge.target == processorId) {
            result.inputs++;
          }
        }, /* scope */ this);
        return result;
      },

      /** Return particular metrics value of a processor as an array. */
      _getProcessorMetrics: function (processorId, metricsGroup, metricType) {
        var values = [];
        if (metricsGroup) {
          var tasks = this.processors[processorId].parallelism;
          for (var taskId = 0; taskId < tasks; taskId++) {
            var name = processorId + '_' + taskId;
            if (metricsGroup.hasOwnProperty(name)) {
              values.push(metricsGroup[name][metricType]);
            }
          }
        }
        return values;
      },

      /** Return particular metrics value of all processors as an array. */
      _getAggregatedMetrics: function (metricsGroup, metricsType, processorId) {
        var values = [];
        if (metricsGroup) {
          var ids = processorId !== undefined ? [processorId] : d3.keys(this.getAliveProcessors());
          ids.map(function (processorId) {
            var processorValues = this._getProcessorMetrics(processorId, metricsGroup, metricsType);
            values = values.concat(processorValues);
          }, /* scope */ this);
        }
        return values;
      },

      /** Return total received messages from nodes without any outputs. */
      getReceivedMessages: function(processorId) {
        if (processorId !== undefined) {
          return this._getProcessedMessagesByProcessor(
            this.meter.receiveThroughput, processorId, false
          );
        } else {
          return this._getProcessedMessages(
            this.meter.receiveThroughput,
            this._getProcessorIdsByType('sink')
          );
        }
      },

      /** Return total sent messages from nodes without any inputs. */
      getSentMessages: function(processorId) {
        if (processorId !== undefined) {
          return this._getProcessedMessagesByProcessor(
            this.meter.sendThroughput, processorId, false
          );
        } else {
          return this._getProcessedMessages(
            this.meter.sendThroughput,
            this._getProcessorIdsByType('source')
          );
        }
      },

      _getProcessedMessages: function(metricsGroup, processorIds) {
        var result = {total: [], rate: []};
        processorIds.map(function(processorId) {
          var processorResult = this._getProcessedMessagesByProcessor(
            metricsGroup, processorId, true);
          result.total.push(processorResult.total);
          result.rate.push(processorResult.rate);
        }, /* scope */ this);
        return {total: d3.sum(result.total), rate: d3.sum(result.rate)};
      },

      _getProcessedMessagesByProcessor: function(metricsGroup, processorId, aggregated) {
        var taskCountArray = this._getAggregatedMetrics(metricsGroup, 'count', processorId);
        var taskRateArray = this._getAggregatedMetrics(metricsGroup, 'movingAverage1m', processorId);
        return aggregated ?
          {total: d3.sum(taskCountArray), rate: d3.sum(taskRateArray)} :
          {total: taskCountArray, rate: taskRateArray};
      },

      _getProcessorIdsByType: function(type) {
        var ids = [];
        d3.keys(this.getAliveProcessors()).map(function(processorId) {
          var conn = this.calculateProcessorConnections(processorId);
          if ((type === 'source' && conn.inputs === 0 && conn.outputs > 0) ||
            (type === 'sink' && conn.inputs > 0 && conn.outputs === 0)) {
            ids.push(processorId);
          }
        }, /* scope */ this);
        return ids;
      },

      getProcessingTime: function (processorId) {
        var array = this._getAggregatedMetrics(this.histogram.processTime, 'mean', processorId);
        return processorId !== undefined ? array : d3.mean(array);
      },

      getReceiveLatency: function (processorId) {
        var array = this._getAggregatedMetrics(this.histogram.receiveLatency, 'mean', processorId);
        return processorId !== undefined ? array : d3.mean(array);
      },

      /** Return the depth of the hierarchy layout */
      hierarchyDepth: function () {
        return d3.max(d3.values(this.processorHierarchyLevels));
      }
    };

    function _getOrCreate(obj, prop, init) {
      if (!obj.hasOwnProperty(prop)) {
        obj[prop] = init;
      }
      return obj[prop];
    }

    function _flatMap(array, fn) {
      var result = {};
      array.map(function (item) {
        if (fn) {
          item = fn(item);
        }
        result[item[0]] = item[1];
      });
      return result;
    }

    return StreamingDag;
  }])
;<commit_msg>fix #1223: stalling status might not be updated expectedly<commit_after>/*
 * Licensed under the Apache License, Version 2.0
 * See accompanying LICENSE file.
 */
'use strict';

angular.module('dashboard.streamingdag', ['dashboard.metrics'])

  .service('StreamingDag', ['Metrics', function (Metrics) {

    /** The constructor */
    function StreamingDag(appId, clock, processors, levels, edges, executors) {
      this.appId = appId;
      this.meter = {};
      this.histogram = {};
      this.stalling = {};
      this.setData(clock, processors, levels, edges, executors);
    }

    StreamingDag.prototype = {

      /** Update the current dag data */
      setData: function(clock, processors, levels, edges, executors) {
        this.clock = clock;
        this.processors = _flatMap(processors); // TODO: Try and convert to Scala (#458)
        this.processorHierarchyLevels = _flatMap(levels);
        this.edges = _flatMap(edges, function (item) {
          return [item[0] + '_' + item[2], {source: item[0], target: item[2], type: item[1]}];
        });
        this.executors = _flatMap(executors);
      },

      setStallingProcessors: function(ids) {
        this.stalling = ids;
      },

      /** Update (or add) specified metrics in an array */
      updateMetricsArray: function (array) {
        for (var i = 0; i < array.length; i++) {
          var value = array[i].value;
          this.updateMetrics(value.$type, value);
        }
      },

      /** Update (or add) specified metrics */
      updateMetrics: function (name, data) {
        switch (name) {
          case 'org.apache.gearpump.metrics.Metrics.Meter':
            _update(Metrics.decodeMeter, this.meter, this);
            break;
          case 'org.apache.gearpump.metrics.Metrics.Histogram':
            _update(Metrics.decodeHistogram, this.histogram, this);
            break;
        }

        function _update(decodeFn, coll, self) {
          var metric = decodeFn(data);
          if (metric.name.appId === self.appId) {
            if (!(metric.name.processorId in self.processors)) {
              return;
            }
            var item = _getOrCreate(coll, metric.name.metric, {});
            var key = metric.name.processorId + '_' + metric.name.taskId;
            item[key] = metric.values;
            item[key].processorId = metric.name.processorId;
            item[key].taskClass = self.processors[metric.name.processorId].taskClass;
            item[key].taskId = metric.name.taskId;
          }
        }
      },

      hasMetrics: function () {
        return Object.keys(this.meter).length + Object.keys(this.histogram).length > 0;
      },

      getNumOfTasks: function() {
        var count = 0;
        angular.forEach(this.getAliveProcessors(), function(processor) {
          count += processor.parallelism;
        }, this);
        return count;
      },

      getAliveProcessors: function() {
        var result = {};
        angular.forEach(this.processors, function(processor, processorId) {
          if (processor.hasOwnProperty('life')) {
            var life = processor.life;
            if (life.hasOwnProperty('death') && this.clock > life.death) {
              return; // dead processors, drop
            }
            // (life.hasOwnProperty('birth') && this.clock < life.birth)
            // future processors, keep
          }
          result[processorId] = processor;
        }, this);
        return result;
      },

      getAliveEdges: function(aliveProcessors) {
        if (!aliveProcessors) {
          aliveProcessors = this.getAliveProcessors();
        }
        var result = {};
        angular.forEach(this.edges, function(edge, edgeId) {
          if (edge.source in aliveProcessors && edge.target in aliveProcessors) {
            result[edgeId] = edge;
          }
        }, this);
        return result;
      },

      getCurrentDag: function () {
        var weights = {};
        var processors = angular.copy(this.getAliveProcessors());
        angular.forEach(processors, function(_, key) {
          var processorId = parseInt(key);
          weights[processorId] = this._calculateProcessorWeight(processorId);
          processors[key].stalling = processorId in this.stalling;
        }, this);

        var bandwidths = {};
        var edges = this.getAliveEdges(processors);
        angular.forEach(edges, function (_, edgeId) {
          bandwidths[edgeId] = this._calculateEdgeBandwidth(edgeId);
        }, this);

        return {
          processors: processors,
          edges: edges,
          hierarchyLevels: angular.copy(this.processorHierarchyLevels),
          weights: weights,
          bandwidths: bandwidths
        };
      },

      /** Weight of a processor equals the sum of its send throughput and receive throughput. */
      _calculateProcessorWeight: function (processorId) {
        return Math.max(d3.sum(this._getProcessorMetrics(processorId, this.meter.sendThroughput, 'movingAverage1m')),
          d3.sum(this._getProcessorMetrics(processorId, this.meter.receiveThroughput, 'movingAverage1m')));
      },

      /** Bandwidth of an edge equals the minimum of average send throughput and average receive throughput. */
      _calculateEdgeBandwidth: function (edgeId) {
        var digits = edgeId.split('_');
        var sourceId = parseInt(digits[0]);
        var targetId = parseInt(digits[1]);
        var sourceOutputs = this.calculateProcessorConnections(sourceId).outputs;
        var targetInputs = this.calculateProcessorConnections(targetId).inputs;
        var sourceSendThroughput = d3.sum(this._getProcessorMetrics(sourceId, this.meter.sendThroughput, 'movingAverage1m'));
        var targetReceiveThroughput = d3.sum(this._getProcessorMetrics(targetId, this.meter.receiveThroughput, 'movingAverage1m'));
        return Math.min(
          sourceOutputs === 0 ? 0 : (sourceSendThroughput / sourceOutputs),
          targetInputs === 0 ? 0 : (targetReceiveThroughput / targetInputs));
      },

      /** Returns the number of inputs and outputs of a processor */
      calculateProcessorConnections: function (processorId) {
        var result = {inputs: 0, outputs: 0};
        angular.forEach(this.edges, function (edge) {
          if (edge.source == processorId) {
            result.outputs++;
          } else if (edge.target == processorId) {
            result.inputs++;
          }
        }, /* scope */ this);
        return result;
      },

      /** Return particular metrics value of a processor as an array. */
      _getProcessorMetrics: function (processorId, metricsGroup, metricType) {
        var values = [];
        if (metricsGroup) {
          var tasks = this.processors[processorId].parallelism;
          for (var taskId = 0; taskId < tasks; taskId++) {
            var name = processorId + '_' + taskId;
            if (metricsGroup.hasOwnProperty(name)) {
              values.push(metricsGroup[name][metricType]);
            }
          }
        }
        return values;
      },

      /** Return particular metrics value of all processors as an array. */
      _getAggregatedMetrics: function (metricsGroup, metricsType, processorId) {
        var values = [];
        if (metricsGroup) {
          var ids = processorId !== undefined ? [processorId] : d3.keys(this.getAliveProcessors());
          ids.map(function (processorId) {
            var processorValues = this._getProcessorMetrics(processorId, metricsGroup, metricsType);
            values = values.concat(processorValues);
          }, /* scope */ this);
        }
        return values;
      },

      /** Return total received messages from nodes without any outputs. */
      getReceivedMessages: function(processorId) {
        if (processorId !== undefined) {
          return this._getProcessedMessagesByProcessor(
            this.meter.receiveThroughput, processorId, false
          );
        } else {
          return this._getProcessedMessages(
            this.meter.receiveThroughput,
            this._getProcessorIdsByType('sink')
          );
        }
      },

      /** Return total sent messages from nodes without any inputs. */
      getSentMessages: function(processorId) {
        if (processorId !== undefined) {
          return this._getProcessedMessagesByProcessor(
            this.meter.sendThroughput, processorId, false
          );
        } else {
          return this._getProcessedMessages(
            this.meter.sendThroughput,
            this._getProcessorIdsByType('source')
          );
        }
      },

      _getProcessedMessages: function(metricsGroup, processorIds) {
        var result = {total: [], rate: []};
        processorIds.map(function(processorId) {
          var processorResult = this._getProcessedMessagesByProcessor(
            metricsGroup, processorId, true);
          result.total.push(processorResult.total);
          result.rate.push(processorResult.rate);
        }, /* scope */ this);
        return {total: d3.sum(result.total), rate: d3.sum(result.rate)};
      },

      _getProcessedMessagesByProcessor: function(metricsGroup, processorId, aggregated) {
        var taskCountArray = this._getAggregatedMetrics(metricsGroup, 'count', processorId);
        var taskRateArray = this._getAggregatedMetrics(metricsGroup, 'movingAverage1m', processorId);
        return aggregated ?
          {total: d3.sum(taskCountArray), rate: d3.sum(taskRateArray)} :
          {total: taskCountArray, rate: taskRateArray};
      },

      _getProcessorIdsByType: function(type) {
        var ids = [];
        d3.keys(this.getAliveProcessors()).map(function(processorId) {
          var conn = this.calculateProcessorConnections(processorId);
          if ((type === 'source' && conn.inputs === 0 && conn.outputs > 0) ||
            (type === 'sink' && conn.inputs > 0 && conn.outputs === 0)) {
            ids.push(processorId);
          }
        }, /* scope */ this);
        return ids;
      },

      getProcessingTime: function (processorId) {
        var array = this._getAggregatedMetrics(this.histogram.processTime, 'mean', processorId);
        return processorId !== undefined ? array : d3.mean(array);
      },

      getReceiveLatency: function (processorId) {
        var array = this._getAggregatedMetrics(this.histogram.receiveLatency, 'mean', processorId);
        return processorId !== undefined ? array : d3.mean(array);
      },

      /** Return the depth of the hierarchy layout */
      hierarchyDepth: function () {
        return d3.max(d3.values(this.processorHierarchyLevels));
      }
    };

    function _getOrCreate(obj, prop, init) {
      if (!obj.hasOwnProperty(prop)) {
        obj[prop] = init;
      }
      return obj[prop];
    }

    function _flatMap(array, fn) {
      var result = {};
      array.map(function (item) {
        if (fn) {
          item = fn(item);
        }
        result[item[0]] = item[1];
      });
      return result;
    }

    return StreamingDag;
  }])
;<|endoftext|>


<commit_before>/*
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import Xen from '../../components/xen/xen.js';
import Arcs from '../lib/arcs.js';
import ArcsUtils from '../lib/arcs-utils.js';

const log = Xen.logFactory('ArcHost', '#007ac1');
const warn = Xen.logFactory('ArcHost', '#007ac1', 'warn');
const error = Xen.logFactory('ArcHost', '#007ac1', 'error');

class ArcHost extends Xen.Debug(Xen.Base, log) {
  static get observedAttributes() {
    return ['config', 'manifest', 'plans', 'suggestions', 'plan'];
  }
  _getInitialState() {
    return {
      pendingPlans: [],
      invalid: 0
    };
  }
  _willReceiveProps(props, state, lastProps) {
    const changed = name => props[name] !== lastProps[name];
    const {manifest, config, plan, suggestions} = props;
    if (config && (config !== state.appliedConfig) && manifest) {
      state.appliedConfig = config;
      this._initArc(config, manifest);
    }
    else if (state.arc && changed('manifest')) {
      log('reloading');
      this._reloadManifest(state.arc, config, manifest);
    }
    if (plan && changed('plan')) {
      state.pendingPlans.push(plan);
    }
    if (suggestions && changed('suggestions')) {
      state.slotComposer.setSuggestions(suggestions);
    }
  }
  _update({plans}, {arc, pendingPlans}) {
    if (arc && pendingPlans.length) {
      this._instantiatePlan(arc, pendingPlans.shift());
    }
    if (arc && !plans) {
      this._schedulePlanning();
    }
  }
  async _initArc(config, manifest) {
    const arc = await this._createArc(config, manifest);
    // TODO(sjmiles): IIUC callback that is invoked by runtime onIdle event
    arc.makeSuggestions = () => this._runtimeHandlesUpdated();
    log('created arc', arc);
    this._setState({arc});
    this._fire('arc', arc);
  }
  async _createArc(config, manifest) {
    // make an id
    const id = 'demo-' + ArcsUtils.randomId();
    // create a system loader
    const loader = this._createLoader(config);
    // load manifest
    const context = await this._createContext(loader, manifest);
    // composer
    const slotComposer = new Arcs.SlotComposer({
      rootContext: document.body, //this.parentElement,
      affordance: config.affordance,
      containerKind: config.containerKind,
      // TODO(sjmiles): typically resolved via `slotid="suggestions"`, but override is allowed here via config
      suggestionsContext: config.suggestionsNode
    });
    // capture composer so we can push suggestions there
    this._setState({slotComposer});
    // send urlMap to the Arc so worker-entry*.js can create mapping loaders
    const urlMap = loader._urlMap;
    // Arc!
    return ArcsUtils.createArc({id, urlMap, slotComposer, context, loader});
  }
  _createLoader(config) {
    // create default URL map
    const urlMap = ArcsUtils.createUrlMap(config.root);
    // create a system loader
    // TODO(sjmiles): `pecFactory` creates loader objects (via worker-entry*.js) for the innerPEC,
    // but we have to create one by hand for manifest loading
    const loader = new Arcs.BrowserLoader(urlMap);
    // add `urls` to `urlMap` after a resolve pass
    if (config.urls) {
      Object.keys(config.urls).forEach(k => urlMap[k] = loader._resolve(config.urls[k]));
    }
    return loader;
  }
  async _createContext(loader, content) {
    // TODO(sjmiles): do we need to be able to `config` this value?
    const manifestFileName = './shell.manifest';
    let context;
    try {
      context = await ArcsUtils.parseManifest(manifestFileName, content, loader);
    } catch (x) {
      warn(x);
      context = ArcsUtils.parseManifest(manifestFileName, '', loader);
    }
    return context;
  }
  async _reloadManifest(arc, config, manifest) {
    arc._context = await this._createContext(loader, manifest);
    this._fire('plans', null);
  }
  _runtimeHandlesUpdated() {
    !this._state.planning && log('runtimeHandlesUpdated');
    this._schedulePlanning();
  }
  async _schedulePlanning() {
    const state = this._state;
    // results obtained before now are invalid
    state.invalid = true;
    // only wait for one _beginPlanning at a time
    if (!state.planning) {
      state.planning = true;
      try {
        await this.__beginPlanning(state, this._props);
      } catch (x) {
        error(x);
      }
      state.planning = false;
    }
  }
  // TODO(sjmiles): only to be called from _schedulePlanning which protects re-entrancy
  async __beginPlanning(state, props) {
    log(`planning...`);
    let time = Date.now();
    let plans;
    while (state.invalid) {
      state.invalid = false;
      plans = await ArcsUtils.makePlans(state.arc, props.config.plannerTimeout) || [];
    }
    time = ((Date.now() - time) / 1000).toFixed(2);
    log(`plans`, plans, `${time}s`);
    this._fire('plans', plans);
    //
    this.suggestions = plans;
  }
  async _instantiatePlan(arc, plan) {
    // aggressively remove old suggestions when a suggestion is applied
    this._setState({suggestions: []});
    log('instantiated plan', plan);
    await arc.instantiate(plan);
    this._fire('plan', plan);
  }
}
ArcHost.groupCollapsed = Xen.Base.logFactory('ArcHost', '#007ac1', 'groupCollapsed');
customElements.define('arc-host', ArcHost);
<commit_msg>remove suggestions short-circuit<commit_after>/*
@license
Copyright (c) 2016 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
*/

import Xen from '../../components/xen/xen.js';
import Arcs from '../lib/arcs.js';
import ArcsUtils from '../lib/arcs-utils.js';

const log = Xen.logFactory('ArcHost', '#007ac1');
const warn = Xen.logFactory('ArcHost', '#007ac1', 'warn');
const error = Xen.logFactory('ArcHost', '#007ac1', 'error');

class ArcHost extends Xen.Debug(Xen.Base, log) {
  static get observedAttributes() {
    return ['config', 'manifest', 'plans', 'suggestions', 'plan'];
  }
  _getInitialState() {
    return {
      pendingPlans: [],
      invalid: 0
    };
  }
  _willReceiveProps(props, state, lastProps) {
    const changed = name => props[name] !== lastProps[name];
    const {manifest, config, plan, suggestions} = props;
    if (config && (config !== state.appliedConfig) && manifest) {
      state.appliedConfig = config;
      this._initArc(config, manifest);
    }
    else if (state.arc && changed('manifest')) {
      log('reloading');
      this._reloadManifest(state.arc, config, manifest);
    }
    if (plan && changed('plan')) {
      state.pendingPlans.push(plan);
    }
    if (suggestions && changed('suggestions')) {
      state.slotComposer.setSuggestions(suggestions);
    }
  }
  _update({plans}, {arc, pendingPlans}) {
    if (arc && pendingPlans.length) {
      this._instantiatePlan(arc, pendingPlans.shift());
    }
    if (arc && !plans) {
      this._schedulePlanning();
    }
  }
  async _initArc(config, manifest) {
    const arc = await this._createArc(config, manifest);
    // TODO(sjmiles): IIUC callback that is invoked by runtime onIdle event
    arc.makeSuggestions = () => this._runtimeHandlesUpdated();
    log('created arc', arc);
    this._setState({arc});
    this._fire('arc', arc);
  }
  async _createArc(config, manifest) {
    // make an id
    const id = 'demo-' + ArcsUtils.randomId();
    // create a system loader
    const loader = this._createLoader(config);
    // load manifest
    const context = await this._createContext(loader, manifest);
    // composer
    const slotComposer = new Arcs.SlotComposer({
      rootContext: document.body, //this.parentElement,
      affordance: config.affordance,
      containerKind: config.containerKind,
      // TODO(sjmiles): typically resolved via `slotid="suggestions"`, but override is allowed here via config
      suggestionsContext: config.suggestionsNode
    });
    // capture composer so we can push suggestions there
    this._setState({slotComposer});
    // send urlMap to the Arc so worker-entry*.js can create mapping loaders
    const urlMap = loader._urlMap;
    // Arc!
    return ArcsUtils.createArc({id, urlMap, slotComposer, context, loader});
  }
  _createLoader(config) {
    // create default URL map
    const urlMap = ArcsUtils.createUrlMap(config.root);
    // create a system loader
    // TODO(sjmiles): `pecFactory` creates loader objects (via worker-entry*.js) for the innerPEC,
    // but we have to create one by hand for manifest loading
    const loader = new Arcs.BrowserLoader(urlMap);
    // add `urls` to `urlMap` after a resolve pass
    if (config.urls) {
      Object.keys(config.urls).forEach(k => urlMap[k] = loader._resolve(config.urls[k]));
    }
    return loader;
  }
  async _createContext(loader, content) {
    // TODO(sjmiles): do we need to be able to `config` this value?
    const manifestFileName = './shell.manifest';
    let context;
    try {
      context = await ArcsUtils.parseManifest(manifestFileName, content, loader);
    } catch (x) {
      warn(x);
      context = ArcsUtils.parseManifest(manifestFileName, '', loader);
    }
    return context;
  }
  async _reloadManifest(arc, config, manifest) {
    arc._context = await this._createContext(loader, manifest);
    this._fire('plans', null);
  }
  _runtimeHandlesUpdated() {
    !this._state.planning && log('runtimeHandlesUpdated');
    this._schedulePlanning();
  }
  async _schedulePlanning() {
    const state = this._state;
    // results obtained before now are invalid
    state.invalid = true;
    // only wait for one _beginPlanning at a time
    if (!state.planning) {
      state.planning = true;
      try {
        await this.__beginPlanning(state, this._props);
      } catch (x) {
        error(x);
      }
      state.planning = false;
    }
  }
  // TODO(sjmiles): only to be called from _schedulePlanning which protects re-entrancy
  async __beginPlanning(state, props) {
    log(`planning...`);
    let time = Date.now();
    let plans;
    while (state.invalid) {
      state.invalid = false;
      plans = await ArcsUtils.makePlans(state.arc, props.config.plannerTimeout) || [];
    }
    time = ((Date.now() - time) / 1000).toFixed(2);
    log(`plans`, plans, `${time}s`);
    this._fire('plans', plans);
  }
  async _instantiatePlan(arc, plan) {
    // aggressively remove old suggestions when a suggestion is applied
    this._setState({suggestions: []});
    log('instantiated plan', plan);
    await arc.instantiate(plan);
    this._fire('plan', plan);
  }
}
ArcHost.groupCollapsed = Xen.Base.logFactory('ArcHost', '#007ac1', 'groupCollapsed');
customElements.define('arc-host', ArcHost);
<|endoftext|>


<commit_before>/*
Simple List has no arguments!
 */

var _ = require('lodash'),
  keycode = require('keycode'),
  dom = require('../services/dom'),
  focus = require('../decorators/focus');

module.exports = function (result) {
  var name = result.name,
    el = dom.create(`

        { item.text }
        <input class="simple-list-add" rv-on-click="${name}.unselectAll" placeholder="Start typing here…" />

`);

  /**
   * unselect all items
   * @param  {[]} items
   * @return {[]}
   */
  function unselectAll(items) {
    return items.map(function (item) {
      item._selected = false;
      return item;
    });
  }

  /**
   * unselect all items, then select a specific item
   * @param {object} item
   * @param  {array} data
   */
  function selectItem(item, data) {
    unselectAll(data);
    item._selected = true;
  }

  /**
   * select previous item in list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function selectPrevious(e, index, data) {
    if (index > 0 && e.target.previousSibling) {
      selectItem(data[index - 1], data);
      e.target.previousSibling.focus();
    }
  }

  /**
   * select next item in list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function selectNext(e, index, data) {
    var input = dom.find(el, '.simple-list-add');

    if (index < data.length - 1) {
      e.preventDefault(); // kill that tab!
      selectItem(data[index + 1], data );
      e.target.nextSibling.focus();
    } else {
      // we currently have the last item selected, so focus the input
      e.preventDefault();
      e.stopPropagation(); // stop the current event first
      input.dispatchEvent(new Event('click'));
      input.focus();
    }
  }

  /**
   * remove item from list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function deleteItem(e, index, data) {
    var prevSibling = e.target.previousSibling,
      input = dom.find(dom.closest(e.target, '.simple-list'), '.simple-list-add');

    e.preventDefault(); // prevent triggering the browser's back button
    data.splice(index, 1); // remove item from the list

    if (index > 0) {
      prevSibling.focus();
      prevSibling.dispatchEvent(new Event('click'));
    } else {
      // you deleted all the items! focus the input
      input.focus();
    }
  }

  /*
  Click Handlers
   */

  // unselect all items when you click to add a new one
  result.bindings.unselectAll = function (e, bindings) {
    unselectAll(bindings[name].data);
  };

  // select an item (and unselect all others) when you click it
  result.bindings.selectItem = function (e, bindings) {
    selectItem(bindings.item, bindings[name].data);
  };

  // move between items and delete items when pressing the relevant keys (when an item is selected)
  result.bindings.keyactions = function (e, bindings) {
    var key = keycode(e),
      data = bindings[name].data,
      index = data.indexOf(bindings.item);

    if (key === 'left') {
      selectPrevious(e, index, data);
    } else if (key === 'tab' || key === 'right') {
      selectNext(e, index, data);
    } else if (key === 'delete' || key === 'backspace') {
      deleteItem(e, index, data);
    }
  };

  // put the element into the result object
  result.el = el;

  // add binder for creating new items
  result.binders.simplelist = {
    publish: true,
    bind: function (boundEl) {
      // this is called when the binder initializes
      var addEl = dom.find(boundEl, '.simple-list-add'),
        observer = this.observer;

      // add new item from the add-items field
      function addItem(e) {
        var data = observer.value(),
          newText = { text: addEl.value }; // get the new item text

        // prevent creating newlines or tabbing out of the field
        if (e) {
          e.preventDefault();
        }

        if (addEl.value.length) {
          addEl.value = ''; // remove it from the add-item field
          data.push(newText); // put it into the data
          observer.setValue(data);
        } else {
          // close the form
          focus.unfocus();
        }
      }

      // select the last item when you backspace from the add-items field
      function selectLastItem(e) {
        var data = observer.value(),
          newItems = dom.findAll(boundEl, '.simple-list-item');

        if (!addEl.value || !addEl.value.length) {
          e.preventDefault(); // prevent triggering the browser's back button
          _.last(data)._selected = true;
          _.last(newItems).focus(); // focus on the last item
          observer.setValue(data);
        }
      }

      // handle keyboard events in the add-items field
      function handleItemKeyEvents(e) {
        var key = keycode(e);

        if (key === 'enter' || key === 'tab') {
          addItem(e);
        } else if (key === 'delete' || key === 'backspace' || key === 'left') {
          selectLastItem(e);
        }
      }

      addEl.addEventListener('keydown', handleItemKeyEvents);
    }
  };

  return result;
};
<commit_msg>comma adds items to simple-list<commit_after>/*
Simple List has no arguments!
 */

var _ = require('lodash'),
  keycode = require('keycode'),
  dom = require('../services/dom'),
  focus = require('../decorators/focus');

module.exports = function (result) {
  var name = result.name,
    el = dom.create(`

        { item.text }
        <input class="simple-list-add" rv-on-click="${name}.unselectAll" placeholder="Start typing here…" />

`);

  /**
   * unselect all items
   * @param  {[]} items
   * @return {[]}
   */
  function unselectAll(items) {
    return items.map(function (item) {
      item._selected = false;
      return item;
    });
  }

  /**
   * unselect all items, then select a specific item
   * @param {object} item
   * @param  {array} data
   */
  function selectItem(item, data) {
    unselectAll(data);
    item._selected = true;
  }

  /**
   * select previous item in list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function selectPrevious(e, index, data) {
    if (index > 0 && e.target.previousSibling) {
      selectItem(data[index - 1], data);
      e.target.previousSibling.focus();
    }
  }

  /**
   * select next item in list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function selectNext(e, index, data) {
    var input = dom.find(el, '.simple-list-add');

    if (index < data.length - 1) {
      e.preventDefault(); // kill that tab!
      selectItem(data[index + 1], data );
      e.target.nextSibling.focus();
    } else {
      // we currently have the last item selected, so focus the input
      e.preventDefault();
      e.stopPropagation(); // stop the current event first
      input.dispatchEvent(new Event('click'));
      input.focus();
    }
  }

  /**
   * remove item from list
   * @param  {Event} e
   * @param  {number} index
   * @param  {array} data
   */
  function deleteItem(e, index, data) {
    var prevSibling = e.target.previousSibling,
      input = dom.find(dom.closest(e.target, '.simple-list'), '.simple-list-add');

    e.preventDefault(); // prevent triggering the browser's back button
    data.splice(index, 1); // remove item from the list

    if (index > 0) {
      prevSibling.focus();
      prevSibling.dispatchEvent(new Event('click'));
    } else {
      // you deleted all the items! focus the input
      input.focus();
    }
  }

  /*
  Click Handlers
   */

  // unselect all items when you click to add a new one
  result.bindings.unselectAll = function (e, bindings) {
    unselectAll(bindings[name].data);
  };

  // select an item (and unselect all others) when you click it
  result.bindings.selectItem = function (e, bindings) {
    selectItem(bindings.item, bindings[name].data);
  };

  // move between items and delete items when pressing the relevant keys (when an item is selected)
  result.bindings.keyactions = function (e, bindings) {
    var key = keycode(e),
      data = bindings[name].data,
      index = data.indexOf(bindings.item);

    if (key === 'left') {
      selectPrevious(e, index, data);
    } else if (key === 'tab' || key === 'right') {
      selectNext(e, index, data);
    } else if (key === 'delete' || key === 'backspace') {
      deleteItem(e, index, data);
    }
  };

  // put the element into the result object
  result.el = el;

  // add binder for creating new items
  result.binders.simplelist = {
    publish: true,
    bind: function (boundEl) {
      // this is called when the binder initializes
      var addEl = dom.find(boundEl, '.simple-list-add'),
        observer = this.observer;

      // add new item from the add-items field
      function addItem(e) {
        var data = observer.value(),
          newText = { text: addEl.value }; // get the new item text

        // prevent creating newlines or tabbing out of the field
        if (e) {
          e.preventDefault();
        }

        if (addEl.value.length) {
          addEl.value = ''; // remove it from the add-item field
          data.push(newText); // put it into the data
          observer.setValue(data);
        } else {
          // close the form
          focus.unfocus();
        }
      }

      // select the last item when you backspace from the add-items field
      function selectLastItem(e) {
        var data = observer.value(),
          newItems = dom.findAll(boundEl, '.simple-list-item');

        if (!addEl.value || !addEl.value.length) {
          e.preventDefault(); // prevent triggering the browser's back button
          _.last(data)._selected = true;
          _.last(newItems).focus(); // focus on the last item
          observer.setValue(data);
        }
      }

      // handle keyboard events in the add-items field
      function handleItemKeyEvents(e) {
        var key = keycode(e);

        if (key === 'enter' || key === 'tab' || key === ',') {
          addItem(e);
        } else if (key === 'delete' || key === 'backspace' || key === 'left') {
          selectLastItem(e);
        }
      }

      addEl.addEventListener('keydown', handleItemKeyEvents);
    }
  };

  return result;
};
<|endoftext|>


<commit_before>/*globals Endpoint, ControlPoint*/
/*exported BezierCurve*/
var BezierCurve = (function() {
  'use strict';

  function BezierCurve() {
    var x0 = 0,
        y0 = 0,
        x1 = 0,
        y1 = 0,
        x2 = 0,
        y2 = 0,
        x3 = 0,
        y3 = 0;

    // Set from existing endpoints.
    if ( arguments.length === 2 ) {
      this.p0 = arguments[0];
      this.p1 = this.p0.next;
      this.p3 = arguments[2];
      this.p2 = this.p3.prev;
      return this;
    }

    // Set endpoints.
    if ( arguments.length === 4 ) {
      x0 = arguments[0];
      y0 = arguments[1];
      x3 = arguments[2];
      y3 = arguments[3];
    }

    // Set endpoint and control points.
    // For path continuation.
    if ( arguments.length === 6 ) {
      x1 = arguments[0];
      y1 = arguments[1];
      x2 = arguments[2];
      y2 = arguments[3];
      x3 = arguments[4];
      y3 = arguments[5];
    }

    // Set endpoints and control points.
    if ( arguments.length === 8 ) {
      x0 = arguments[0];
      y0 = arguments[1];
      x1 = arguments[2];
      y1 = arguments[3];
      x2 = arguments[4];
      y2 = arguments[5];
      x3 = arguments[6];
      y3 = arguments[7];
    }

    this.p0 = new Endpoint( x0, y0 );
    this.p1 = this.p0.next = new ControlPoint( x1, y1 );
    this.p3 = new Endpoint( x3, y3 );
    this.p2 = this.p3.prev = new ControlPoint( x2, y2 );
  }

  BezierCurve.prototype.draw = function( ctx ) {
    ctx.moveTo( this.p0.x, this.p0.y );
    ctx.bezierCurveTo(
      this.p1.x, this.p1.y,
      this.p2.x, this.p2.y,
      this.p3.x, this.p3.y
    );
  };

  BezierCurve.prototype.controlPoints = function() {
    return [ this.p0, this.p1, this.p2, this.p3 ];
  };

  BezierCurve.prototype.linkTo = function( curve ) {
    var next = this.p0.next.unobserve();
    this.p0 = curve.p3;
    this.p0.next = next;
    return this;
  };

  BezierCurve.prototype.unlink = function() {
    this.p0 = this.p0.clone();
    return this;
  };

  /**
   * Returns the coordinate arrays of the curves formed by the split at
   * parameter t.
   */
  BezierCurve.prototype.split = function( t ) {
    var x0 = this.p0.x,
        y0 = this.p0.y,
        x1 = this.p1.x,
        y1 = this.p1.y,
        x2 = this.p2.x,
        y2 = this.p2.y,
        x3 = this.p3.x,
        y3 = this.p3.y;

    // Lerp control points.
    var x01 = x0 + t * ( x1 - x0 ),
        y01 = y0 + t * ( y1 - y0 );

    var x12 = x1 + t * ( x2 - x1 ),
        y12 = y1 + t * ( y2 - y1 );

    var x23 = x2 + t * ( x3 - x2 ),
        y23 = y2 + t * ( y3 - y2 );

    // Second iteration.
    var x012 = x01 + t * ( x12 - x01 ),
        y012 = y01 + t * ( y12 - y01 );

    var x123 = x12 + t * ( x23 - x12 ),
        y123 = y12 + t * ( y23 - y12 );

    // Final iteration.
    var x0123 = x012 + t * ( x123 - x012 ),
        y0123 = y012 + t * ( y123 - y012 );

    return [
      // First curve, from 0 to t.
      [ x0, y0, x01, y01, x012, y012, x0123, y0123 ],
      // Second curve, from t to 1.
      [ x0123, y0123, x123, y123, x23, y23, x3, y3 ]
    ];
  };

  BezierCurve.prototype.toArray = function() {
    return [
      this.p0.x, this.p0.y,
      this.p1.x, this.p1.y,
      this.p2.x, this.p2.y,
      this.p3.x, this.p3.y
    ];
  };

  BezierCurve.fromArray = function() {
    /* jshint supernew:true */
    return new (Function.prototype.bind.apply( BezierCurve, arguments ));
    /* jshint supernew:false */
  };

  // Define public getters and setters for individual coordinates.
  // Favors convenience over speed.
  [ 'p0', 'p1', 'p2', 'p3' ].forEach(function( point, index ) {
    [ 'x', 'y' ].forEach(function( axis ) {
      var prop = axis + index;

      Object.defineProperty( BezierCurve.prototype, prop, {
        get: function() {
          return this[ point ][ axis ];
        },

        set: function( value ) {
          this[ point ][ axis ] = value;
          return value;
        }
      });
    });
  });

  return BezierCurve;

}) ();
<commit_msg>bezier-curve: Fix missing context argument in BezierCurve.fromArray.<commit_after>/*globals Endpoint, ControlPoint*/
/*exported BezierCurve*/
var BezierCurve = (function() {
  'use strict';

  function BezierCurve() {
    var x0 = 0,
        y0 = 0,
        x1 = 0,
        y1 = 0,
        x2 = 0,
        y2 = 0,
        x3 = 0,
        y3 = 0;

    // Set from existing endpoints.
    if ( arguments.length === 2 ) {
      this.p0 = arguments[0];
      this.p1 = this.p0.next;
      this.p3 = arguments[2];
      this.p2 = this.p3.prev;
      return this;
    }

    // Set endpoints.
    if ( arguments.length === 4 ) {
      x0 = arguments[0];
      y0 = arguments[1];
      x3 = arguments[2];
      y3 = arguments[3];
    }

    // Set endpoint and control points.
    // For path continuation.
    if ( arguments.length === 6 ) {
      x1 = arguments[0];
      y1 = arguments[1];
      x2 = arguments[2];
      y2 = arguments[3];
      x3 = arguments[4];
      y3 = arguments[5];
    }

    // Set endpoints and control points.
    if ( arguments.length === 8 ) {
      x0 = arguments[0];
      y0 = arguments[1];
      x1 = arguments[2];
      y1 = arguments[3];
      x2 = arguments[4];
      y2 = arguments[5];
      x3 = arguments[6];
      y3 = arguments[7];
    }

    this.p0 = new Endpoint( x0, y0 );
    this.p1 = this.p0.next = new ControlPoint( x1, y1 );
    this.p3 = new Endpoint( x3, y3 );
    this.p2 = this.p3.prev = new ControlPoint( x2, y2 );
  }

  BezierCurve.prototype.draw = function( ctx ) {
    ctx.moveTo( this.p0.x, this.p0.y );
    ctx.bezierCurveTo(
      this.p1.x, this.p1.y,
      this.p2.x, this.p2.y,
      this.p3.x, this.p3.y
    );
  };

  BezierCurve.prototype.controlPoints = function() {
    return [ this.p0, this.p1, this.p2, this.p3 ];
  };

  BezierCurve.prototype.linkTo = function( curve ) {
    var next = this.p0.next.unobserve();
    this.p0 = curve.p3;
    this.p0.next = next;
    return this;
  };

  BezierCurve.prototype.unlink = function() {
    this.p0 = this.p0.clone();
    return this;
  };

  /**
   * Returns the coordinate arrays of the curves formed by the split at
   * parameter t.
   */
  BezierCurve.prototype.split = function( t ) {
    var x0 = this.p0.x,
        y0 = this.p0.y,
        x1 = this.p1.x,
        y1 = this.p1.y,
        x2 = this.p2.x,
        y2 = this.p2.y,
        x3 = this.p3.x,
        y3 = this.p3.y;

    // Lerp control points.
    var x01 = x0 + t * ( x1 - x0 ),
        y01 = y0 + t * ( y1 - y0 );

    var x12 = x1 + t * ( x2 - x1 ),
        y12 = y1 + t * ( y2 - y1 );

    var x23 = x2 + t * ( x3 - x2 ),
        y23 = y2 + t * ( y3 - y2 );

    // Second iteration.
    var x012 = x01 + t * ( x12 - x01 ),
        y012 = y01 + t * ( y12 - y01 );

    var x123 = x12 + t * ( x23 - x12 ),
        y123 = y12 + t * ( y23 - y12 );

    // Final iteration.
    var x0123 = x012 + t * ( x123 - x012 ),
        y0123 = y012 + t * ( y123 - y012 );

    return [
      // First curve, from 0 to t.
      [ x0, y0, x01, y01, x012, y012, x0123, y0123 ],
      // Second curve, from t to 1.
      [ x0123, y0123, x123, y123, x23, y23, x3, y3 ]
    ];
  };

  BezierCurve.prototype.toArray = function() {
    return [
      this.p0.x, this.p0.y,
      this.p1.x, this.p1.y,
      this.p2.x, this.p2.y,
      this.p3.x, this.p3.y
    ];
  };

  BezierCurve.fromArray = function( array ) {
    // Add the context argument to array for Function.prototype.bind.
    array = [ this ].concat( array );
    /* jshint supernew:true */
    return new (Function.prototype.bind.apply( BezierCurve, array ));
    /* jshint supernew:false */
  };

  // Define public getters and setters for individual coordinates.
  // Favors convenience over speed.
  [ 'p0', 'p1', 'p2', 'p3' ].forEach(function( point, index ) {
    [ 'x', 'y' ].forEach(function( axis ) {
      var prop = axis + index;

      Object.defineProperty( BezierCurve.prototype, prop, {
        get: function() {
          return this[ point ][ axis ];
        },

        set: function( value ) {
          this[ point ][ axis ] = value;
          return value;
        }
      });
    });
  });

  return BezierCurve;

}) ();
<|endoftext|>


<commit_before>var merohcCRMControllers = angular.module('merohc.crm.controllers', ['ui.router']);

merohcCRMControllers.controller('CompanyListController', ['$scope', '$http', '$location', '$state', 'merohc-config' , function ($scope, $http, $location, $state, merohcConfig) {

  $scope.items = '';

  $scope.filteredItems = $scope.items;

  $scope.searchTerm="";

  $scope.selectedItem=$scope.items[0];

  $scope.targetId="";

  $scope.setTargetId=function(targetId){
    $scope.targetId=targetId;
  }

  $scope.selectItem=function(selectedCompany){
    $scope.selectedItem = selectedCompany;
    $state.go('crm.companies.view', { companyId: selectedCompany.id });
  };

  $scope.selectItemById=function(id){
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===id){
        $scope.selectedItem=company;
      }
    }
  }

  $scope.isItemSelected=function(selectedCompany){
    return selectedCompany===$scope.selectedItem;
  };

  $scope.filter=function(){
    $scope.filteredItems=[];
    for (var index in $scope.items) {
      var company = $scope.items[index];
      var regexp = new RegExp($scope.searchTerm,"i");
      if (company.name.match(regexp)){
        $scope.filteredItems.push(company);
      }
    }
  };

  $scope.updateItem = function(oldItem, newItem) {
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===oldItem.id){
        $scope.items[index]=newItem;
      }
    }

    $scope.filter();
  }

  $scope.addItem = function(newItem) {
    $scope.items.push(newItem);
    $scope.filter();
  }

  $scope.deleteItem = function(oldItem) {

    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===oldItem.id){
        if (index > -1) {
          $scope.items.splice(index, 1);
        }
      }
    }
    $state.go("crm.companies");
    $scope.refresh();
  }

  $scope.refresh=function(){
    $http.get(merohcConfig.BASE_URL + '/company').success(function(data){
      $scope.items = data;
      $scope.filter();
    });
  };

  $scope.refresh();

}]);

merohcCRMControllers.controller('CompanyDetailController',
    ['$scope', '$http', '$state', 'merohc-config',
    function ($scope, $http, $state, merohcConfig){

  $scope.companyId=$state.params.companyId;

  $scope.updateCompany=function(){
    if ($scope.companyId){
      $http.get(merohcConfig.BASE_URL + '/company/'+encodeURIComponent($scope.companyId)).success(function(data){
        $scope.company = data;
      });
    }
  };

  $scope.deleteCompany=function(){
    //FIXME JC 151212 - Should ask confirmation for deletion
    if ($scope.companyId){
      $http.delete(merohcConfig.BASE_URL + '/company/'+encodeURIComponent($scope.companyId)).success(function(data){
        $scope.deleteItem($scope.company);
      });
    }
  }

  $scope.editCompany=function(){
    $state.go('crm.companies.edit', { companyId: $scope.companyId });
  }

  //get back contact details for the company
  /*$scope.updateContactDetails=function(){
    if ($scope.companyId){
      $http.get('services/v1/ContactDetails?field=target&value='+$scope.companyId).success(function(data){
        $scope.contactDetails = data;
      });
    }
  };

  //get back notes for the company
  $scope.updateNotes=function(){
    if ($scope.companyId){
      $http.get('services/v1/Note?field=target&value='+$scope.companyId).success(function(data){
        $scope.notes = data;
      });
    }
  };

  //get back employees for the company
  $scope.updateEmployees=function(){
    if ($scope.companyId){
      $http.get('services/v1/Employee?field=company&value='+$scope.companyId).success(function(data){
        $scope.employees = data;
      });
    }
  };

  //get back invoices for the company
  $scope.updateInvoices=function(){
    if ($scope.companyId){
      $http.get('services/v1/Invoice?field=payer&value='+$scope.companyId).success(function(data){
        $scope.invoices = data;
      });
    }
  };*/

  $scope.updateCompany();
  /*$scope.updateContactDetails();
  $scope.updateNotes();
  $scope.updateEmployees();
  $scope.updateInvoices();*/

}]);

merohcCRMControllers.controller('CompanyCreateController', ['$scope', '$http', '$window', 'merohc-config',function ($scope, $http, $window, merohcConfig) {

  $scope.saveCompany = function(){
    $http({
            method  : 'PUT',
            url     : merohcConfig.BASE_URL + '/company/add',
            data    : $.param($scope.company),  // pass in data as strings
            headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
         })
          .success(function(data) {

            //update company and selectedItem in parent scope
            $scope.addItem(data);
            $scope.$parent.selectedItem=data;

            $window.history.back();
          });
  };

  $scope.cancel = function(){
    $window.history.back();
  }

}]);

merohcCRMControllers.controller('CompanyEditController', ['$scope', '$http', '$window', '$state', 'merohc-config',function ($scope, $http, $window, $state, merohcConfig) {

  $scope.companyId=$state.params.companyId;

  $scope.updateCompany=function(){
    if ($scope.companyId){
      $http.get(merohcConfig.BASE_URL + '/company/'+encodeURIComponent($scope.companyId)).success(function(data){
        $scope.company = data;
        $scope.oldCompany = angular.copy(data);
      });
    }
  };

  $scope.saveCompany = function(){
    $http({
            method  : 'POST',
            url     : merohcConfig.BASE_URL + '/company',
            data    : $.param($scope.company),  // pass in data as strings
            headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
         })
          .success(function(data) {

            //update company and selectedItem in parent scope
            $scope.updateItem($scope.oldCompany, data);
            $scope.$parent.selectedItem=data;

            $state.go('^')
          });
  };

  $scope.cancel = function(){
    $window.history.back();
  }

  $scope.updateCompany();

}]);

merohcCRMControllers.controller('PersonListController', function ($scope, $http, $location, $state) {

  $scope.items = '';

  $scope.filteredItems = $scope.items;

  $scope.searchTerm="";

  $scope.selectedItem=$scope.items[0];

  $scope.targetId="";

  $scope.setTargetId=function(targetId){
    $scope.targetId=targetId;
  }

  $scope.selectItem=function(selectedPerson){
    $scope.selectedItem = selectedPerson;
    $state.go('crm.persons.view', { personId: selectedPerson.id });
  };

  $scope.selectItemById=function(id){
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===id){
        $scope.selectedItem=company;
      }
    }
  }

  $scope.isItemSelected=function(selectedCompany){
    return selectedCompany===$scope.selectedItem;
  };

  $scope.filter=function(){
    $scope.filteredItems=[];
    for (var index in $scope.items) {
      var company = $scope.items[index];
      var regexp = new RegExp($scope.searchTerm,"i");
      if (company.Person.firstName.match(regexp)||company.Person.lastName.match(regexp)){
        $scope.filteredItems.push(company);
      }
    }
  };

  $scope.updateItem = function(oldItem, newItem) {
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company===oldItem){
        $scope.items[index]=newItem;
      }
    }

    $scope.filter();
  }

  $scope.addItem = function(newItem) {
    $scope.items.push(newItem);
    $scope.filter();
  }

  $scope.deleteItem = function(oldItem) {
    var index = $scope.items.indexOf(oldItem);

    if (index > -1) {
        $scope.items.splice(index, 1);
    }

    $scope.filter();
  }

  $scope.refresh=function(){
    $http.get('services/v1/Person').success(function(data){
      $scope.items = data;
      $scope.filter();
    });
  };

  $scope.refresh();

});

merohcCRMControllers.controller('PersonCreateController', function ($scope, $http, $window) {

  $scope.saveCompany = function(){
    $http({
            method  : 'PUT',
            url     : 'companies/add',
            data    : $.param($scope.company),  // pass in data as strings
            headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
         })
          .success(function(data) {

            //update company and selectedItem in parent scope
            $scope.addItem(data);
            $scope.$parent.selectedItem=data;

            $window.history.back();
          });
  };

  $scope.cancel = function(){
    $window.history.back();
  }

});

merohcCRMControllers.controller('PersonDetailController', function ($scope, $http, $state){

  $scope.personId=$state.params.personId;

  $scope.updatePerson=function(){
      if ($scope.personId){
        $http.get('services/v1/Person/'+$scope.personId).success(function(data){
          $scope.person = data;
        });
      }
    };

  //get back contact details for the person
  $scope.updateContactDetails=function(){
    if ($scope.personId){
      $http.get('services/v1/ContactDetails?field=target&value='+$scope.personId).success(function(data){
        $scope.contactDetails = data;
      });
    }
  };

  //get back notes for the person
  $scope.updateNotes=function(){
    if ($scope.personId){
      $http.get('services/v1/Note?field=target&value='+$scope.personId).success(function(data){
        $scope.notes = data;
      });
    }
  };

  //get back employees for the person
  $scope.updateEmployees=function(){
    if ($scope.personId){
      $http.get('services/v1/Employee?field=company&value='+$scope.personId).success(function(data){
        $scope.employees = data;
      });
    }
  };

  $scope.updatePerson();
  $scope.updateContactDetails();
  $scope.updateNotes();
  $scope.updateEmployees();

});

merohcCRMControllers.controller('NoteController', function ($scope, $http, $state){

  $scope.noteId=$state.params.noteId;

  $scope.updateNote=function(){
    if ($scope.noteId){
      $http.get('services/v1/Note/'+$scope.noteId).success(function(data){
        $scope.note = data;
      });
    }
  };

  $scope.updateNote();
});

merohcCRMControllers.controller('ContactDetailsController', function ($scope, $http, $state){

  $scope.contactDetailsId=$state.params.contactDetailsId;

  $scope.updateContactDetails=function(){
    if ($scope.contactDetailsId){
      $http.get('services/v1/ContactDetails/'+$scope.contactDetailsId).success(function(data){
        $scope.contactDetails = data;
      });
    }
  };

  $scope.updateContactDetails();
});<commit_msg>Fix views update after company creation (now go on company view)<commit_after>var merohcCRMControllers = angular.module('merohc.crm.controllers', ['ui.router']);

merohcCRMControllers.controller('CompanyListController', ['$scope', '$http', '$location', '$state', 'merohc-config' , function ($scope, $http, $location, $state, merohcConfig) {

  $scope.items = '';

  $scope.filteredItems = $scope.items;

  $scope.searchTerm="";

  $scope.selectedItem=$scope.items[0];

  $scope.targetId="";

  $scope.setTargetId=function(targetId){
    $scope.targetId=targetId;
  }

  $scope.selectItem=function(selectedCompany){
    $scope.selectedItem = selectedCompany;
    $state.go('crm.companies.view', { companyId: selectedCompany.id });
  };

  $scope.selectItemById=function(id){
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===id){
        $scope.selectedItem=company;
      }
    }
  }

  $scope.isItemSelected=function(selectedCompany){
    return selectedCompany===$scope.selectedItem;
  };

  $scope.filter=function(){
    $scope.filteredItems=[];
    for (var index in $scope.items) {
      var company = $scope.items[index];
      var regexp = new RegExp($scope.searchTerm,"i");
      if (company.name.match(regexp)){
        $scope.filteredItems.push(company);
      }
    }
  };

  $scope.updateItem = function(oldItem, newItem) {
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===oldItem.id){
        $scope.items[index]=newItem;
      }
    }

    $scope.filter();
  }

  $scope.addItem = function(newItem) {
    $scope.items.push(newItem);
    $scope.filter();
    $state.go('crm.companies.view', { companyId: newItem.id })
  }

  $scope.deleteItem = function(oldItem) {

    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===oldItem.id){
        if (index > -1) {
          $scope.items.splice(index, 1);
        }
      }
    }
    $state.go("crm.companies");
    $scope.refresh();
  }

  $scope.refresh=function(){
    $http.get(merohcConfig.BASE_URL + '/company').success(function(data){
      $scope.items = data;
      $scope.filter();
    });
  };

  $scope.refresh();

}]);

merohcCRMControllers.controller('CompanyDetailController',
    ['$scope', '$http', '$state', 'merohc-config',
    function ($scope, $http, $state, merohcConfig){

  $scope.companyId=$state.params.companyId;

  $scope.updateCompany=function(){
    if ($scope.companyId){
      $http.get(merohcConfig.BASE_URL + '/company/'+encodeURIComponent($scope.companyId)).success(function(data){
        $scope.company = data;
      });
    }
  };

  $scope.deleteCompany=function(){
    //FIXME JC 151212 - Should ask confirmation for deletion
    if ($scope.companyId){
      $http.delete(merohcConfig.BASE_URL + '/company/'+encodeURIComponent($scope.companyId)).success(function(data){
        $scope.deleteItem($scope.company);
      });
    }
  }

  $scope.editCompany=function(){
    $state.go('crm.companies.edit', { companyId: $scope.companyId });
  }

  //get back contact details for the company
  /*$scope.updateContactDetails=function(){
    if ($scope.companyId){
      $http.get('services/v1/ContactDetails?field=target&value='+$scope.companyId).success(function(data){
        $scope.contactDetails = data;
      });
    }
  };

  //get back notes for the company
  $scope.updateNotes=function(){
    if ($scope.companyId){
      $http.get('services/v1/Note?field=target&value='+$scope.companyId).success(function(data){
        $scope.notes = data;
      });
    }
  };

  //get back employees for the company
  $scope.updateEmployees=function(){
    if ($scope.companyId){
      $http.get('services/v1/Employee?field=company&value='+$scope.companyId).success(function(data){
        $scope.employees = data;
      });
    }
  };

  //get back invoices for the company
  $scope.updateInvoices=function(){
    if ($scope.companyId){
      $http.get('services/v1/Invoice?field=payer&value='+$scope.companyId).success(function(data){
        $scope.invoices = data;
      });
    }
  };*/

  $scope.updateCompany();
  /*$scope.updateContactDetails();
  $scope.updateNotes();
  $scope.updateEmployees();
  $scope.updateInvoices();*/

}]);

merohcCRMControllers.controller('CompanyCreateController', ['$scope', '$http', '$window', 'merohc-config',function ($scope, $http, $window, merohcConfig) {

  $scope.saveCompany = function(){
    $http({
            method  : 'PUT',
            url     : merohcConfig.BASE_URL + '/company/add',
            data    : $.param($scope.company),  // pass in data as strings
            headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
         })
          .success(function(data) {

            //update company and selectedItem in parent scope
            $scope.addItem(data);
            $scope.$parent.selectedItem=data;
          });
  };

  $scope.cancel = function(){
    $window.history.back();
  }

}]);

merohcCRMControllers.controller('CompanyEditController', ['$scope', '$http', '$window', '$state', 'merohc-config',function ($scope, $http, $window, $state, merohcConfig) {

  $scope.companyId=$state.params.companyId;

  $scope.updateCompany=function(){
    if ($scope.companyId){
      $http.get(merohcConfig.BASE_URL + '/company/'+encodeURIComponent($scope.companyId)).success(function(data){
        $scope.company = data;
        $scope.oldCompany = angular.copy(data);
      });
    }
  };

  $scope.saveCompany = function(){
    $http({
            method  : 'POST',
            url     : merohcConfig.BASE_URL + '/company',
            data    : $.param($scope.company),  // pass in data as strings
            headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
         })
          .success(function(data) {

            //update company and selectedItem in parent scope
            $scope.updateItem($scope.oldCompany, data);
            $scope.$parent.selectedItem=data;

            $state.go('^')
          });
  };

  $scope.cancel = function(){
    $window.history.back();
  }

  $scope.updateCompany();

}]);

merohcCRMControllers.controller('PersonListController', function ($scope, $http, $location, $state) {

  $scope.items = '';

  $scope.filteredItems = $scope.items;

  $scope.searchTerm="";

  $scope.selectedItem=$scope.items[0];

  $scope.targetId="";

  $scope.setTargetId=function(targetId){
    $scope.targetId=targetId;
  }

  $scope.selectItem=function(selectedPerson){
    $scope.selectedItem = selectedPerson;
    $state.go('crm.persons.view', { personId: selectedPerson.id });
  };

  $scope.selectItemById=function(id){
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company.id===id){
        $scope.selectedItem=company;
      }
    }
  }

  $scope.isItemSelected=function(selectedCompany){
    return selectedCompany===$scope.selectedItem;
  };

  $scope.filter=function(){
    $scope.filteredItems=[];
    for (var index in $scope.items) {
      var company = $scope.items[index];
      var regexp = new RegExp($scope.searchTerm,"i");
      if (company.Person.firstName.match(regexp)||company.Person.lastName.match(regexp)){
        $scope.filteredItems.push(company);
      }
    }
  };

  $scope.updateItem = function(oldItem, newItem) {
    for (var index in $scope.items) {
      var company = $scope.items[index];
      if (company===oldItem){
        $scope.items[index]=newItem;
      }
    }

    $scope.filter();
  }

  $scope.addItem = function(newItem) {
    $scope.items.push(newItem);
    $scope.filter();
  }

  $scope.deleteItem = function(oldItem) {
    var index = $scope.items.indexOf(oldItem);

    if (index > -1) {
        $scope.items.splice(index, 1);
    }

    $scope.filter();
  }

  $scope.refresh=function(){
    $http.get('services/v1/Person').success(function(data){
      $scope.items = data;
      $scope.filter();
    });
  };

  $scope.refresh();

});

merohcCRMControllers.controller('PersonCreateController', function ($scope, $http, $window) {

  $scope.saveCompany = function(){
    $http({
            method  : 'PUT',
            url     : 'companies/add',
            data    : $.param($scope.company),  // pass in data as strings
            headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
         })
          .success(function(data) {

            //update company and selectedItem in parent scope
            $scope.addItem(data);
            $scope.$parent.selectedItem=data;

            $window.history.back();
          });
  };

  $scope.cancel = function(){
    $window.history.back();
  }

});

merohcCRMControllers.controller('PersonDetailController', function ($scope, $http, $state){

  $scope.personId=$state.params.personId;

  $scope.updatePerson=function(){
      if ($scope.personId){
        $http.get('services/v1/Person/'+$scope.personId).success(function(data){
          $scope.person = data;
        });
      }
    };

  //get back contact details for the person
  $scope.updateContactDetails=function(){
    if ($scope.personId){
      $http.get('services/v1/ContactDetails?field=target&value='+$scope.personId).success(function(data){
        $scope.contactDetails = data;
      });
    }
  };

  //get back notes for the person
  $scope.updateNotes=function(){
    if ($scope.personId){
      $http.get('services/v1/Note?field=target&value='+$scope.personId).success(function(data){
        $scope.notes = data;
      });
    }
  };

  //get back employees for the person
  $scope.updateEmployees=function(){
    if ($scope.personId){
      $http.get('services/v1/Employee?field=company&value='+$scope.personId).success(function(data){
        $scope.employees = data;
      });
    }
  };

  $scope.updatePerson();
  $scope.updateContactDetails();
  $scope.updateNotes();
  $scope.updateEmployees();

});

merohcCRMControllers.controller('NoteController', function ($scope, $http, $state){

  $scope.noteId=$state.params.noteId;

  $scope.updateNote=function(){
    if ($scope.noteId){
      $http.get('services/v1/Note/'+$scope.noteId).success(function(data){
        $scope.note = data;
      });
    }
  };

  $scope.updateNote();
});

merohcCRMControllers.controller('ContactDetailsController', function ($scope, $http, $state){

  $scope.contactDetailsId=$state.params.contactDetailsId;

  $scope.updateContactDetails=function(){
    if ($scope.contactDetailsId){
      $http.get('services/v1/ContactDetails/'+$scope.contactDetailsId).success(function(data){
        $scope.contactDetails = data;
      });
    }
  };

  $scope.updateContactDetails();
});<|endoftext|>


<commit_before>
var fs = require('fs'),
    http = require('http'),
    exec = require('child_process').exec,
	_ = require('underscore'),
    Geo = require('./geo');

function dpc(t,fn) { if(typeof(t) == 'function') setTimeout(t,0); else setTimeout(fn,t); }

function Scanner(options) {

	var self = this;
    self.options = options;

    var config = eval('('+fs.readFileSync("scanner.cfg",'utf8')+')');
    var upload = fs.existsSync('upload.cfg') ? eval('('+fs.readFileSync("upload.cfg",'utf8')+')') : null;

    self.addr_pending = { }     // list of addresses waiting scan
    self.addr_digested = { }    // list of scanned addresses
    self.addr_working = { }     // list of working addresses

    self.geo = new Geo({ timeout : config.http_socket_timeout });

  	// -----------------------------------------
   	// local http server interface
    if(config.http_port)
    {
        var express = require('express');
        var app = express();
        app.configure(function(){
            app.use(express.bodyParser());
        });
        app.get('/', function(req, res) {
            var str = self.render();
            res.write(str);
            res.end();
        });

        http.createServer(app).listen(config.http_port, function() {
            console.log("HTTP server listening on port: ",config.http_port);
        });
    }

    var logo = fs.readFileSync("resources/"+config.currency.toLowerCase()+".png","base64");
    if(logo)
        logo = "data:image/png;base64,"+logo;

    self.render = function() {
        var str = ""
            +""
            +"";
        if(logo)
            str += "

";
        str += "PEER TO PEER "+(config.currency.toUpperCase())+" MINING NETWORK - PUBLIC NODE LIST
GENERATED ON: "+(new
Date())+"
"
        if(self.poolstats)
            str += "Pool speed: "+(self.poolstats.pool_hash_rate/1000000).toFixed(2)+" "+config.speed_abbrev+"";
        str += "Currently observing "+(self.nodes_total || "N/A")+" nodes.
"+_.size(self.addr_working)+" nodes are public with following IPs:
";
        str += "
";
        str += "

IPs

Fee

Uptime

Location
";
        str += "

";

        var list = _.sortBy(_.toArray(self.addr_working), function(o) { return o.stats ? -o.stats.uptime : 0; })

        var row = 0;
        _.each(list, function(info) {
            var ip = info.ip;

            if (ip == '178.63.15.130') {
                ip = 'q30.qhor.net';
            }
            if (ip == '176.9.64.195') {
                ip = 'p2cache.syware.de';
            }

            var uptime = info.stats ? (info.stats.uptime / 60 / 60 / 24).toFixed(1) : "N/A";
            var fee = (info.fee || 0).toFixed(2);

            str += "

"+ip+":8336

"+fee+"%

"+uptime+" days
";
            str += "
";
            if(info.geo) {
                str += ""+info.geo.country+" "+"";
            }
            str += "
";
            str += "
";
            str += "
";
        })
        str += "

";
        str += ""
        return str;
    }

    // setup flushing of rendered HTML page to a file (useful for uploading to other sites)
    if(config.flush_to_file_every_N_msec && config.flush_filename) {
        function flush_rendering() {
            var str = self.render();
            fs.writeFile(config.flush_filename, str, { encoding : 'utf8'});
            dpc(config.flush_to_file_every_N_msec, flush_rendering);
        }

        dpc(5000, flush_rendering);
    }

    // defer init
    dpc(function(){
        self.restore_working();
        self.update();
    })

    var p2pool_init = true;

    // main function that reloads 'addr' file from p2pool
    self.update = function() {
        var filename = config.addr_file;
        if(!fs.existsSync(filename)) {
            console.error("Unable to fetch p2pool address list from:",config.addr_file);
            filename = config.init_file;    // if we can't read p2pool's addr file, we just cycle on the local default init...
        }

        fs.readFile(filename, { encoding : 'utf8' }, function(err, data) {
            if(err) {
                console.error(err);
            }
            else {
                try {
                    var addr_list = JSON.parse(data);
                    self.inject(addr_list);

                    // main init
                    if(p2pool_init) {
                        p2pool_init = false;

                        // if we can read p2pool addr file, also add our pre-collected IPs
                        // if(filename != config.init_file) {
                            var init_addr = JSON.parse(fs.readFileSync(config.init_file, 'utf8'));
                            self.inject(init_addr);
                        //}

                        for(var i = 0; i < (config.probe_N_IPs_simultaneously || 1); i++)
                            self.digest();
                        dpc(60*1000, function() { self.store_working(); })
                    }
                }
                catch(ex) {
                    console.error("Unable to parse p2pool address list");
                    console.error(ex);
                }
            }

            dpc(1000 * 60, self.update);
        })
    }

    // store public pools in a file that reloads at startup
    self.store_working = function() {
        var data = JSON.stringify(self.addr_working);
        fs.writeFile(config.store_file, data, { encoding : 'utf8' }, function(err) {
            dpc(60*1000, self.store_working);
        })
    }

    // reload public list at startup
    self.restore_working = function() {
        try {
            self.addr_working = JSON.parse(fs.readFileSync(config.store_file, 'utf8'));
        } catch(ex) { /*console.log(ex);*/ }
    }

    // inject new IPs from p2pool addr file
    self.inject = function(addr_list) {
        _.each(addr_list, function(info) {
            var ip = info[0][0];
            var port = info[0][1];

            if(!self.addr_digested[ip] && !self.addr_pending[ip]) {
                self.addr_pending[ip] = { ip : ip, port : port }
            }

            self.nodes_total = _.size(self.addr_digested) + _.size(self.addr_pending);
        });
    }

    // as we scan pools, we fetch global info from them to update the page
    self.update_global_stats = function(poolstats) {
        self.poolstats = poolstats;
    }

    // execute scan of a single IP
    self.digest = function() {

        if(!_.size(self.addr_pending))
            return self.list_complete();

        var info = _.find(self.addr_pending, function() { return true; });
        delete self.addr_pending[info.ip];
        self.addr_digested[info.ip] = info;
        // console.log("P2POOL DIGESTING:",info.ip);

        digest_ip(info, function(err, fee){
            if(!err) {
                info.fee = fee;
                self.addr_working[info.ip] = info;
                // console.log("FOUND WORKING POOL: ", info.ip);

                digest_local_stats(info, function(err, stats) {
                    if(!err)
                        info.stats = stats;
                    digest_global_stats(info, function(err, stats) {
                        if(!err)
                            self.update_global_stats(stats);

                        if(!info.geo)
                            self.geo.get(info.ip, function(err, geo) {
                                if(!err)
                                    info.geo = geo;

                                continue_digest();
                            });
                        else
                            continue_digest();
                    });
                });
            }
            else {
                delete self.addr_working[info.ip];
                continue_digest();
            }

            function continue_digest() {
                self.working_size = _.size(self.addr_working);
                dpc(self.digest);
            }
        });
    }

    // schedule restar of the scan once all IPs are done
    self.list_complete = function() {
        self.addr_pending = self.addr_digested;
        self.addr_digested = { }
        dpc(config.rescan_list_delay, self.digest);
    }

    // functions to fetch data from target node IP

    function digest_ip(info, callback) {

        var options = {
          host: info.ip,
          port: 8336,
          path: '/fee',
          method: 'GET'
        };

        self.request(options, callback);
    }

    function digest_local_stats(info, callback) {

        var options = {
          host: info.ip,
          port: 8336,
          path: '/local_stats',
          method: 'GET'
        };

        self.request(options, callback);
    }

    function digest_global_stats(info, callback) {

        var options = {
          host: info.ip,
          port: 8336,
          path: '/global_stats',
          method: 'GET'
        };

        self.request(options, callback);
    }

    // make http request to the target node ip
    self.request = function(options, callback, is_plain)
    {
        http_handler = http;
        var req = http_handler.request(options, function(res) {
            res.setEncoding('utf8');
            var result = '';
            res.on('data', function (data) {
                result += data;
            });

            res.on('end', function () {
                if(options.plain)
                    callback(null, result);
                else {
                    try {
                        var o = JSON.parse(result);
                        callback(null, o);
                    } catch(ex) {
                        console.error(ex);
                        callback(ex);
                    }
                }
            });
        });

        req.on('socket', function (socket) {
            socket.setTimeout(config.http_socket_timeout);
            socket.on('timeout', function() {
                req.abort();
            });
        });

        req.on('error', function(e) {
            callback(e);
        });

        req.end();
    }

    if(upload && process.platform != 'win32') {

        function do_upload() {

            if(upload.ftp) {
                var ftp = upload.ftp;
                if(!ftp.address || !ftp.username || !ftp.password)
                    return console.error("upload.cfg ftp configuration must contain target address, username and password");
                var cmd = "curl -T "+config.flush_filename+" "+ftp.address+" --user "+ftp.address+":"+ftp.password;
                exec(cmd, function(error){ if(error) console.error(error); });
            }

            if(upload.scp) {
                var scp = upload.scp;
                if(!scp.address)
                    return console.error("upload.cfg scp configuration must contain target address");
                var cmd = "scp -q ./"+config.flush_filename+" "+scp.address;
                exec(cmd, function(error){ if(error) console.error(error); });
            }

            dpc(config.upload_interval, do_upload);
        }

        dpc(5000, do_upload);
    }
    else
        console.log("upload.cfg not found, rendering available only on the local interface");
}

GLOBAL.scanner = new Scanner();

//
<commit_msg>fixed layout<commit_after>
var fs = require('fs'),
    http = require('http'),
    exec = require('child_process').exec,
	_ = require('underscore'),
    Geo = require('./geo');

function dpc(t,fn) { if(typeof(t) == 'function') setTimeout(t,0); else setTimeout(fn,t); }

function Scanner(options) {

	var self = this;
    self.options = options;

    var config = eval('('+fs.readFileSync("scanner.cfg",'utf8')+')');
    var upload = fs.existsSync('upload.cfg') ? eval('('+fs.readFileSync("upload.cfg",'utf8')+')') : null;

    self.addr_pending = { }     // list of addresses waiting scan
    self.addr_digested = { }    // list of scanned addresses
    self.addr_working = { }     // list of working addresses

    self.geo = new Geo({ timeout : config.http_socket_timeout });

  	// -----------------------------------------
   	// local http server interface
    if(config.http_port)
    {
        var express = require('express');
        var app = express();
        app.configure(function(){
            app.use(express.bodyParser());
        });
        app.get('/', function(req, res) {
            var str = self.render();
            res.write(str);
            res.end();
        });

        http.createServer(app).listen(config.http_port, function() {
            console.log("HTTP server listening on port: ",config.http_port);
        });
    }

    var logo = fs.readFileSync("resources/"+config.currency.toLowerCase()+".png","base64");
    if(logo)
        logo = "data:image/png;base64,"+logo;

    self.render = function() {
        var str = ""
            +""
            +"";
        if(logo)
            str += "

";
        str += "PEER TO PEER "+(config.currency.toUpperCase())+" MINING NETWORK - PUBLIC NODE LIST
GENERATED ON: "+(new
Date())+"
"
        if(self.poolstats)
            str += "Pool speed: "+(self.poolstats.pool_hash_rate/1000000).toFixed(2)+" "+config.speed_abbrev+"";
        str += "Currently observing "+(self.nodes_total || "N/A")+" nodes.
"+_.size(self.addr_working)+" nodes are public with following IPs:
";
        str += "
";
        str += "

IPs

Fee

Uptime

Location
";
        str += "

";

        var list = _.sortBy(_.toArray(self.addr_working), function(o) { return o.stats ? -o.stats.uptime : 0; })

        var row = 0;
        _.each(list, function(info) {
            var ip = info.ip;

            if (ip == '178.63.15.130') {
                ip = 'q30.qhor.net';
            }
            if (ip == '176.9.64.195') {
                ip = 'p2cache.syware.de';
            }

            var uptime = info.stats ? (info.stats.uptime / 60 / 60 / 24).toFixed(1) : "N/A";
            var fee = (info.fee || 0).toFixed(2);

            str += "

"+ip+":8336

"+fee+"%

"+uptime+" days
";
            str += "
";
            if(info.geo) {
                str += ""+info.geo.country+" "+"";
            }
            str += "
";
            str += "
";
            str += "
";
        })
        str += "

";
        str += ""
        return str;
    }

    // setup flushing of rendered HTML page to a file (useful for uploading to other sites)
    if(config.flush_to_file_every_N_msec && config.flush_filename) {
        function flush_rendering() {
            var str = self.render();
            fs.writeFile(config.flush_filename, str, { encoding : 'utf8'});
            dpc(config.flush_to_file_every_N_msec, flush_rendering);
        }

        dpc(5000, flush_rendering);
    }

    // defer init
    dpc(function(){
        self.restore_working();
        self.update();
    })

    var p2pool_init = true;

    // main function that reloads 'addr' file from p2pool
    self.update = function() {
        var filename = config.addr_file;
        if(!fs.existsSync(filename)) {
            console.error("Unable to fetch p2pool address list from:",config.addr_file);
            filename = config.init_file;    // if we can't read p2pool's addr file, we just cycle on the local default init...
        }

        fs.readFile(filename, { encoding : 'utf8' }, function(err, data) {
            if(err) {
                console.error(err);
            }
            else {
                try {
                    var addr_list = JSON.parse(data);
                    self.inject(addr_list);

                    // main init
                    if(p2pool_init) {
                        p2pool_init = false;

                        // if we can read p2pool addr file, also add our pre-collected IPs
                        // if(filename != config.init_file) {
                            var init_addr = JSON.parse(fs.readFileSync(config.init_file, 'utf8'));
                            self.inject(init_addr);
                        //}

                        for(var i = 0; i < (config.probe_N_IPs_simultaneously || 1); i++)
                            self.digest();
                        dpc(60*1000, function() { self.store_working(); })
                    }
                }
                catch(ex) {
                    console.error("Unable to parse p2pool address list");
                    console.error(ex);
                }
            }

            dpc(1000 * 60, self.update);
        })
    }

    // store public pools in a file that reloads at startup
    self.store_working = function() {
        var data = JSON.stringify(self.addr_working);
        fs.writeFile(config.store_file, data, { encoding : 'utf8' }, function(err) {
            dpc(60*1000, self.store_working);
        })
    }

    // reload public list at startup
    self.restore_working = function() {
        try {
            self.addr_working = JSON.parse(fs.readFileSync(config.store_file, 'utf8'));
        } catch(ex) { /*console.log(ex);*/ }
    }

    // inject new IPs from p2pool addr file
    self.inject = function(addr_list) {
        _.each(addr_list, function(info) {
            var ip = info[0][0];
            var port = info[0][1];

            if(!self.addr_digested[ip] && !self.addr_pending[ip]) {
                self.addr_pending[ip] = { ip : ip, port : port }
            }

            self.nodes_total = _.size(self.addr_digested) + _.size(self.addr_pending);
        });
    }

    // as we scan pools, we fetch global info from them to update the page
    self.update_global_stats = function(poolstats) {
        self.poolstats = poolstats;
    }

    // execute scan of a single IP
    self.digest = function() {

        if(!_.size(self.addr_pending))
            return self.list_complete();

        var info = _.find(self.addr_pending, function() { return true; });
        delete self.addr_pending[info.ip];
        self.addr_digested[info.ip] = info;
        // console.log("P2POOL DIGESTING:",info.ip);

        digest_ip(info, function(err, fee){
            if(!err) {
                info.fee = fee;
                self.addr_working[info.ip] = info;
                // console.log("FOUND WORKING POOL: ", info.ip);

                digest_local_stats(info, function(err, stats) {
                    if(!err)
                        info.stats = stats;
                    digest_global_stats(info, function(err, stats) {
                        if(!err)
                            self.update_global_stats(stats);

                        if(!info.geo)
                            self.geo.get(info.ip, function(err, geo) {
                                if(!err)
                                    info.geo = geo;

                                continue_digest();
                            });
                        else
                            continue_digest();
                    });
                });
            }
            else {
                delete self.addr_working[info.ip];
                continue_digest();
            }

            function continue_digest() {
                self.working_size = _.size(self.addr_working);
                dpc(self.digest);
            }
        });
    }

    // schedule restar of the scan once all IPs are done
    self.list_complete = function() {
        self.addr_pending = self.addr_digested;
        self.addr_digested = { }
        dpc(config.rescan_list_delay, self.digest);
    }

    // functions to fetch data from target node IP

    function digest_ip(info, callback) {

        var options = {
          host: info.ip,
          port: 8336,
          path: '/fee',
          method: 'GET'
        };

        self.request(options, callback);
    }

    function digest_local_stats(info, callback) {

        var options = {
          host: info.ip,
          port: 8336,
          path: '/local_stats',
          method: 'GET'
        };

        self.request(options, callback);
    }

    function digest_global_stats(info, callback) {

        var options = {
          host: info.ip,
          port: 8336,
          path: '/global_stats',
          method: 'GET'
        };

        self.request(options, callback);
    }

    // make http request to the target node ip
    self.request = function(options, callback, is_plain)
    {
        http_handler = http;
        var req = http_handler.request(options, function(res) {
            res.setEncoding('utf8');
            var result = '';
            res.on('data', function (data) {
                result += data;
            });

            res.on('end', function () {
                if(options.plain)
                    callback(null, result);
                else {
                    try {
                        var o = JSON.parse(result);
                        callback(null, o);
                    } catch(ex) {
                        console.error(ex);
                        callback(ex);
                    }
                }
            });
        });

        req.on('socket', function (socket) {
            socket.setTimeout(config.http_socket_timeout);
            socket.on('timeout', function() {
                req.abort();
            });
        });

        req.on('error', function(e) {
            callback(e);
        });

        req.end();
    }

    if(upload && process.platform != 'win32') {

        function do_upload() {

            if(upload.ftp) {
                var ftp = upload.ftp;
                if(!ftp.address || !ftp.username || !ftp.password)
                    return console.error("upload.cfg ftp configuration must contain target address, username and password");
                var cmd = "curl -T "+config.flush_filename+" "+ftp.address+" --user "+ftp.address+":"+ftp.password;
                exec(cmd, function(error){ if(error) console.error(error); });
            }

            if(upload.scp) {
                var scp = upload.scp;
                if(!scp.address)
                    return console.error("upload.cfg scp configuration must contain target address");
                var cmd = "scp -q ./"+config.flush_filename+" "+scp.address;
                exec(cmd, function(error){ if(error) console.error(error); });
            }

            dpc(config.upload_interval, do_upload);
        }

        dpc(5000, do_upload);
    }
    else
        console.log("upload.cfg not found, rendering available only on the local interface");
}

GLOBAL.scanner = new Scanner();

//
<|endoftext|>