<commit_before>var _ = require('underscore'),
    Backbone = require('backbone'),
    async = require('async'),
    ModelStore = require('./store/model_store'),
    CollectionStore = require('./store/collection_store'),
    isServer = (typeof window === 'undefined');

if (!isServer) {
  Backbone.$ = window.$ || require('jquery');
}

module.exports = Fetcher;

function Fetcher(options) {
  this.options = options;
  this.app = this.options.app;
  this.modelUtils = this.app.modelUtils;
  this.modelStore = new ModelStore({
    app: this.app,
    modelUtils: this.modelUtils
  });
  this.collectionStore = new CollectionStore({
    app: this.app,
    modelUtils: this.modelUtils
  });
}

Fetcher.prototype.buildOptions = function(additionalOptions, params) {
  var options = {app: this.app};
  _.defaults(options, additionalOptions);
  _.defaults(options, params);
  return options;
};

/**
 * Returns an instance of Model or Collection.
 */
Fetcher.prototype.getModelOrCollectionForSpec = function(spec, attrsOrModels, options, callback) {
  if (spec.model) {
    return this.getModelForSpec(spec, attrsOrModels, options, callback);
  } else {
    return this.getCollectionForSpec(spec, attrsOrModels, options, callback);
  }
};

/**
 * Returns an instance of Collection.
 */
Fetcher.prototype.getCollectionForSpec = function(spec, models, options, callback) {
  var collectionOptions = this.buildOptions(options, spec.params);
  models = models || [];
  return this.modelUtils.getCollection(spec.collection, models, collectionOptions, callback);
};

/**
 * Returns an instance of Model.
 */
Fetcher.prototype.getModelForSpec = function(spec, attributes, options, callback) {
  var modelOptions = this.buildOptions(options);

  attributes = attributes || {};
  _.defaults(attributes, spec.params);

  return this.modelUtils.getModel(spec.model, attributes, modelOptions, callback);
};

/**
 * Used to hold timestamps of when 'checkFresh()' was called on a model/collection.
 * We use this to throttle it in 'shouldCheckFresh()'.
 */
Fetcher.prototype.checkedFreshTimestamps = {};

/**
 * Only once every ten seconds. Smarter?
 */
Fetcher.prototype.checkedFreshRate = 10000;

Fetcher.prototype.shouldCheckFresh = function(spec) {
  var key = this.checkedFreshKey(spec),
      timestamp = this.checkedFreshTimestamps[key];
  if (!timestamp) {
    return true;
  }
  if (new Date().getTime() - timestamp > this.checkedFreshRate) {
    return true;
  }
  return false;
};

Fetcher.prototype.didCheckFresh = function(spec) {
  var key = this.checkedFreshKey(spec);
  this.checkedFreshTimestamps[key] = new Date().getTime();
};

Fetcher.prototype.checkedFreshKey = function(spec) {
  var meta;
  meta = {
    name: spec.model || spec.collection,
    params: spec.params
  };
  return JSON.stringify(meta);
};

/**
 * map fetchSpecs to models and fetch data in parallel
 */
Fetcher.prototype._retrieve = function(fetchSpecs, options, callback) {
  var batchedRequests = {};

  _.each(fetchSpecs, function(spec, name) {
    batchedRequests[name] = function(cb) {
      var modelData, modelOptions;

      if (!options.readFromCache) {
        this.fetchFromApi(spec, cb);
      } else {
        modelData = null;
        modelOptions = {};

        // First, see if we have stored the model or collection.
        if (spec.model != null) {

          this._retrieveModel(spec, function(err, modelData) {
            this._retrieveModelData(spec, modelData, modelOptions, cb);
          }.bind(this));

        } else if (spec.collection != null) {

          this.collectionStore.get(spec.collection, spec.params, function(collection) {
            if (collection) {
              modelData = this.retrieveModelsForCollectionName(spec.collection, _.pluck(collection.models, 'id'));
              modelOptions = {
                meta: collection.meta,
                params: collection.params
              };
            }
            this._retrieveModelData(spec, modelData, modelOptions, cb);
          }.bind(this));

        }

      }
    }.bind(this);
  }, this);
  async.parallel(batchedRequests, callback);
};

Fetcher.prototype._retrieveModelData = function(spec, modelData, modelOptions, cb) {

  // If we found the model/collection in the store, then return that.
  if (!this.needsFetch(modelData, spec)) {
    model = this.getModelOrCollectionForSpec(spec, modelData, modelOptions);

    /**
     * If 'checkFresh' is set (and we're in the client), then before we
     * return the cached object we fire off a fetch, compare the results,
     * and if the data is different, we trigger a 'refresh' event.
     */
    if (spec.checkFresh && !isServer && this.shouldCheckFresh(spec)) {
      model.checkFresh();
      this.didCheckFresh(spec);
    }
    cb(null, model);
  } else {
    /**
     * Else, fetch anew.
     */
    this.fetchFromApi(spec, cb);
  }
}

Fetcher.prototype._retrieveModel = function(spec, callback) {
  var fetcher = this;

  // Attempt to fetch from the modelStore based on the idAttribute
  this.modelUtils.modelIdAttribute(spec.model, function(idAttribute) {
    var modelData = fetcher.modelStore.get(spec.model, spec.params[idAttribute]);
    if (modelData)
      return callback(null, modelData);

    // if there are no other keys than the id in the params, return null;
    if (_.isEmpty(_.omit(spec.params, idAttribute)))
      return callback(null, null);

    // Attempt to fetch the model in the modelStore based on the other params
    return callback(null, fetcher.modelStore.find(spec.model, spec.params));
  });
};

Fetcher.prototype.needsFetch = function(modelData, spec) {
  if (modelData == null) return true;
  if (this.isMissingKeys(modelData, spec.ensureKeys)) return true;
  if (spec.needsFetch === true) return true;
  if (typeof spec.needsFetch === 'function' && spec.needsFetch(modelData)) return true;
  return false;
};

Fetcher.prototype.isMissingKeys = function(modelData, keys) {
  var key;

  if (keys == null) {
    return false;
  }
  if (!_.isArray(keys)) {
    keys = [keys];
  }
  for (var i = 0, len = keys.length; i < len; i++) {
    key = keys[i];
    if (modelData[key] == null) {
      return true;
    }
  }
  return false;
};

Fetcher.prototype.fetchFromApi = function(spec, callback) {
  var model = this.getModelOrCollectionForSpec(spec),
      fetcher = this;
  model.fetch({
    data: spec.params,
    success: function(model, body) {
      callback(null, model);
    },
    error: function(model, resp, options) {
      var body, respOutput, err;

      body = resp.body;
      resp.body = typeof body === 'string' ? body.slice(0, 150) : body;
      respOutput = JSON.stringify(resp);
      err = new Error("ERROR fetching model '" + fetcher.modelUtils.modelName(model.constructor) + "' with options '" + JSON.stringify(options) + "'. Response: " + respOutput);
      err.status = resp.status;
      err.body = body;
      callback(err);
    }
  });
};

Fetcher.prototype.retrieveModelsForCollectionName = function(collectionName, modelIds) {
  var modelName = this.modelUtils.getModelNameForCollectionName(collectionName);
  return this.retrieveModels(modelName, modelIds);
};

Fetcher.prototype.retrieveModels = function(modelName, modelIds) {
  return modelIds.map(function(id) {
    return this.modelStore.get(modelName, id);
  }, this);
};

Fetcher.prototype.summarize = function(modelOrCollection) {
  var summary = {},
      idAttribute;

  if (this.modelUtils.isCollection(modelOrCollection)) {
    idAttribute = modelOrCollection.model.prototype.idAttribute;
    summary = {
      collection: this.modelUtils.modelName(modelOrCollection.constructor),
      ids: modelOrCollection.pluck(idAttribute),
      params: modelOrCollection.params,
      meta: modelOrCollection.meta
    };
  } else if (this.modelUtils.isModel(modelOrCollection)) {
    idAttribute = modelOrCollection.idAttribute;
    summary = {
      model: this.modelUtils.modelName(modelOrCollection.constructor),
      id: modelOrCollection.get(idAttribute)
    };
  }
  return summary;
};

Fetcher.prototype.storeResults = function(results) {
  _.each(results, function(modelOrCollection) {
    modelOrCollection.store();
  });
};

Fetcher.prototype.bootstrapData = function(modelMap) {
  var results = {},
      fetcher = this;

  async.forEach(_.keys(modelMap), function(name, cb) {
    var map = modelMap[name];
    fetcher.getModelOrCollectionForSpec(map.summary, map.data, _.pick(map.summary, 'params', 'meta'), function(modelOrCollection) {
      results[name] = modelOrCollection;
      cb(null);
    });
  }, function(err) {
    fetcher.storeResults(results);
  });
};

Fetcher.prototype.hydrate = function(summaries, options, callback) {
  var results = {},
      fetcher = this;

  /**
   * Support both (summaries, options, callback)
   * and (summaries, callback).
   */
  if (arguments.length === 2) {
    callback = options;
    options = {};
  } else {
    options = options || {};
  }

  async.forEach(_.keys(summaries), function(name, cb) {
    var summary = summaries[name];
    if (summary.model != null) {
      results[name] = fetcher.modelStore.get(summary.model, summary.id, true);

      if ((results[name] != null) && (options.app != null)) {
        results[name].app = options.app;
      }

      cb(null);

    } else if (summary.collection != null) {
      // Also support getting all models for a collection.
      fetcher.collectionStore.get(summary.collection, summary.params, function(collection) {
        if (collection == null) {
          throw new Error("Collection of type \"" + summary.collection + "\" not found for params: " + JSON.stringify(summary.params));
        }

        results[name] = collection;
      });
    }
  }, function(err) {
    callback(err, results);
  });
};

Fetcher.prototype.pendingFetches = 0;

