Jump To …

levents.js

/*
 *
 * Copyright (C) 2011-2012, The Locker Project
 * All rights reserved.
 *
 * Please see the LICENSE file for more information.
 *
 */

var request = require("request")
  , url = require("url")
  , lconfig = require("lconfig")
  , serviceManager = require("lservicemanager")
  , logger = require("./logger.js")
  , syncManager = require('lsyncmanager')
  , url = require('url')
  , async = require('async')
  , path = require('path');

var eventListeners = {};
var processingQueue = []; // queue of events being processed

exports.addListener = function (type, id, cb, batching) {
  logger.verbose("Adding a listener for " + id + cb + " to " + type);
  if (!eventListeners.hasOwnProperty(type)) eventListeners[type] = [];

Remove the previous listener for the id

  eventListeners[type] = eventListeners[type].filter(function (entry) {
    if (entry.id == id) return false;
    return true;
  });
  eventListeners[type].push({"id":id, "cb":cb, "batching":(batching ? true : false)});
};

exports.removeListener = function (type, id, cb) {
  logger.info("Going to remove " + id + cb + " from " + type);
  if (!eventListeners.hasOwnProperty(type)) return;
  var pos = findListenerPosition(type, id, cb);
  if (pos >= 0) eventListeners[type].splice(pos, 1);
};

get all possible listeners

function fetchListeners(idr) {
  var r = url.parse(idr);
  var types = [];

we're back-porting to the type system for now too

  var oldType = r.protocol.substr(0,r.protocol.length-1);
  types.push(oldType);
  oldType += '/' + r.host;
  types.push(oldType);
  if (r.pathname && r.pathname.length > 1) {
    oldType = r.pathname.substr(1);
    types.push(oldType);
    oldType += '/' + r.host;
    types.push(oldType);
  }
  delete r.hash;
  delete r.search;
  types.push(url.format(r));
  delete r.pathname;
  types.push(url.format(r));
  var ret = [];
  types.forEach(function (type) {
    if (!eventListeners.hasOwnProperty(type)) return;
    eventListeners[type].forEach(function (e) { ret.push(e); });
  });
  return ret;
}

exports.fireEvent = function (idr, action, obj) {
  logger.verbose("received event for " + idr + " action(" + action + ")");

Short circuit when no one is listening

  var listeners = fetchListeners(idr);
  if (listeners.length === 0) return;
  var newEventInfo = {
    idr:idr,
    action:action,
    data:obj
  };
  listeners.forEach(function (listener) {
    var lurl = lconfig.lockerBase + path.join("/Me", listener.id, listener.cb);
    if (listener.batching) {
      batchqueue(lurl).push(newEventInfo);
    } else {
      lqueue(lurl).push(newEventInfo);
    }
  });
};

exports.displayListeners = function (type) {
  return eventListeners[type];
};

function findListenerPosition(type, id, cb) {
  for (var i = 0; i < eventListeners[type].length; ++i) {
    var listener = eventListeners[type][i];
    if (listener.id == id && listener.cb == cb) return i;
  }
  return -1;
}

create a queue per listener url, maximum 1 concurrency

var lqueues = {};

function lqueue(lurl) {
  if (lqueues[lurl]) return lqueues[lurl];
  return lqueues[lurl] = async.queue(function (curEvent, callback) {
    var httpOpts = {
      url: lurl,
      method:"POST",
      headers: {
        "Content-Type":"application/json",
        "Connection":"keep-alive"
      },
      body: JSON.stringify(curEvent)
    };
    logger.verbose(lqueues[lurl].length()+": sending "+curEvent.action+" event "+curEvent.idr+" to " + lurl);
    request(httpOpts, function (err, res, body) {
      if (err || res.statusCode != 200) {
        logger.error("There was an error sending " + curEvent.idr + " " + curEvent.action + " to " + lurl + " got " + (err || res.statusCode) + ', ' + JSON.stringify(body));
        logger.verbose(JSON.stringify(curEvent.data));

logger.verbose(body); TODO: Need to evaluate the logic here, to see if we should retry or other options.

      }
      callback();
    });

  }, 1);
}

function BatchSendQueue(url) {
  this.url = url;
  this.items = [];
  this.running = false;
}

BatchSendQueue.prototype.push = function (item) {

Every push is added and we see if we can send more

  this.items.push(item);
  var self = this;
  if (!this.running) {
    if (this.timer) clearTimeout(this.timer);
    this.timer = setTimeout(function () { self.run(); }, 1000); // give a chance to queue up if coming in fast
  }
};

BatchSendQueue.prototype.run = function () {
  if (this.timer) delete this.timer;
  if (this.running) return;
  if (this.items.length === 0) return;

  this.running = true;
  var sendingItems = this.items;
  this.items = [];
  logger.verbose("Sending " + sendingItems.length + " batched events to " + this.url);
  var self = this;
  var req = request({url:this.url, method:"POST", headers:{"content-type":"application/jsonstream"}}, function (err, res, body) {
    if (err || res.statusCode != 200) {
      logger.error("There was an error sending batched events to " + self.url + " got " + (err || res.statusCode));
    }
    logger.verbose("Done sending batched events to " + self.url);

If more stuff came in we run again, otherwise push will get it next time

    self.running = false;
    if (self.items.length > 0) setTimeout(function () { self.run(); }, 1000); // breather
  });
  async.forEachSeries(sendingItems, function (item, sendCb) {
    req.write(JSON.stringify(item) + "\n");
    sendCb();
  }, function () {
    req.end();
  });
};

function batchqueue(lurl) {
  if (lqueues[lurl]) return lqueues[lurl];
  lqueues[lurl] = new BatchSendQueue(lurl);
  return lqueues[lurl];
}