Fetcher.prototype.fetch = function(fetchSpecs, options, callback) {
  var fetcher = this;

  /**
   * Support both (fetchSpecs, options, callback)
   * and (fetchSpecs, callback).
   */
  if (arguments.length === 2) {
    callback = options;
    options = {};
  } else {
    options = options || {};
  }

  // Different defaults for client v server.
  if (isServer) {
    if (options.readFromCache == null) {
      options.readFromCache = false;
    }
    if (options.writeToCache == null) {
      options.writeToCache = false;
    }
  } else {
    if (options.readFromCache == null) {
      options.readFromCache = true;
    }
    if (options.writeToCache == null) {
      options.writeToCache = true;
    }
  }

  this.pendingFetches++;
  this.trigger('fetch:start', fetchSpecs);
  this._retrieve(fetchSpecs, options, function(err, results) {
    fetcher.pendingFetches--;
    fetcher.trigger('fetch:end', fetchSpecs, err, results);
    if (err) return callback(err);
    if (options.writeToCache) {
      fetcher.storeResults(results);
    }
    callback(null, results);
  });
};

// Mixin Backbone.Events for events that work in client & server.
_.extend(Fetcher.prototype, Backbone.Events);
<commit_msg>add missing callback<commit_after>var _ = require('underscore'),
    Backbone = require('backbone'),
    async = require('async'),
    ModelStore = require('./store/model_store'),
    CollectionStore = require('./store/collection_store'),
    isServer = (typeof window === 'undefined');

if (!isServer) {
  Backbone.$ = window.$ || require('jquery');
}

module.exports = Fetcher;

function Fetcher(options) {
  this.options = options;
  this.app = this.options.app;
  this.modelUtils = this.app.modelUtils;
  this.modelStore = new ModelStore({
    app: this.app,
    modelUtils: this.modelUtils
  });
  this.collectionStore = new CollectionStore({
    app: this.app,
    modelUtils: this.modelUtils
  });
}

Fetcher.prototype.buildOptions = function(additionalOptions, params) {
  var options = {app: this.app};
  _.defaults(options, additionalOptions);
  _.defaults(options, params);
  return options;
};

/**
 * Returns an instance of Model or Collection.
 */
Fetcher.prototype.getModelOrCollectionForSpec = function(spec, attrsOrModels, options, callback) {
  if (spec.model) {
    return this.getModelForSpec(spec, attrsOrModels, options, callback);
  } else {
    return this.getCollectionForSpec(spec, attrsOrModels, options, callback);
  }
};

/**
 * Returns an instance of Collection.
 */
Fetcher.prototype.getCollectionForSpec = function(spec, models, options, callback) {
  var collectionOptions = this.buildOptions(options, spec.params);
  models = models || [];
  return this.modelUtils.getCollection(spec.collection, models, collectionOptions, callback);
};

/**
 * Returns an instance of Model.
 */
Fetcher.prototype.getModelForSpec = function(spec, attributes, options, callback) {
  var modelOptions = this.buildOptions(options);

  attributes = attributes || {};
  _.defaults(attributes, spec.params);

  return this.modelUtils.getModel(spec.model, attributes, modelOptions, callback);
};

/**
 * Used to hold timestamps of when 'checkFresh()' was called on a model/collection.
 * We use this to throttle it in 'shouldCheckFresh()'.
 */
Fetcher.prototype.checkedFreshTimestamps = {};

/**
 * Only once every ten seconds. Smarter?
 */
Fetcher.prototype.checkedFreshRate = 10000;

Fetcher.prototype.shouldCheckFresh = function(spec) {
  var key = this.checkedFreshKey(spec),
      timestamp = this.checkedFreshTimestamps[key];
  if (!timestamp) {
    return true;
  }
  if (new Date().getTime() - timestamp > this.checkedFreshRate) {
    return true;
  }
  return false;
};

Fetcher.prototype.didCheckFresh = function(spec) {
  var key = this.checkedFreshKey(spec);
  this.checkedFreshTimestamps[key] = new Date().getTime();
};

Fetcher.prototype.checkedFreshKey = function(spec) {
  var meta;
  meta = {
    name: spec.model || spec.collection,
    params: spec.params
  };
  return JSON.stringify(meta);
};

/**
 * map fetchSpecs to models and fetch data in parallel
 */
Fetcher.prototype._retrieve = function(fetchSpecs, options, callback) {
  var batchedRequests = {};

  _.each(fetchSpecs, function(spec, name) {
    batchedRequests[name] = function(cb) {
      var modelData, modelOptions;

      if (!options.readFromCache) {
        this.fetchFromApi(spec, cb);
      } else {
        modelData = null;
        modelOptions = {};

        // First, see if we have stored the model or collection.
        if (spec.model != null) {

          this._retrieveModel(spec, function(err, modelData) {
            this._retrieveModelData(spec, modelData, modelOptions, cb);
          }.bind(this));

        } else if (spec.collection != null) {

          this.collectionStore.get(spec.collection, spec.params, function(collection) {
            if (collection) {
              modelData = this.retrieveModelsForCollectionName(spec.collection, _.pluck(collection.models, 'id'));
              modelOptions = {
                meta: collection.meta,
                params: collection.params
              };
            }
            this._retrieveModelData(spec, modelData, modelOptions, cb);
          }.bind(this));

        }

      }
    }.bind(this);
  }, this);
  async.parallel(batchedRequests, callback);
};

Fetcher.prototype._retrieveModelData = function(spec, modelData, modelOptions, cb) {

  // If we found the model/collection in the store, then return that.
  if (!this.needsFetch(modelData, spec)) {
    model = this.getModelOrCollectionForSpec(spec, modelData, modelOptions);

    /**
     * If 'checkFresh' is set (and we're in the client), then before we
     * return the cached object we fire off a fetch, compare the results,
     * and if the data is different, we trigger a 'refresh' event.
     */
    if (spec.checkFresh && !isServer && this.shouldCheckFresh(spec)) {
      model.checkFresh();
      this.didCheckFresh(spec);
    }
    cb(null, model);
  } else {
    /**
     * Else, fetch anew.
     */
    this.fetchFromApi(spec, cb);
  }
}

Fetcher.prototype._retrieveModel = function(spec, callback) {
  var fetcher = this;

  // Attempt to fetch from the modelStore based on the idAttribute
  this.modelUtils.modelIdAttribute(spec.model, function(idAttribute) {
    var modelData = fetcher.modelStore.get(spec.model, spec.params[idAttribute]);
    if (modelData)
      return callback(null, modelData);

    // if there are no other keys than the id in the params, return null;
    if (_.isEmpty(_.omit(spec.params, idAttribute)))
      return callback(null, null);

    // Attempt to fetch the model in the modelStore based on the other params
    return callback(null, fetcher.modelStore.find(spec.model, spec.params));
  });
};

Fetcher.prototype.needsFetch = function(modelData, spec) {
  if (modelData == null) return true;
  if (this.isMissingKeys(modelData, spec.ensureKeys)) return true;
  if (spec.needsFetch === true) return true;
  if (typeof spec.needsFetch === 'function' && spec.needsFetch(modelData)) return true;
  return false;
};

Fetcher.prototype.isMissingKeys = function(modelData, keys) {
  var key;

  if (keys == null) {
    return false;
  }
  if (!_.isArray(keys)) {
    keys = [keys];
  }
  for (var i = 0, len = keys.length; i < len; i++) {
    key = keys[i];
    if (modelData[key] == null) {
      return true;
    }
  }
  return false;
};

Fetcher.prototype.fetchFromApi = function(spec, callback) {
  var model = this.getModelOrCollectionForSpec(spec),
      fetcher = this;
  model.fetch({
    data: spec.params,
    success: function(model, body) {
      callback(null, model);
    },
    error: function(model, resp, options) {
      var body, respOutput, err;

      body = resp.body;
      resp.body = typeof body === 'string' ? body.slice(0, 150) : body;
      respOutput = JSON.stringify(resp);
      err = new Error("ERROR fetching model '" + fetcher.modelUtils.modelName(model.constructor) + "' with options '" + JSON.stringify(options) + "'. Response: " + respOutput);
      err.status = resp.status;
      err.body = body;
      callback(err);
    }
  });
};

Fetcher.prototype.retrieveModelsForCollectionName = function(collectionName, modelIds) {
  var modelName = this.modelUtils.getModelNameForCollectionName(collectionName);
  return this.retrieveModels(modelName, modelIds);
};

Fetcher.prototype.retrieveModels = function(modelName, modelIds) {
  return modelIds.map(function(id) {
    return this.modelStore.get(modelName, id);
  }, this);
};

Fetcher.prototype.summarize = function(modelOrCollection) {
  var summary = {},
      idAttribute;

  if (this.modelUtils.isCollection(modelOrCollection)) {
    idAttribute = modelOrCollection.model.prototype.idAttribute;
    summary = {
      collection: this.modelUtils.modelName(modelOrCollection.constructor),
      ids: modelOrCollection.pluck(idAttribute),
      params: modelOrCollection.params,
      meta: modelOrCollection.meta
    };
  } else if (this.modelUtils.isModel(modelOrCollection)) {
    idAttribute = modelOrCollection.idAttribute;
    summary = {
      model: this.modelUtils.modelName(modelOrCollection.constructor),
      id: modelOrCollection.get(idAttribute)
    };
  }
  return summary;
};

Fetcher.prototype.storeResults = function(results) {
  _.each(results, function(modelOrCollection) {
    modelOrCollection.store();
  });
};

Fetcher.prototype.bootstrapData = function(modelMap) {
  var results = {},
      fetcher = this;

  async.forEach(_.keys(modelMap), function(name, cb) {
    var map = modelMap[name];
    fetcher.getModelOrCollectionForSpec(map.summary, map.data, _.pick(map.summary, 'params', 'meta'), function(modelOrCollection) {
      results[name] = modelOrCollection;
      cb(null);
    });
  }, function(err) {
    fetcher.storeResults(results);
  });
};

Fetcher.prototype.hydrate = function(summaries, options, callback) {
  var results = {},
      fetcher = this;

  /**
   * Support both (summaries, options, callback)
   * and (summaries, callback).
   */
  if (arguments.length === 2) {
    callback = options;
    options = {};
  } else {
    options = options || {};
  }

  async.forEach(_.keys(summaries), function(name, cb) {
    var summary = summaries[name];
    if (summary.model != null) {
      results[name] = fetcher.modelStore.get(summary.model, summary.id, true);

      if ((results[name] != null) && (options.app != null)) {
        results[name].app = options.app;
      }

      cb(null);

    } else if (summary.collection != null) {
      // Also support getting all models for a collection.
      fetcher.collectionStore.get(summary.collection, summary.params, function(collection) {
        if (collection == null) {
          throw new Error("Collection of type \"" + summary.collection + "\" not found for params: " + JSON.stringify(summary.params));
        }

        results[name] = collection;

        cb(null);
      });
    }
  }, function(err) {
    callback(err, results);
  });
};

Fetcher.prototype.pendingFetches = 0;

Fetcher.prototype.fetch = function(fetchSpecs, options, callback) {
  var fetcher = this;

  /**
   * Support both (fetchSpecs, options, callback)
   * and (fetchSpecs, callback).
   */
  if (arguments.length === 2) {
    callback = options;
    options = {};
  } else {
    options = options || {};
  }

  // Different defaults for client v server.
  if (isServer) {
    if (options.readFromCache == null) {
      options.readFromCache = false;
    }
    if (options.writeToCache == null) {
      options.writeToCache = false;
    }
  } else {
    if (options.readFromCache == null) {
      options.readFromCache = true;
    }
    if (options.writeToCache == null) {
      options.writeToCache = true;
    }
  }

  this.pendingFetches++;
  this.trigger('fetch:start', fetchSpecs);
  this._retrieve(fetchSpecs, options, function(err, results) {
    fetcher.pendingFetches--;
    fetcher.trigger('fetch:end', fetchSpecs, err, results);
    if (err) return callback(err);
    if (options.writeToCache) {
      fetcher.storeResults(results);
    }
    callback(null, results);
  });
};

// Mixin Backbone.Events for events that work in client & server.
_.extend(Fetcher.prototype, Backbone.Events);
<|endoftext|>


<commit_before>var _ = require('underscore');
var async = require('async');

var R = 6378;
var EQUATOR_DEGREE_KM = (2 * Math.PI * R) / 360;

/**
 * Возвращает уникальные и объединенные боксы для lineString.
 *
 * 1. Расчитывает mainBox из Юго-Западной и Северо-Восточной точек трека.
 * 2. Расширяет mainBox на расстояние fat, чтобы гарантированно соблюдался fat во всех местах трека.
 * 3. Ищем subBoxes - разбиваем mainBox на множество subBoxes с шириной fat.
 * 4. Фильтруем subBoxes, оставляем только те, что пересекают трек. intersectBoxes.
 * 5. Добавляем соседние subBoxes, к каждому из intersectBoxes. necessaryBoxes.
 * 6. Объединяем боксы, чтобы вернуть их как можно меньше.
 *
 * */
function getBoxes(lineString, options, callback) {
    var coordinates = lineString.coordinates;
    var fat = options.fat;

    if (options.reverse) {
        coordinates = reverse(coordinates);
    }

    var mainBox = getMainBox(coordinates);
    extendByFat(mainBox, fat);
    var subBoxes = getSubBoxes(mainBox, fat);
    filterIntersectBoxes(subBoxes, coordinates, function(err, result) {
        var necessaryBoxes = getNecessaryBoxes(result, subBoxes);
        callback(null, mergeBoxes(necessaryBoxes));
    });
}

function getMainBox(coordinates) {
    var lats = _.map(coordinates, function(item) {return item[0]});
    var lons = _.map(coordinates, function(item) {return item[1]});

    var sw = [_.min(lats), _.min(lons)];
    var ne = [_.max(lats), _.max(lons)];

    return [sw, ne];
}

function extendByFat(box, fat) {
    var sw = box[0];
    var ne = box[1];
    var cosCurrentLat = cosd(sw[0]);
    var currentLatDegreeKm = cosCurrentLat * EQUATOR_DEGREE_KM;

    var fatHeightDegree = fat / EQUATOR_DEGREE_KM;
    var fatWidthDegree  = fat / currentLatDegreeKm;

    sw = [sw[0]-fatHeightDegree, sw[1]-fatWidthDegree];
    ne = [ne[0]+fatHeightDegree, ne[1]+fatWidthDegree];

    box[0] = sw;
    box[1] = ne;
}

function getSubBoxes(box, fat) {
    var sw = box[0];
    var ne = box[1];
    var boxWidth = ne[1] - sw[1];
    var boxHeight = ne[0] - sw[0];

    var cosCurrentLat = cosd(sw[0]);
    var currentLatDegreeKm = cosCurrentLat * EQUATOR_DEGREE_KM;

    var boxWidthKm = boxWidth * currentLatDegreeKm;
    var boxHeightKm = boxHeight * EQUATOR_DEGREE_KM;

    var colAmount = Math.floor(boxWidthKm / fat) + 1;
    var rowAmount = Math.floor(boxHeightKm / fat) + 1;

    var subBoxHeight = fat / EQUATOR_DEGREE_KM;
    var subBoxWidth  = fat / currentLatDegreeKm;

    return _.map(_.range(colAmount), function(colIndex) {
        return _.map(_.range(rowAmount), function(rowIndex) {
            var swLat = sw[0];
            var swLon = sw[1];

            var verticalOffset = subBoxHeight * rowIndex;
            var horisontalOffset = subBoxWidth * colIndex;

            var subSwLat = swLat + verticalOffset;
            var subSwLon = swLon + horisontalOffset;
            var subNeLat = swLat + verticalOffset + subBoxHeight;
            var subNeLon = swLon + horisontalOffset + subBoxWidth;

            var subSw = [subSwLat, subSwLon];
            var subNe = [subNeLat, subNeLon];
            return [subSw, subNe];

        });
    });
}

function filterIntersectBoxes(boxes, coordinates, callback) {
    return async.map(boxes,

        function(oneLineSubBoxes, cb) {
            var result = _.filter(oneLineSubBoxes, function(box) {
                return isIntersectOneBox(box, coordinates);
            });

            cb(null, result);
        },

        function(err, result) {
            callback(null, result)
        }
    );
}

function isIntersectOneBox(box, coordinates) {
    var boxSides = [];
    var lineStringSides = [];

    var sw = box[0];
    var ne = box[1];
    var nw = [sw[0], ne[1]];
    var se = [ne[0], sw[1]];

    var boxVertexesList = [sw, nw, ne, se];

    for (var i=0; i<boxVertexesList.length; i++) {
        var side = [boxVertexesList[i], boxVertexesList[i+1] || boxVertexesList[0]];
        boxSides.push(side);
    }

    for (var i=0; i<coordinates.length - 1; i++) {
        var side = [coordinates[i], coordinates[i+1]];
        lineStringSides.push(side);
    }

    for (var j=0; j<lineStringSides.length; j++) {
        for (var k=0; k<boxSides.length; k++) {
            if (isIntersectOneSide(boxSides[k], lineStringSides[j])) {
                return true;
            }
        }
    }
}

function isIntersectOneSide(boxSide, lineStringSide) {
    var ax1 = boxSide[0][0];
    var ay1 = boxSide[0][1];
    var ax2 = boxSide[1][0];
    var ay2 = boxSide[1][1];

    var bx1 = lineStringSide[0][0];
    var by1 = lineStringSide[0][1];
    var bx2 = lineStringSide[1][0];
    var by2 = lineStringSide[1][1];

    var v1 = (bx2-bx1)*(ay1-by1)-(by2-by1)*(ax1-bx1);
    var v2 = (bx2-bx1)*(ay2-by1)-(by2-by1)*(ax2-bx1);
    var v3 = (ax2-ax1)*(by1-ay1)-(ay2-ay1)*(bx1-ax1);
    var v4 = (ax2-ax1)*(by2-ay1)-(ay2-ay1)*(bx2-ax1);

    if ((v1*v2<0) && (v3*v4<0)) return true;
}

function getNecessaryBoxes(intersectBoxes, subBoxes) {
    var necessaryBoxes = [];

    for (var i=0; i<intersectBoxes.length; i++) {
        var oneLineIntersectBoxes = intersectBoxes[i];

        var line = subBoxes[i];

        for (var k=0; k<oneLineIntersectBoxes.length; k++) {
            var oneIntersectBox = oneLineIntersectBoxes[k];
            var index = [i, line.indexOf(oneIntersectBox)]; // [1,0] строка и колонка в матрице

            necessaryBoxes.push(oneIntersectBox);
            pushSiblingsByIndex(subBoxes, index, necessaryBoxes);
        }
    }

    return _.uniq(necessaryBoxes);
}

function pushSiblingsByIndex(subBoxes, index, necessaryBoxes) {
    var nbox = getOneByIndex(subBoxes, [index[0]+1, index[1]]);
    var nebox = getOneByIndex(subBoxes, [index[0]+1, index[1]+1]);
    var ebox = getOneByIndex(subBoxes, [index[0], index[1]+1]);
    var sebox = getOneByIndex(subBoxes, [index[0]-1, index[1]+1]);
    var sbox = getOneByIndex(subBoxes, [index[0]-1, index[1]]);
    var swbox = getOneByIndex(subBoxes, [index[0]-1, index[1]-1]);
    var wbox = getOneByIndex(subBoxes, [index[0], index[1]-1]);
    var nwbox = getOneByIndex(subBoxes, [index[0]+1, index[1]-1]);

    if (nbox) necessaryBoxes.push(nbox);
    if (nebox) necessaryBoxes.push(nebox);
    if (ebox) necessaryBoxes.push(ebox);
    if (sebox) necessaryBoxes.push(sebox);
    if (sbox) necessaryBoxes.push(sbox);
    if (swbox) necessaryBoxes.push(swbox);
    if (wbox) necessaryBoxes.push(wbox);
    if (nwbox) necessaryBoxes.push(nwbox);
}

function getOneByIndex(subBoxes, index) {
    var line = subBoxes[index[0]];
    if (!line) return false;
    var box = line[index[1]];
    if (!box) return false;
    return box;
}

function mergeBoxes(necessaryBoxes) {
    return verticalMergeBoxes(necessaryBoxes);
}

function verticalMergeBoxes(boxes) {
    var groupBoxes = {};

    boxes.forEach(function(box) {
        var swLng = box[0][1];

        if (!groupBoxes[swLng]) groupBoxes[swLng] = [];
        groupBoxes[swLng].push(box);
    });

    var keys = Object.keys(groupBoxes);
    var mergedBoxes = [];

    keys.forEach(function(key) {
        var oneGroup = sortBySwLon(groupBoxes[key]);
        var mergedOneGroup = mergeOneVerticalLine(oneGroup);

        mergedOneGroup.forEach(function(box) {
            mergedBoxes.push(box);
        });
    });

    return mergedBoxes;
}

function mergeOneVerticalLine(oneGroup) {
    var subLineList = [];
    var subLine = [];

    // Создает подстрочки из соседних (непрерывных) боксов. После forEach
    // subLineList имеет вид: [ [box, box, box], [subLine], [subLine] ].
    oneGroup.forEach(function(box) {
        if (!subLine.length) {
            subLine.push(box);
        }

        else if ( isVerticalSibling(_.last(subLine), box) ) {
            subLine.push(box);

        } else {
            subLineList.push(subLine);
            subLine = [];
            subLine.push(box);
        }
    });
    subLineList.push(subLine);

    return _.map(subLineList, function(subLine) {
        return _merge(subLine);
    });
}

function isVerticalSibling(box1, box2) {
    var box1NwLat = box1[1][0];
    var box2SwLat = box2[0][0];
    var diff = Math.abs(box1NwLat - box2SwLat);
    if (diff < 0.001) return true;
}

function sortBySwLon(boxes) {
    return _.sortBy(boxes, function(box) {
        return box[0][0];
    });
}

function _merge(oneGroup) {
    var lats = [];
    var lons = [];

    oneGroup.forEach(function(box) {
        lats.push( box[0][0], box[1][0] );
        lons.push( box[0][1], box[1][1] );
    });

    var maxLat = Math.max.apply(Math, lats);
    var maxLon = Math.max.apply(Math, lons);

    var minLat = Math.min.apply(Math, lats);
    var minLon = Math.min.apply(Math, lons);

    return [
        [minLat, minLon],  // sw
        [maxLat, maxLon]   // ne
    ];
}

function reverse(coordinates) {
    return _.map(coordinates, function(coords) {
        return [coords[1], coords[0]];
    });
}

function cosd(degree) {
    return Math.cos( toRadian(degree) );
}

function toRadian(num) {
    return num * Math.PI / 180;
}

module.exports = getBoxes;<commit_msg>Revert "refactor: самый тяжелый _.map -> async.map"<commit_after>var _ = require('underscore');
var async = require('async');

var R = 6378;
var EQUATOR_DEGREE_KM = (2 * Math.PI * R) / 360;

/**
 * Возвращает уникальные и объединенные боксы для lineString.
 *
 * 1. Расчитывает mainBox из Юго-Западной и Северо-Восточной точек трека.
 * 2. Расширяет mainBox на расстояние fat, чтобы гарантированно соблюдался fat во всех местах трека.
 * 3. Ищем subBoxes - разбиваем mainBox на множество subBoxes с шириной fat.
 * 4. Фильтруем subBoxes, оставляем только те, что пересекают трек. intersectBoxes.
 * 5. Добавляем соседние subBoxes, к каждому из intersectBoxes. necessaryBoxes.
 * 6. Объединяем боксы, чтобы вернуть их как можно меньше.
 *
 * */
function getBoxes(lineString, options, callback) {
    var coordinates = lineString.coordinates;
    var fat = options.fat;

    if (options.reverse) {
        coordinates = reverse(coordinates);
    }

    var mainBox = getMainBox(coordinates);
    extendByFat(mainBox, fat);
    var subBoxes = getSubBoxes(mainBox, fat);
    var intersectBoxes = filterIntersectBoxes(subBoxes, coordinates);
    var necessaryBoxes = getNecessaryBoxes(intersectBoxes, subBoxes);

    callback(null, mergeBoxes(necessaryBoxes))
}

function getMainBox(coordinates) {
    var lats = _.map(coordinates, function(item) {return item[0]});
    var lons = _.map(coordinates, function(item) {return item[1]});

    var sw = [_.min(lats), _.min(lons)];
    var ne = [_.max(lats), _.max(lons)];

    return [sw, ne];
}

function extendByFat(box, fat) {
    var sw = box[0];
    var ne = box[1];
    var cosCurrentLat = cosd(sw[0]);
    var currentLatDegreeKm = cosCurrentLat * EQUATOR_DEGREE_KM;

    var fatHeightDegree = fat / EQUATOR_DEGREE_KM;
    var fatWidthDegree  = fat / currentLatDegreeKm;

    sw = [sw[0]-fatHeightDegree, sw[1]-fatWidthDegree];
    ne = [ne[0]+fatHeightDegree, ne[1]+fatWidthDegree];

    box[0] = sw;
    box[1] = ne;
}

function getSubBoxes(box, fat) {
    var sw = box[0];
    var ne = box[1];
    var boxWidth = ne[1] - sw[1];
    var boxHeight = ne[0] - sw[0];

    var cosCurrentLat = cosd(sw[0]);
    var currentLatDegreeKm = cosCurrentLat * EQUATOR_DEGREE_KM;

    var boxWidthKm = boxWidth * currentLatDegreeKm;
    var boxHeightKm = boxHeight * EQUATOR_DEGREE_KM;

    var colAmount = Math.floor(boxWidthKm / fat) + 1;
    var rowAmount = Math.floor(boxHeightKm / fat) + 1;

    var subBoxHeight = fat / EQUATOR_DEGREE_KM;
    var subBoxWidth  = fat / currentLatDegreeKm;

    return _.map(_.range(colAmount), function(colIndex) {
        return _.map(_.range(rowAmount), function(rowIndex) {
            var swLat = sw[0];
            var swLon = sw[1];

            var verticalOffset = subBoxHeight * rowIndex;
            var horisontalOffset = subBoxWidth * colIndex;

            var subSwLat = swLat + verticalOffset;
            var subSwLon = swLon + horisontalOffset;
            var subNeLat = swLat + verticalOffset + subBoxHeight;
            var subNeLon = swLon + horisontalOffset + subBoxWidth;

            var subSw = [subSwLat, subSwLon];
            var subNe = [subNeLat, subNeLon];
            return [subSw, subNe];

        });
    });
}

function filterIntersectBoxes(boxes, coordinates) {
    return _.map(boxes, function(oneLineSubBoxes) {
        return _.filter(oneLineSubBoxes, function(box) {
            return isIntersectOneBox(box, coordinates);
        });
    });
}

function isIntersectOneBox(box, coordinates) {
    var boxSides = [];
    var lineStringSides = [];

    var sw = box[0];
    var ne = box[1];
    var nw = [sw[0], ne[1]];
    var se = [ne[0], sw[1]];

    var boxVertexesList = [sw, nw, ne, se];

    for (var i=0; i<boxVertexesList.length; i++) {
        var side = [boxVertexesList[i], boxVertexesList[i+1] || boxVertexesList[0]];
        boxSides.push(side);
    }

    for (var i=0; i<coordinates.length - 1; i++) {
        var side = [coordinates[i], coordinates[i+1]];
        lineStringSides.push(side);
    }

    for (var j=0; j<lineStringSides.length; j++) {
        for (var k=0; k<boxSides.length; k++) {
            if (isIntersectOneSide(boxSides[k], lineStringSides[j])) {
                return true;
            }
        }
    }
}

function isIntersectOneSide(boxSide, lineStringSide) {
    var ax1 = boxSide[0][0];
    var ay1 = boxSide[0][1];
    var ax2 = boxSide[1][0];
    var ay2 = boxSide[1][1];

    var bx1 = lineStringSide[0][0];
    var by1 = lineStringSide[0][1];
    var bx2 = lineStringSide[1][0];
    var by2 = lineStringSide[1][1];

    var v1 = (bx2-bx1)*(ay1-by1)-(by2-by1)*(ax1-bx1);
    var v2 = (bx2-bx1)*(ay2-by1)-(by2-by1)*(ax2-bx1);
    var v3 = (ax2-ax1)*(by1-ay1)-(ay2-ay1)*(bx1-ax1);
    var v4 = (ax2-ax1)*(by2-ay1)-(ay2-ay1)*(bx2-ax1);

    if ((v1*v2<0) && (v3*v4<0)) return true;
}

function getNecessaryBoxes(intersectBoxes, subBoxes) {
    var necessaryBoxes = [];

    for (var i=0; i<intersectBoxes.length; i++) {
        var oneLineIntersectBoxes = intersectBoxes[i];

        var line = subBoxes[i];

        for (var k=0; k<oneLineIntersectBoxes.length; k++) {
            var oneIntersectBox = oneLineIntersectBoxes[k];
            var index = [i, line.indexOf(oneIntersectBox)]; // [1,0] строка и колонка в матрице

            necessaryBoxes.push(oneIntersectBox);
            pushSiblingsByIndex(subBoxes, index, necessaryBoxes);
        }
    }

    return _.uniq(necessaryBoxes);
}

function pushSiblingsByIndex(subBoxes, index, necessaryBoxes) {
    var nbox = getOneByIndex(subBoxes, [index[0]+1, index[1]]);
    var nebox = getOneByIndex(subBoxes, [index[0]+1, index[1]+1]);
    var ebox = getOneByIndex(subBoxes, [index[0], index[1]+1]);
    var sebox = getOneByIndex(subBoxes, [index[0]-1, index[1]+1]);
    var sbox = getOneByIndex(subBoxes, [index[0]-1, index[1]]);
    var swbox = getOneByIndex(subBoxes, [index[0]-1, index[1]-1]);
    var wbox = getOneByIndex(subBoxes, [index[0], index[1]-1]);
    var nwbox = getOneByIndex(subBoxes, [index[0]+1, index[1]-1]);

    if (nbox) necessaryBoxes.push(nbox);
    if (nebox) necessaryBoxes.push(nebox);
    if (ebox) necessaryBoxes.push(ebox);
    if (sebox) necessaryBoxes.push(sebox);
    if (sbox) necessaryBoxes.push(sbox);
    if (swbox) necessaryBoxes.push(swbox);
    if (wbox) necessaryBoxes.push(wbox);
    if (nwbox) necessaryBoxes.push(nwbox);
}

function getOneByIndex(subBoxes, index) {
    var line = subBoxes[index[0]];
    if (!line) return false;
    var box = line[index[1]];
    if (!box) return false;
    return box;
}

function mergeBoxes(necessaryBoxes) {
    return verticalMergeBoxes(necessaryBoxes);
}

function verticalMergeBoxes(boxes) {
    var groupBoxes = {};

    boxes.forEach(function(box) {
        var swLng = box[0][1];

        if (!groupBoxes[swLng]) groupBoxes[swLng] = [];
        groupBoxes[swLng].push(box);
    });

    var keys = Object.keys(groupBoxes);
    var mergedBoxes = [];

    keys.forEach(function(key) {
        var oneGroup = sortBySwLon(groupBoxes[key]);
        var mergedOneGroup = mergeOneVerticalLine(oneGroup);

        mergedOneGroup.forEach(function(box) {
            mergedBoxes.push(box);
        });
    });

    return mergedBoxes;
}

function mergeOneVerticalLine(oneGroup) {
    var subLineList = [];
    var subLine = [];

    // Создает подстрочки из соседних (непрерывных) боксов. После forEach
    // subLineList имеет вид: [ [box, box, box], [subLine], [subLine] ].
    oneGroup.forEach(function(box) {
        if (!subLine.length) {
            subLine.push(box);
        }

        else if ( isVerticalSibling(_.last(subLine), box) ) {
            subLine.push(box);

        } else {
            subLineList.push(subLine);
            subLine = [];
            subLine.push(box);
        }
    });
    subLineList.push(subLine);

    return _.map(subLineList, function(subLine) {
        return _merge(subLine);
    });
}

function isVerticalSibling(box1, box2) {
    var box1NwLat = box1[1][0];
    var box2SwLat = box2[0][0];
    var diff = Math.abs(box1NwLat - box2SwLat);
    if (diff < 0.001) return true;
}

function sortBySwLon(boxes) {
    return _.sortBy(boxes, function(box) {
        return box[0][0];
    });
}

function _merge(oneGroup) {
    var lats = [];
    var lons = [];

    oneGroup.forEach(function(box) {
        lats.push( box[0][0], box[1][0] );
        lons.push( box[0][1], box[1][1] );
    });

    var maxLat = Math.max.apply(Math, lats);
    var maxLon = Math.max.apply(Math, lons);

    var minLat = Math.min.apply(Math, lats);
    var minLon = Math.min.apply(Math, lons);

    return [
        [minLat, minLon],  // sw
        [maxLat, maxLon]   // ne
    ];
}

function reverse(coordinates) {
    return _.map(coordinates, function(coords) {
        return [coords[1], coords[0]];
    });
}

function cosd(degree) {
    return Math.cos( toRadian(degree) );
}

function toRadian(num) {
    return num * Math.PI / 180;
}

module.exports = getBoxes;<|endoftext|>


<commit_before>"use babel";

import {CompositeDisposable} from 'atom';
import livereload from 'livereload';
import _ from 'lodash';

const DEFAULT_EXTS = 'html css js png gif jpg php php5 py rb erb coffee'.split(' ');
const DEFAULT_EXCLUSIONS = '.git/ .svn/ .hg/'.split(' ');

class LivereloadView extends HTMLDivElement {
  server = null;
  subscriptions = null;
  tooltipText = 'hello';

  initialize(state) {
    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    this.subscriptions = new CompositeDisposable();

    // add content
    this.innerHTML = '';
    this.firstChild.addEventListener('click', (event) => this.handleClick(event), false);

    this.classList.add('livereload-status', 'inline-block');
  }

  loadConfig() {
    let ret = {};

    // port number
    ret.port = atom.config.get('livereload.port');

    // use HTTPS
    ret.https = atom.config.get('livereload.useHTTPS') ? {} : null;

    // applyJSLive and applyCSSLive
    ret.applyJSLive = atom.config.get('livereload.applyJSLive');
    ret.applyCSSLive = atom.config.get('livereload.applyCSSLive');

    // exts
    let exts = atom.config.get('livereload.exts').split(',').map( ext => ext.trim() );
    exts = _.difference(exts, DEFAULT_EXTS);
    exts = _.uniq(exts);
    ret.exts = exts;

    let exclusions = atom.config.get('livereload.exclusions').split(',').map( ex => ex.trim() );
    exclusions = exclusions.concat(['.DS_Store', '.gitignore']);
    exclusions = _.difference(exclusions, DEFAULT_EXCLUSIONS);
    exclusions = _.uniq(exclusions);
    ret.exclusions = exclusions;

    return ret;
  }

  attach() {
    // Register command that toggles this view
    this.subscriptions.add(
      atom.commands.add( 'atom-workspace', { 'livereload:toggle': this.toggle.bind(this) } )
    );

    // tooltip
    this.subscriptions.add(
      atom.tooltips.add( this, {title: () => this.tooltipText} )
    );
  }

  detach() {
    this.subscriptions.dispose();
  }

  serialize() {

  }

  destroy() {
    try { this.detach() } catch(e){};

    this.subscriptions = null;
    this.remove();
  }

  toggle() {
    if (this.server) {
      this.closeServer();
    } else {
      this.startServer();
    }
  }

  handleClick(event) {
    event.preventDefault();
    if (this.firstChild.dataset.url) {
      atom.clipboard.write(this.firstChild.dataset.url, 'url');
    }
  }

  startServer() {
    this.firstChild.dataset.url = '';
    this.firstChild.textContent = 'LiveReload: ...';

    // load configurations
    let config = this.loadConfig();

    // create a server
    this.server = livereload.createServer(config);

    this.server.config.server
      .on('error', (err) => {
        if (err.code === 'EADDRINUSE') {
          this.tooltipText = `Trying port ${config.port+1}...`;
          console.log(`LiveReload: port ${config.port} already in use. Trying port ${config.port+1}...`);
          config.port++;

          try { this.server.close(); } catch(e) {};
          this.server = null;

          setTimeout( this.startServer.bind(this), 1000 );
        }
      })
      .on('listening', () => {
        console.log(`LiveReload: listening on port ${config.port}.`);

        this.firstChild.textContent = `LiveReload: ${config.port}`;
        this.tooltipText = 'Click to copy the URL to clipboard';
        this.firstChild.dataset.url = (config.useHTTPS ? 'https':'http') + `://localhost:${config.port}/livereload.js`;

        let paths = atom.project.getPaths();
        this.server.watch(paths);
      });
  }

  closeServer() {
    this.firstChild.textContent = 'LiveReload: Off';
    this.server.config.server.close();
    this.server = null;
  }
}

var LivereloadViewTag = document.registerElement('livereload-status-bar', {prototype:LivereloadView.prototype});

export default LivereloadViewTag;
<commit_msg>Fixed: livereload does not try another port when it fails<commit_after>"use babel";

import {CompositeDisposable} from 'atom';
import livereload from 'livereload';
import _ from 'lodash';

const DEFAULT_EXTS = 'html css js png gif jpg php php5 py rb erb coffee'.split(' ');
const DEFAULT_EXCLUSIONS = '.git/ .svn/ .hg/'.split(' ');

class LivereloadView extends HTMLDivElement {
  server = null;
  subscriptions = null;
  tooltipText = 'hello';

  initialize(state) {
    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    this.subscriptions = new CompositeDisposable();

    // add content
    this.innerHTML = '';
    this.firstChild.addEventListener('click', (event) => this.handleClick(event), false);

    this.classList.add('livereload-status', 'inline-block');
  }

  loadConfig() {
    let ret = {};

    // port number
    ret.port = atom.config.get('livereload.port');

    // use HTTPS
    ret.https = atom.config.get('livereload.useHTTPS') ? {} : null;

    // applyJSLive and applyCSSLive
    ret.applyJSLive = atom.config.get('livereload.applyJSLive');
    ret.applyCSSLive = atom.config.get('livereload.applyCSSLive');

    // exts
    let exts = atom.config.get('livereload.exts').split(',').map( ext => ext.trim() );
    exts = _.difference(exts, DEFAULT_EXTS);
    exts = _.uniq(exts);
    ret.exts = exts;

    let exclusions = atom.config.get('livereload.exclusions').split(',').map( ex => ex.trim() );
    exclusions = exclusions.concat(['.DS_Store', '.gitignore']);
    exclusions = _.difference(exclusions, DEFAULT_EXCLUSIONS);
    exclusions = _.uniq(exclusions);
    ret.exclusions = exclusions;

    return ret;
  }

  attach() {
    // Register command that toggles this view
    this.subscriptions.add(
      atom.commands.add( 'atom-workspace', { 'livereload:toggle': this.toggle.bind(this) } )
    );

    // tooltip
    this.subscriptions.add(
      atom.tooltips.add( this, {title: () => this.tooltipText} )
    );
  }

  detach() {
    this.subscriptions.dispose();
  }

  serialize() {

  }

  destroy() {
    try { this.detach() } catch(e){};

    this.subscriptions = null;
    this.remove();
  }

  toggle() {
    if (this.server) {
      this.closeServer();
    } else {
      this.startServer();
    }
  }

  handleClick(event) {
    event.preventDefault();
    if (this.firstChild.dataset.url) {
      atom.clipboard.write(this.firstChild.dataset.url, 'url');
    }
  }

  startServer(config) {
    this.firstChild.dataset.url = '';
    this.firstChild.textContent = 'LiveReload: ...';

    // load configurations
    if (!config) config = this.loadConfig();

    // create a server
    this.server = livereload.createServer(config);

    this.server.config.server
      .on('error', (err) => {
        if (err.code === 'EADDRINUSE') {
          this.tooltipText = `Trying port ${config.port+1}...`;
          console.log(`LiveReload: port ${config.port} already in use. Trying port ${config.port+1}...`);
          config.port++;

          try { this.server.close(); } catch(e) {};
          this.server = null;

          setTimeout( this.startServer.bind(this, config), 1000 );
        }
      })
      .on('listening', () => {
        console.log(`LiveReload: listening on port ${config.port}.`);

        this.firstChild.textContent = `LiveReload: ${config.port}`;
        this.tooltipText = 'Click to copy the URL to clipboard';
        this.firstChild.dataset.url = (config.useHTTPS ? 'https':'http') + `://localhost:${config.port}/livereload.js`;

        let paths = atom.project.getPaths();
        this.server.watch(paths);
      });
  }

  closeServer() {
    this.firstChild.textContent = 'LiveReload: Off';
    this.server.config.server.close();
    this.server = null;
  }
}

var LivereloadViewTag = document.registerElement('livereload-status-bar', {prototype:LivereloadView.prototype});

export default LivereloadViewTag;
<|endoftext|>


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

    /*jslint
        browser: true
    */
    /*global
        L, reqwest, Cowboy
    */

    var map = L.map('hugemap', {
            minZoom: 10
        }),
        tileURL = 'https://maps.bustimes.org/styles/klokantech-basic/{z}/{x}/{y}' + (L.Browser.retina ? '@2x' : '') + '.png',
        statusBar = L.control({
            position: 'bottomleft'
        }),
        lastVehiclesReq,
        lastStopsReq,
        timeout,
        oldVehicles = {},
        newVehicles = {},
        oldStops = {},
        newStops = {},
        stopsGroup = L.layerGroup(),
        vehiclesGroup = L.layerGroup(),
        highWater;

    stopsGroup.addTo(map);
    vehiclesGroup.addTo(map);

    map.attributionControl.setPrefix('');

    L.tileLayer(tileURL, {
        attribution: '© MapTiler © OpenStreetMap contributors'
    }).addTo(map);

    statusBar.onAdd = function () {
        var div = L.DomUtil.create('div', 'hugemap-status');
        return div;
    };
    statusBar.addTo(map);

    function getRotation(direction) {
        if (direction === null) {
            return '';
        }
        var rotation = 'transform: rotate(' + direction + 'deg)';
        return '-ms-' + rotation + ';-webkit-' + rotation + ';-moz-' + rotation + ';-o-' + rotation + ';' + rotation;
    }

    function getBusIcon(service, direction, livery, textColour) {
        if (direction === null) {
            var html = '';
        } else {
            html = '

';
            if (direction < 180) {
                direction -= 90;
            } else {
                direction -= 270;
            }
        }
        var className = 'bus';
        if (livery) {
            var style = 'background:' + livery;
            className += ' coloured';
            if (textColour) {
                className += ' white-text';
            }
            style += ';';
        } else {
            style = '';
        }
        style += getRotation(direction);
        html += '
';
        if (service) {
            html += service.line_name;
        }
        html += '
';
        return L.divIcon({
            iconSize: [20, 20],
            html: html,
            popupAnchor: [0, -5],
        });
    }

    function getStopIcon(indicator, bearing) {
        var className = 'leaflet-div-icon';
        if (indicator) {
            var indicatorParts = indicator.split(' ');
            var firstPart = indicatorParts[0].toLowerCase();
            if (indicatorParts.length === 2 && (firstPart === 'stop' || firstPart === 'bay' || firstPart === 'stand' || firstPart === 'stance')) {
                indicator = indicatorParts[1];
            } else {
                indicator = indicator.slice(0, 3);
            }
        } else {
            indicator = '';
        }
        indicator = '
' + indicator + '
';
        if (bearing !== null) {
            indicator += '

';
        }
        return L.divIcon({
            iconSize: [20, 20],
            html: indicator,
            popupAnchor: [0, -5],
            className: className
        });
    }

    function handleVehicle(data) {
        var props = data.properties;

        if (props.vehicle.url in oldVehicles) {
            var marker = oldVehicles[props.vehicle.url];
            newVehicles[props.vehicle.url] = marker;
            if (marker.datetime === props.datetime) {
                return;
            }
        }

        var icon = getBusIcon(props.service, props.direction, props.vehicle.livery, props.vehicle.text_colour),
            latLng = L.latLng(data.geometry.coordinates[1], data.geometry.coordinates[0]);

        if (marker) {
            marker.setLatLng(latLng);
            marker.setIcon(icon);
        } else {
            marker = L.marker(latLng, {
                icon: icon,
                zIndexOffset: 1000
            });
            marker.addTo(vehiclesGroup);
            newVehicles[props.vehicle.url] = marker;
        }
        marker.datetime = props.datetime;

        var popup = '';
        if (props.service) {
            if (props.service.url) {
                popup = '' + props.service.line_name + '';
            } else {
                popup = props.service.line_name;
            }
        }
        if (props.destination) {
            if (props.destination.indexOf(' to ') === -1) {
                popup += ' to ';
            }
            popup += props.destination;
        }

        if (props.operator) {
            if (popup) {
                popup += '
';
            }
            popup += props.operator + '
';
        }

        if (props.vehicle) {
            popup += '' + props.vehicle.name + '';
            if (props.vehicle.type) {
                popup += ' - ' + props.vehicle.type;
            }
            if (props.vehicle.notes) {
                popup += ' - ' + props.vehicle.notes;
            }
            popup += '
';
        }

        if (props.delta === 0) {
            popup += 'On time
';
        } else if (props.delta) {
            popup += 'About ';
            if (props.delta > 0) {
                popup += props.delta;
            } else {
                popup += props.delta * -1;
            }
            popup += ' minute';
            if (props.delta !== 1 && props.delta !== -1) {
                popup += 's';
            }
            if (props.delta > 0) {
                popup += ' early';
            } else {
                popup += ' late';
            }
            popup += '
';
        }

        var dateTime = new Date(props.datetime);
        popup += 'Updated at ' + dateTime.toTimeString().slice(0, 5);

        if ((props.source === 75 || props.source === 79 || props.source === 86) && props.service && props.service.url) {
            popup += '
(Updates if someone views
the ' + props.service.line_name + ' page)';
        }

        marker.bindPopup(popup);
    }

    function handleStop(data) {
        if (data.properties.url in oldStops) {
            var marker = oldStops[data.properties.url];
            newStops[data.properties.url] = marker;
            return;
        }

        var a = document.createElement('a');
        marker = L.marker(L.latLng(data.geometry.coordinates[1], data.geometry.coordinates[0]), {
            icon: getStopIcon(data.properties.indicator, data.properties.bearing)
        });

        a.innerHTML = '' + data.properties.name + '' + data.properties.services.join('') + '';
        a.href = data.properties.url;
        a.className = 'stop';

        marker.bindPopup(a.outerHTML, {
            autoPan: false
        });

        marker.addTo(stopsGroup);
        newStops[data.properties.url] = marker;
    }

    function processVehiclesData(data) {
        for (var i = data.features.length - 1; i >= 0; i -= 1) {
            handleVehicle(data.features[i]);
        }
        for (var vehicle in oldVehicles) {
            if (!(vehicle in newVehicles)) {
                vehiclesGroup.removeLayer(oldVehicles[vehicle]);
            }
        }
        oldVehicles = newVehicles;
        newVehicles = {};
        statusBar.getContainer().innerHTML = '';
    }

    function processStopsData(data) {
        for (var i = data.features.length - 1; i >= 0; i -= 1) {
            handleStop(data.features[i]);
        }
        for (var stop in oldStops) {
            if (!(stop in newStops)) {
                stopsGroup.removeLayer(oldStops[stop]);
            }
        }
        oldStops = newStops;
        newStops = {};
    }

    // load vehicles, and possibly stops
    function load(map, statusBar, stops) {
        statusBar.getContainer().innerHTML = 'Loading\u2026';
        var bounds = map.getBounds();
        var params = '?ymax=' + bounds.getNorth() + '&xmax=' + bounds.getEast() + '&ymin=' + bounds.getSouth() + '&xmin=' + bounds.getWest();

        if (lastVehiclesReq) {
            lastVehiclesReq.abort();
        }
        lastVehiclesReq = reqwest(
            '/vehicles.json' + params,
            function(data) {
                if (data) {
                    processVehiclesData(data);
                }
                timeout = setTimeout(function() {
                    load(map, statusBar, false);
                }, 10000);
            }
        );

        if (stops) {
            if (lastStopsReq) {
                lastStopsReq.abort();
            }
            if (map.getZoom() < 15) {
                stopsGroup.clearLayers();
                oldStops = {};
                highWater = null;
                return;
            }
            if (highWater && highWater.contains(bounds)) {
                return;
            }
            lastStopsReq = reqwest('/stops.json' + params, processStopsData);
            highWater = bounds;
        }
    }

    function handleMoveEnd(event) {
        clearTimeout(timeout);
        load(map, statusBar, true);

        var latLng = event.target.getCenter(),
            string = event.target.getZoom() + '/' + Math.round(latLng.lat * 10000) / 10000 + '/' + Math.round(latLng.lng * 10000) / 10000;

        if (history.replaceState) {
            history.replaceState(null, null, location.pathname + '#' + string);
        }
        if (window.localStorage) {
            localStorage.setItem('vehicleMap', string);
        }
    }

    map.on('moveend', Cowboy.debounce(500, handleMoveEnd));

    window.onhashchange = function(event) {
        var parts = event.target.location.hash.substr(1).split('/');

        if (parts.length == 3) {
            try {
                map.setView([parts[1], parts[2]], parts[0]);
            } catch (error) {
                // caught!
            }
        }
    };

    var parts;
    if (location.hash) {
        parts = location.hash.substring(1).split('/');
    } else if (localStorage.vehicleMap) {
        parts = localStorage.vehicleMap.split('/');
    }
    if (parts) {
        if (parts.length === 1) {
            parts = parts[0].split(',');
        }
        try {
            if (parts.length === 3) {
                map.setView([parts[1], parts[2]], parts[0]);
            } else {
                map.setView([parts[0], parts[1]], 15);
            }
        } catch (error) {
            // oh well
        }
    }
    if (!map._loaded) {
        map.setView([51.9, 0.9], 9);
    }

    function handleVisibilityChange(event) {
        if (event.target.hidden) {
            clearTimeout(timeout);
        } else {
            load(map, statusBar, false);
        }
    }

    if (document.addEventListener) {
        document.addEventListener('visibilitychange', handleVisibilityChange);
    }

    var locateButton = L.control();

    locateButton.onAdd = function(map) {
        var div = document.createElement('div');
        div.className = 'leaflet-bar';
        var a = document.createElement('a');
        a.href = '#';
        a.title = 'Find my location';
        a.setAttribute('role', 'button');
        var img = document.createElement('img');
        img.alt = 'Locate';
        img.width = 16;
        img.height = 16;
        img.src = '/static/locate.png';
        a.appendChild(img);
        div.appendChild(a);

        function locate() {
            img.className = 'working';
            map.locate({setView: true});
            return false;
        }

        function located() {
            img.className = '';
        }
        a.onclick = locate;

        map.on('locationfound', located);
        map.on('locationerror', located);

        return div;
    };

    locateButton.addTo(map);

    load(map, statusBar, true);
})();
<commit_msg>avoid errors when failing to load stops or vehicles on the big map. BUS-TIMES-JS-EC BUS-TIMES-JS-DT<commit_after>(function () {
    'use strict';

    /*jslint
        browser: true
    */
    /*global
        L, reqwest, Cowboy
    */

    var map = L.map('hugemap', {
            minZoom: 10
        }),
        tileURL = 'https://maps.bustimes.org/styles/klokantech-basic/{z}/{x}/{y}' + (L.Browser.retina ? '@2x' : '') + '.png',
        statusBar = L.control({
            position: 'bottomleft'
        }),
        lastVehiclesReq,
        lastStopsReq,
        timeout,
        oldVehicles = {},
        newVehicles = {},
        oldStops = {},
        newStops = {},
        stopsGroup = L.layerGroup(),
        vehiclesGroup = L.layerGroup(),
        highWater;

    stopsGroup.addTo(map);
    vehiclesGroup.addTo(map);

    map.attributionControl.setPrefix('');

    L.tileLayer(tileURL, {
        attribution: '© MapTiler © OpenStreetMap contributors'
    }).addTo(map);

    statusBar.onAdd = function () {
        var div = L.DomUtil.create('div', 'hugemap-status');
        return div;
    };
    statusBar.addTo(map);

    function getRotation(direction) {
        if (direction === null) {
            return '';
        }
        var rotation = 'transform: rotate(' + direction + 'deg)';
        return '-ms-' + rotation + ';-webkit-' + rotation + ';-moz-' + rotation + ';-o-' + rotation + ';' + rotation;
    }

    function getBusIcon(service, direction, livery, textColour) {
        if (direction === null) {
            var html = '';
        } else {
            html = '

';
            if (direction < 180) {
                direction -= 90;
            } else {
                direction -= 270;
            }
        }
        var className = 'bus';
        if (livery) {
            var style = 'background:' + livery;
            className += ' coloured';
            if (textColour) {
                className += ' white-text';
            }
            style += ';';
        } else {
            style = '';
        }
        style += getRotation(direction);
        html += '
';
        if (service) {
            html += service.line_name;
        }
        html += '
';
        return L.divIcon({
            iconSize: [20, 20],
            html: html,
            popupAnchor: [0, -5],
        });
    }

    function getStopIcon(indicator, bearing) {
        var className = 'leaflet-div-icon';
        if (indicator) {
            var indicatorParts = indicator.split(' ');
            var firstPart = indicatorParts[0].toLowerCase();
            if (indicatorParts.length === 2 && (firstPart === 'stop' || firstPart === 'bay' || firstPart === 'stand' || firstPart === 'stance')) {
                indicator = indicatorParts[1];
            } else {
                indicator = indicator.slice(0, 3);
            }
        } else {
            indicator = '';
        }
        indicator = '
' + indicator + '
';
        if (bearing !== null) {
            indicator += '

';
        }
        return L.divIcon({
            iconSize: [20, 20],
            html: indicator,
            popupAnchor: [0, -5],
            className: className
        });
    }

    function handleVehicle(data) {
        var props = data.properties;

        if (props.vehicle.url in oldVehicles) {
            var marker = oldVehicles[props.vehicle.url];
            newVehicles[props.vehicle.url] = marker;
            if (marker.datetime === props.datetime) {
                return;
            }
        }

        var icon = getBusIcon(props.service, props.direction, props.vehicle.livery, props.vehicle.text_colour),
            latLng = L.latLng(data.geometry.coordinates[1], data.geometry.coordinates[0]);

        if (marker) {
            marker.setLatLng(latLng);
            marker.setIcon(icon);
        } else {
            marker = L.marker(latLng, {
                icon: icon,
                zIndexOffset: 1000
            });
            marker.addTo(vehiclesGroup);
            newVehicles[props.vehicle.url] = marker;
        }
        marker.datetime = props.datetime;

        var popup = '';
        if (props.service) {
            if (props.service.url) {
                popup = '' + props.service.line_name + '';
            } else {
                popup = props.service.line_name;
            }
        }
        if (props.destination) {
            if (props.destination.indexOf(' to ') === -1) {
                popup += ' to ';
            }
            popup += props.destination;
        }

        if (props.operator) {
            if (popup) {
                popup += '
';
            }
            popup += props.operator + '
';
        }

        if (props.vehicle) {
            popup += '' + props.vehicle.name + '';
            if (props.vehicle.type) {
                popup += ' - ' + props.vehicle.type;
            }
            if (props.vehicle.notes) {
                popup += ' - ' + props.vehicle.notes;
            }
            popup += '
';
        }

        if (props.delta === 0) {
            popup += 'On time
';
        } else if (props.delta) {
            popup += 'About ';
            if (props.delta > 0) {
                popup += props.delta;
            } else {
                popup += props.delta * -1;
            }
            popup += ' minute';
            if (props.delta !== 1 && props.delta !== -1) {
                popup += 's';
            }
            if (props.delta > 0) {
                popup += ' early';
            } else {
                popup += ' late';
            }
            popup += '
';
        }

        var dateTime = new Date(props.datetime);
        popup += 'Updated at ' + dateTime.toTimeString().slice(0, 5);

        if ((props.source === 75 || props.source === 79 || props.source === 86) && props.service && props.service.url) {
            popup += '
(Updates if someone views
the ' + props.service.line_name + ' page)';
        }

        marker.bindPopup(popup);
    }

    function handleStop(data) {
        if (data.properties.url in oldStops) {
            var marker = oldStops[data.properties.url];
            newStops[data.properties.url] = marker;
            return;
        }

        var a = document.createElement('a');
        marker = L.marker(L.latLng(data.geometry.coordinates[1], data.geometry.coordinates[0]), {
            icon: getStopIcon(data.properties.indicator, data.properties.bearing)
        });

        a.innerHTML = '' + data.properties.name + '' + data.properties.services.join('') + '';
        a.href = data.properties.url;
        a.className = 'stop';

        marker.bindPopup(a.outerHTML, {
            autoPan: false
        });

        marker.addTo(stopsGroup);
        newStops[data.properties.url] = marker;
    }

    function processVehiclesData(data) {
        if (data && data.features) {
            for (var i = data.features.length - 1; i >= 0; i -= 1) {
                handleVehicle(data.features[i]);
            }
            for (var vehicle in oldVehicles) {
                if (!(vehicle in newVehicles)) {
                    vehiclesGroup.removeLayer(oldVehicles[vehicle]);
                }
            }
            oldVehicles = newVehicles;
            newVehicles = {};
            statusBar.getContainer().innerHTML = '';
        }
    }

    function processStopsData(data) {
        if (data && data.features) {
            for (var i = data.features.length - 1; i >= 0; i -= 1) {
                handleStop(data.features[i]);
            }
            for (var stop in oldStops) {
                if (!(stop in newStops)) {
                    stopsGroup.removeLayer(oldStops[stop]);
                }
            }
            oldStops = newStops;
            newStops = {};
        }
    }

    // load vehicles, and possibly stops
    function load(map, statusBar, stops) {
        statusBar.getContainer().innerHTML = 'Loading\u2026';
        var bounds = map.getBounds();
        var params = '?ymax=' + bounds.getNorth() + '&xmax=' + bounds.getEast() + '&ymin=' + bounds.getSouth() + '&xmin=' + bounds.getWest();

        if (lastVehiclesReq) {
            lastVehiclesReq.abort();
        }
        lastVehiclesReq = reqwest(
            '/vehicles.json' + params,
            function(data) {
                if (data) {
                    processVehiclesData(data);
                }
                timeout = setTimeout(function() {
                    load(map, statusBar, false);
                }, 10000);
            }
        );

        if (stops) {
            if (lastStopsReq) {
                lastStopsReq.abort();
            }
            if (map.getZoom() < 15) {
                stopsGroup.clearLayers();
                oldStops = {};
                highWater = null;
                return;
            }
            if (highWater && highWater.contains(bounds)) {
                return;
            }
            lastStopsReq = reqwest('/stops.json' + params, processStopsData);
            highWater = bounds;
        }
    }

    function handleMoveEnd(event) {
        clearTimeout(timeout);
        load(map, statusBar, true);

        var latLng = event.target.getCenter(),
            string = event.target.getZoom() + '/' + Math.round(latLng.lat * 10000) / 10000 + '/' + Math.round(latLng.lng * 10000) / 10000;

        if (history.replaceState) {
            history.replaceState(null, null, location.pathname + '#' + string);
        }
        if (window.localStorage) {
            localStorage.setItem('vehicleMap', string);
        }
    }

    map.on('moveend', Cowboy.debounce(500, handleMoveEnd));

    window.onhashchange = function(event) {
        var parts = event.target.location.hash.substr(1).split('/');

        if (parts.length == 3) {
            try {
                map.setView([parts[1], parts[2]], parts[0]);
            } catch (error) {
                // caught!
            }
        }
    };

    var parts;
    if (location.hash) {
        parts = location.hash.substring(1).split('/');
    } else if (localStorage.vehicleMap) {
        parts = localStorage.vehicleMap.split('/');
    }
    if (parts) {
        if (parts.length === 1) {
            parts = parts[0].split(',');
        }
        try {
            if (parts.length === 3) {
                map.setView([parts[1], parts[2]], parts[0]);
            } else {
                map.setView([parts[0], parts[1]], 15);
            }
        } catch (error) {
            // oh well
        }
    }
    if (!map._loaded) {
        map.setView([51.9, 0.9], 9);
    }

    function handleVisibilityChange(event) {
        if (event.target.hidden) {
            clearTimeout(timeout);
        } else {
            load(map, statusBar, false);
        }
    }

    if (document.addEventListener) {
        document.addEventListener('visibilitychange', handleVisibilityChange);
    }

    var locateButton = L.control();

    locateButton.onAdd = function(map) {
        var div = document.createElement('div');
        div.className = 'leaflet-bar';
        var a = document.createElement('a');
        a.href = '#';
        a.title = 'Find my location';
        a.setAttribute('role', 'button');
        var img = document.createElement('img');
        img.alt = 'Locate';
        img.width = 16;
        img.height = 16;
        img.src = '/static/locate.png';
        a.appendChild(img);
        div.appendChild(a);

        function locate() {
            img.className = 'working';
            map.locate({setView: true});
            return false;
        }

        function located() {
            img.className = '';
        }
        a.onclick = locate;

        map.on('locationfound', located);
        map.on('locationerror', located);

        return div;
    };

    locateButton.addTo(map);

    load(map, statusBar, true);
})();
<|endoftext|>


<commit_before>var util = require('util');
var assert = require('better-assert');

var MessageBuilderBase = require("./message_builder_base").MessageBuilderBase;
var chooseSecurityHeader = require("./secure_channel_service").chooseSecurityHeader;
var SequenceHeader = require("./secure_channel_service").SequenceHeader;
var BinaryStream = require("./binaryStream").BinaryStream;
var ec = require("./encode_decode");
var factories = require("./factories");
var packet_analyzer = require("./packet_analyzer").packet_analyzer;
var hexDump = require("./utils").hexDump;
var _ = require("underscore");

var debugLog  = require("./utils").make_debugLog(__filename);
/**
 *
 * @constructor
 */
var MessageBuilder = function() {

    MessageBuilderBase.call(this);
    this.previous_sequenceNumber = -1; // means unknown
    assert(_.isFinite(this.previous_sequenceNumber));
};
util.inherits(MessageBuilder, MessageBuilderBase);

MessageBuilder.prototype._validateSequenceNumber=function(sequenceNumber) {

    // checking that sequenceNumber is increasing
    assert(_.isFinite(this.previous_sequenceNumber));
    assert(_.isFinite(sequenceNumber) && sequenceNumber>=0 );

    var expectedSequenceNumber;
    if (this.previous_sequenceNumber !== -1) {

        expectedSequenceNumber = this.previous_sequenceNumber + 1;

        if ( expectedSequenceNumber !== sequenceNumber) {
            var errMessage ="Invalid Sequence Number found ( expected " + expectedSequenceNumber + ", got " + sequenceNumber + ")";
            console.log(errMessage.red.bold);
            this.emit("invalid_sequence_number",expectedSequenceNumber,sequenceNumber);
        }
        // todo : handle the case where sequenceNumber wraps back to < 1024
    }
    debugLog(" Sequence Number = ".yellow.bold,sequenceNumber);
    this.previous_sequenceNumber =sequenceNumber;
};

MessageBuilder.prototype._read_headers = function(binaryStream) {

    MessageBuilderBase.prototype._read_headers.apply(this,arguments);
    assert(binaryStream.length==12);

    var msgType = this.messageHeader.msgType;
    if (msgType === "HEL" || msgType === "ACK" )  {

    } else {

        this.securityHeader = chooseSecurityHeader(msgType);
        this.sequenceHeader = new SequenceHeader();

        this.securityHeader.decode(binaryStream);
        this.sequenceHeader.decode(binaryStream);

        this._validateSequenceNumber(this.sequenceHeader.sequenceNumber);
    }
};

MessageBuilder.prototype._save_decode_message_body = function(full_message_body,objMessage,binaryStream) {
    try {
        // de-serialize the object from the binary stream
        objMessage.decode(binaryStream);
    }
    catch(err) {
        console.log(err);
        console.log(err.stack);
        console.log(" Cannot read message id= ",id);
        console.log(hexDump(full_message_body));
        packet_analyzer(full_message_body);

        console.log(" ---------------- block");
        this.message_chunks.forEach(function(messageChunk){
            console.log(hexDump(messageChunk));
        });
        this.emit("error",new Error("cannot decode message"));
        return false;
    }
    return true;
}

MessageBuilder.prototype._decode_message_body = function(full_message_body) {

    var binaryStream = new BinaryStream(full_message_body);
    var msgType = this.messageHeader.msgType;

    if (msgType === "HEL" || msgType === "ACK" ) {
        // invalid message type
        this.emit("error",new Error("Invalid message type ( HEL/ACK )"));
        return;
    }

    // read expandedNodeId:
    var id = ec.decodeExpandedNodeId(binaryStream);

    // construct the object
    var objMessage = factories.constructObject(id);

    if (!objMessage) {
        this.emit("error",new Error("cannot construct object with nodeID " + id));
    } else {

        if (this._save_decode_message_body(full_message_body,objMessage,binaryStream)) {
            try {
                this.emit("message",objMessage,msgType,this.sequenceHeader.requestId);
            }
            catch(err) {
                // this code catches a uncaught exception somewhere in one of the event handler
                // this indicates a bug in the code that uses this class
                // please check the stack trace to find the problem
                console.log("MessageBuilder : ERROR DETECTED IN event handler".red)
                console.log(err);
                console.log(err.stack);
            }
        }
    }
};

exports.MessageBuilder = MessageBuilder;<commit_msg>fix message builder<commit_after>var util = require('util');
var assert = require('better-assert');

var MessageBuilderBase = require("./message_builder_base").MessageBuilderBase;
var chooseSecurityHeader = require("./secure_channel_service").chooseSecurityHeader;
var SequenceHeader = require("./secure_channel_service").SequenceHeader;
var BinaryStream = require("./binaryStream").BinaryStream;
var ec = require("./encode_decode");
var factories = require("./factories");
var packet_analyzer = require("./packet_analyzer").packet_analyzer;
var hexDump = require("./utils").hexDump;
var _ = require("underscore");

var debugLog  = require("./utils").make_debugLog(__filename);
/**
 *
 * @constructor
 */
var MessageBuilder = function() {

    MessageBuilderBase.call(this);
    this.previous_sequenceNumber = -1; // means unknown
    assert(_.isFinite(this.previous_sequenceNumber));
};
util.inherits(MessageBuilder, MessageBuilderBase);

MessageBuilder.prototype._validateSequenceNumber=function(sequenceNumber) {

    // checking that sequenceNumber is increasing
    assert(_.isFinite(this.previous_sequenceNumber));
    assert(_.isFinite(sequenceNumber) && sequenceNumber>=0 );

    var expectedSequenceNumber;
    if (this.previous_sequenceNumber !== -1) {

        expectedSequenceNumber = this.previous_sequenceNumber + 1;

        if ( expectedSequenceNumber !== sequenceNumber) {
            var errMessage ="Invalid Sequence Number found ( expected " + expectedSequenceNumber + ", got " + sequenceNumber + ")";
            console.log(errMessage.red.bold);
            this.emit("invalid_sequence_number",expectedSequenceNumber,sequenceNumber);
        }
        // todo : handle the case where sequenceNumber wraps back to < 1024
    }
    debugLog(" Sequence Number = ".yellow.bold,sequenceNumber);
    this.previous_sequenceNumber =sequenceNumber;
};

MessageBuilder.prototype._read_headers = function(binaryStream) {

    MessageBuilderBase.prototype._read_headers.apply(this,arguments);
    assert(binaryStream.length==12);

    var msgType = this.messageHeader.msgType;
    if (msgType === "HEL" || msgType === "ACK" )  {

    } else {

        this.securityHeader = chooseSecurityHeader(msgType);
        this.sequenceHeader = new SequenceHeader();

        this.securityHeader.decode(binaryStream);
        this.sequenceHeader.decode(binaryStream);

        this._validateSequenceNumber(this.sequenceHeader.sequenceNumber);
    }
};

MessageBuilder.prototype._save_decode_message_body = function(full_message_body,objMessage,binaryStream) {
    try {
        // de-serialize the object from the binary stream
        objMessage.decode(binaryStream);
    }
    catch(err) {
        console.log(err);
        console.log(err.stack);
        console.log(hexDump(full_message_body));
        packet_analyzer(full_message_body);

        console.log(" ---------------- block");
        this.message_chunks.forEach(function(messageChunk){
            console.log(hexDump(messageChunk));
        });
        this.emit("error",new Error("cannot decode message"));
        return false;
    }
    return true;
}

MessageBuilder.prototype._decode_message_body = function(full_message_body) {

    var binaryStream = new BinaryStream(full_message_body);
    var msgType = this.messageHeader.msgType;

    if (msgType === "HEL" || msgType === "ACK" ) {
        // invalid message type
        this.emit("error",new Error("Invalid message type ( HEL/ACK )"));
        return;
    }

    // read expandedNodeId:
    var id = ec.decodeExpandedNodeId(binaryStream);

    // construct the object
    var objMessage = factories.constructObject(id);

    if (!objMessage) {
        this.emit("error",new Error("cannot construct object with nodeID " + id));
    } else {

        if (this._save_decode_message_body(full_message_body,objMessage,binaryStream)) {
            try {
                this.emit("message",objMessage,msgType,this.sequenceHeader.requestId);
            }
            catch(err) {
                // this code catches a uncaught exception somewhere in one of the event handler
                // this indicates a bug in the code that uses this class
                // please check the stack trace to find the problem
                console.log("MessageBuilder : ERROR DETECTED IN event handler".red)
                console.log(err);
                console.log(err.stack);
            }
        } else {
            console.log(" Cannot read message id= ",id);
        }
    }
};

exports.MessageBuilder = MessageBuilder;<|endoftext|>