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

var Ribbon = function(opts) {
  EventEmitter.call(this);
  this.up = null;
  this.opts = opts || {};
  this.tickerTape = '';
  this.callbackQs = {};
  this.adaptor = {};
  this.shuttingDown = false;
  this.startingUp = false;
  this.debugOn = false;

  if(this.opts.autoRestart) this.on('dropped', this.restart.bind(this));
};

util.inherits(Ribbon, EventEmitter);

Ribbon.prototype.debug = function(error) {
  if(this.debugOn) console.log(error);
};

Ribbon.prototype.setDebug = function(status){
  this.debugOn = status;
};

Ribbon.prototype.declareUp = function(){
  this.up = true;
  this.ticker(1);
  this.emit('up');
  if(this.wasDown()) this.emit('revived');
};

Ribbon.prototype.declareDown = function(){
  this.up = false;
  this.ticker(0);
  if(!this.isShuttingDown()) this.emit('dropped');
  this.emit('down');
};

Ribbon.prototype.isUp = function(){
  return !!this.up;
};

Ribbon.prototype.isDown = function(){
  return !this.up;
};

Ribbon.prototype.wasUp = function(){
  // By definition reflects penultimate state
  return this.tickerTape[this.tickerTape.length-2] == "1";
};

Ribbon.prototype.wasDown = function(){
  return this.tickerTape[this.tickerTape.length-2] == "0";
};

Ribbon.prototype.ticker = function(value){
  if(this.tickerTape.length >= 100) this.tickerTape = this.tickerTape.substr(1);
  this.tickerTape += value;
};

Ribbon.prototype.isShuttingDown = function(){
  return this.shuttingDown;
};

Ribbon.prototype.isStartingUp = function(){
  return this.startingUp;
};

Ribbon.prototype.runCallbackQ = function(name, args){
  var cb, q = this.callbackQs[name] || [];
  args = args || [];
  while((cb = q.shift())){
    cb.apply(cb, args);
  }
};

Ribbon.prototype.pushQ = function(name, item){
  var q;
  if(!(q = this.callbackQs[name])) q = this.callbackQs[name] = [];
  return q.push(item);
};

/**
 * Cycle through the adaptor's methods and bind Ribbon's context
 * @param {object} adaptor
 */
Ribbon.prototype.setAdaptor = function(adaptor){
  var self = this;
  this.adaptor = {};
  for(var i in adaptor){
    if(!adaptor.hasOwnProperty(i)) continue;
    this.adaptor[i] = adaptor[i].bind(this);
  }
};

Ribbon.prototype.invokeHandler = function(name, complete){
  var self = this;
  (this.adaptor[name] || function(){ return self.debug('Handler '+name+' not available'); })(complete);
};

Ribbon.prototype.runAction = function(name, cb){
  var self = this, timeout;

  this.emit('begin:'+name);
  if(this.opts.actionTimeout){
    timeout = setTimeout(function(){
      self.emit('timeout:'+name);
      cb('timeout');
      cb = function(){};
    }, this.opts.actionTimeout);
  }

  this.invokeHandler(name, function(){
    clearTimeout(timeout);
    self.emit.apply(self, ['complete:'+name].concat(arguments));
    return cb.apply(self, arguments);
  });
};

Ribbon.prototype.emit = function(){
  this.debug(arguments[0]);
  EventEmitter.prototype.emit.apply(this, arguments);
};

// *** BEGIN ACTIONS ***
Ribbon.prototype.startUp = function(cb){
  var self = this, name = 'startUp';
  cb = cb || function(){};

  if(self.isShuttingDown()) return cb('shuttingDown');
  if(this.isUp()) return cb(null, this.getClient());

  this.startingUp = true;
  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err, client){
    // There could be multiple ways we can get a startup error. If we do we should make sure the callback queue is flushed with an error
    // If we get called while shutting down, don't do anything
    if(self.isShuttingDown()) return self.runCallbackQ(name, ['shuttingDown']);

    // If we get called with an error in 'up' state after a previously successful startup, and we're not shutting down, the connection has dropped.
    if(err && self.isUp()){
      return self.dropped();
    }

    // If we get called with no error, startup succeeded
    if(!err){
      this.startingUp = false;
      self.declareUp();
    }

    self.setClient(client);
    return self.runCallbackQ(name, [err, client]);
  });
};

Ribbon.prototype.shutDown = function(cb){
  var self = this, name = 'shutDown';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  // Declare we're shutting down
  this.shuttingDown = true;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

Ribbon.prototype.restart = function(cb){
  var self = this, name = 'restart';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  if(this.adaptor.restart){
    return this.runAction(name, function(err){
      return self.runCallbackQ(name, [err]);
    });
  }

  var startUp = function(){
    self.startUp(cb);
  };

  // Make sure everything's shut down properly and then start up again
  return this.isUp() ? this.shutDown(startUp) : startUp();
};

Ribbon.prototype.destroy = function(cb){
  var self = this, name = 'destroy';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

/**
 * An interruption in service could happen at any point, regardless of if other actions are in progress.
 * Therefore, we should establish what state Ribbon is in at the time of being informed of a service drop.
 * Any callbacks for outstanding actions should then be called with an error so the clients know an interruption happened.
 */
Ribbon.prototype.dropped = function(){
  var self = this;

  if(this.isShuttingDown()) return;

  this.declareDown();
  this.emit('dropped');
  return this.runCallbackQ('startUp', ['dropped']) && this.runCallbackQ('shutDown', ['dropped']);
};

// *** END ACTIONS ***

Ribbon.prototype.setClient = function(client){
  if(!client) return;
  this.client = client;
};

Ribbon.prototype.getClient = function(){
  return this.client || false;
};

module.exports.Ribbon = Ribbon;

module.exports.wrap = function(adaptor, opts) {
  if(typeof adaptor == 'string') {
    var err = null;
    var adaptorPath = __dirname+'/adaptors/'+adaptor+'/'+adaptor;
    try {
      adaptor = require(adaptorPath);
    }
    catch(e) {
      err = e;
    }
    if(!adaptor || err){
      throw new Error('Adapter not found. Failed loading '+adaptorPath+' '+err);
    }
  }
  else {
    adaptor = adaptor;
  }

  var r = new Ribbon(opts);
  r.setAdaptor(adaptor);
  return r;
};
<commit_msg>Only drop when connected. Add restart restrictions<commit_after>var EventEmitter = require('events').EventEmitter,
    util = require('util');

var Ribbon = function(opts) {
  EventEmitter.call(this);
  this.up = null;
  this.opts = {
    // Invoke a restart when dropped
    autoRestart: false,
    actionTimeout: 5000,
    backoffFactor: 1.2,
    restartDelay: 1000,
    maxRestartAttempts: -1
  };
  // Shallow copy
  if(typeof opts == 'object'){
    for(var i in opts){
      this.opts[i] = opts[i];
    }
  }
  this.tickerTape = '';
  this.callbackQs = {};
  this.adaptor = {};
  this.shuttingDown = false;
  this.startingUp = false;
  this.debugOn = false;
  this.restartDelay = this.opts.restartDelay;
  this.restartAttempts = 0;

  if(this.opts.autoRestart) this.on('dropped', this.restart.bind(this));
};

util.inherits(Ribbon, EventEmitter);

Ribbon.prototype.debug = function(error) {
  if(this.debugOn) console.log(error);
};

Ribbon.prototype.setDebug = function(status){
  this.debugOn = status;
};

Ribbon.prototype.declareUp = function(){
  this.up = true;
  this.ticker(1);
  this.emit('up');
  if(this.wasDown()) this.emit('revived');
};

Ribbon.prototype.declareDown = function(){
  this.up = false;
  this.ticker(0);
  this.emit('down');
};

Ribbon.prototype.isUp = function(){
  return !!this.up;
};

Ribbon.prototype.isDown = function(){
  return !this.up;
};

Ribbon.prototype.wasUp = function(){
  // By definition reflects penultimate state
  return this.tickerTape[this.tickerTape.length-2] == "1";
};

Ribbon.prototype.wasDown = function(){
  return this.tickerTape[this.tickerTape.length-2] == "0";
};

Ribbon.prototype.ticker = function(value){
  if(this.tickerTape.length >= 100) this.tickerTape = this.tickerTape.substr(1);
  this.tickerTape += value;
};

Ribbon.prototype.isShuttingDown = function(){
  return this.shuttingDown;
};

Ribbon.prototype.isStartingUp = function(){
  return this.startingUp;
};

Ribbon.prototype.runCallbackQ = function(name, args){
  var cb, q = this.callbackQs[name] || [];
  args = args || [];
  while((cb = q.shift())){
    cb.apply(cb, args);
  }
};

Ribbon.prototype.pushQ = function(name, item){
  var q;
  if(!(q = this.callbackQs[name])) q = this.callbackQs[name] = [];
  return q.push(item);
};

/**
 * Cycle through the adaptor's methods and bind Ribbon's context
 * @param {object} adaptor
 */
Ribbon.prototype.setAdaptor = function(adaptor){
  var self = this;
  this.adaptor = {};
  for(var i in adaptor){
    if(!adaptor.hasOwnProperty(i)) continue;
    this.adaptor[i] = adaptor[i].bind(this);
  }
};

Ribbon.prototype.invokeHandler = function(name, complete){
  var self = this;
  (this.adaptor[name] || function(){ return self.debug('Handler '+name+' not available'); })(complete);
};

Ribbon.prototype.runAction = function(name, cb){
  var self = this, timeout;

  this.emit('begin:'+name);
  if(this.opts.actionTimeout){
    timeout = setTimeout(function(){
      self.emit('timeout:'+name);
      cb('timeout');
      cb = function(){};
    }, this.opts.actionTimeout);
  }

  this.invokeHandler(name, function(){
    clearTimeout(timeout);
    self.emit.apply(self, ['complete:'+name].concat(arguments));
    return cb.apply(self, arguments);
  });
};

Ribbon.prototype.emit = function(){
  this.debug(arguments[0]);
  EventEmitter.prototype.emit.apply(this, arguments);
};

// *** BEGIN ACTIONS ***
Ribbon.prototype.startUp = function(cb){
  var self = this, name = 'startUp';
  cb = cb || function(){};

  if(self.isShuttingDown()) return cb('shuttingDown');
  if(this.isUp()) return cb(null, this.getClient());

  this.startingUp = true;
  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err, client){
    // There could be multiple ways we can get a startup error. If we do we should make sure the callback queue is flushed with an error
    // If we get called while shutting down, don't do anything
    if(self.isShuttingDown()) return self.runCallbackQ(name, ['shuttingDown']);

    // If we get called with an error in 'up' state after a previously successful startup, and we're not shutting down, the connection has dropped.
    if(err && self.isUp()){
      return self.dropped();
    }

    // If we get called with no error, startup succeeded
    if(!err){
      this.startingUp = false;
      self.declareUp();
    }

    self.setClient(client);
    self.runCallbackQ(name, [err, client]);
    if(err){
      self.restart();
    }
  });
};

Ribbon.prototype.shutDown = function(cb){
  var self = this, name = 'shutDown';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  // Declare we're shutting down
  this.shuttingDown = true;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

Ribbon.prototype.restart = function(cb){
  var self = this, name = 'restart';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;
  if(this.opts.maxRestartAttempts > -1 && ++this.restartAttempts > this.opts.maxRestartAttempts) return;

  var resetDelay = function(){
    self.restartDelay = self.opts.restartDelay;
  };

  setTimeout(function(){
    if(self.adaptor.restart){
      return self.runAction(name, function(err){
        if(!err) resetDelay();
        return self.runCallbackQ(name, [err]);
      });
    }

    var startUp = function(){
      self.startUp(function(err, client){
        self.runCallbackQ(name);
        if(!err) resetDelay();
        cb(err, client);
      });
    };

    // Make sure everything's shut down properly and then start up again
    return self.isUp() ? self.shutDown(startUp) : startUp();
  }, self.restartDelay);
  self.restartDelay = Math.floor(self.restartDelay * self.opts.backoffFactor);
};

Ribbon.prototype.destroy = function(cb){
  var self = this, name = 'destroy';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

/**
 * An interruption in service could happen at any point, regardless of if other actions are in progress.
 * Therefore, we should establish what state Ribbon is in at the time of being informed of a service drop.
 * Any callbacks for outstanding actions should then be called with an error so the clients know an interruption happened.
 */
Ribbon.prototype.dropped = function(){
  var self = this;

  if(this.isShuttingDown() || !this.isUp()) return;

  this.declareDown();
  this.runCallbackQ('startUp', ['dropped']) && this.runCallbackQ('shutDown', ['dropped']);
  this.emit('dropped');
};

// *** END ACTIONS ***

Ribbon.prototype.setClient = function(client){
  if(!client) return;
  this.client = client;
};

Ribbon.prototype.getClient = function(){
  return this.client || false;
};

module.exports.Ribbon = Ribbon;

module.exports.wrap = function(adaptor, opts) {
  if(typeof adaptor == 'string') {
    var err = null;
    var adaptorPath = __dirname+'/adaptors/'+adaptor+'/'+adaptor;
    try {
      adaptor = require(adaptorPath);
    }
    catch(e) {
      err = e;
    }
    if(!adaptor || err){
      throw new Error('Adapter not found. Failed loading '+adaptorPath+' '+err);
    }
  }
  else {
    adaptor = adaptor;
  }

  var r = new Ribbon(opts);
  r.setAdaptor(adaptor);
  return r;
};
<|endoftext|>


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

var Ribbon = function(opts) {
  EventEmitter.call(this);
  this.up = null;
  this.opts = {
    // Invoke a restart when dropped
    autoRestart: false,
    actionTimeout: 5000,
    backoffFactor: 1.2,
    restartDelay: 1000,
    maxRestartAttempts: -1
  };
  // Shallow copy
  if(typeof opts == 'object'){
    for(var i in opts){
      this.opts[i] = opts[i];
    }
  }
  this.tickerTape = '';
  this.callbackQs = {};
  this.adaptor = {};
  this.shuttingDown = false;
  this.startingUp = false;
  this.restartDelay = this.opts.restartDelay;
  this.restartAttempts = 0;
  this.instanceID = Math.floor(Math.random()*9999).toString(16);
  this.debugPrefix = 'ribbon:'+this.instanceID;
  this.debug = debug(this.debugPrefix);
  this.debugEvents = debug(this.debugPrefix+':events');
  this.debugAdaptor = debug(this.debugPrefix+':adaptor');

  if(this.opts.autoRestart) this.on('dropped', this.restart.bind(this));
};

util.inherits(Ribbon, EventEmitter);

Ribbon.prototype.declareUp = function(){
  this.up = true;
  this.ticker(1);
  this.emit('up');
  if(this.wasDown()) this.emit('revived');
};

Ribbon.prototype.declareDown = function(){
  this.up = false;
  this.ticker(0);
  this.emit('down');
};

Ribbon.prototype.isUp = function(){
  return !!this.up;
};

Ribbon.prototype.isDown = function(){
  return !this.up;
};

Ribbon.prototype.wasUp = function(){
  // By definition reflects penultimate state
  return this.tickerTape[this.tickerTape.length-2] == "1";
};

Ribbon.prototype.wasDown = function(){
  return this.tickerTape[this.tickerTape.length-2] == "0";
};

Ribbon.prototype.ticker = function(value){
  if(this.tickerTape.length >= 100) this.tickerTape = this.tickerTape.substr(1);
  this.tickerTape += value;
};

Ribbon.prototype.isShuttingDown = function(){
  return this.shuttingDown;
};

Ribbon.prototype.isStartingUp = function(){
  return this.startingUp;
};

Ribbon.prototype.runCallbackQ = function(name, args){
  var cb, q = this.callbackQs[name] || [];
  args = args || [];
  while((cb = q.shift())){
    cb.apply(cb, args);
  }
};

Ribbon.prototype.pushQ = function(name, item){
  var q;
  if(!(q = this.callbackQs[name])) q = this.callbackQs[name] = [];
  return q.push(item);
};

/**
 * Cycle through the adaptor's methods and bind Ribbon's context
 * @param {object} adaptor
 */
Ribbon.prototype.setAdaptor = function(adaptor){
  var self = this;
  this.adaptor = {};
  for(var i in adaptor){
    if(!adaptor.hasOwnProperty(i)) continue;
    this.adaptor[i] = adaptor[i].bind(this);
  }
};

Ribbon.prototype.invokeHandler = function(name, complete){
  var self = this;
  (this.adaptor[name] || function(){ return self.debug('Handler '+name+' not available'); })(complete);
};

Ribbon.prototype.runAction = function(name, cb){
  var self = this, timeout;

  this.emit('begin:'+name);
  if(this.opts.actionTimeout){
    timeout = setTimeout(function(){
      self.emit('timeout:'+name);
      cb('timeout');
      cb = function(){};
    }, this.opts.actionTimeout);
  }

  this.invokeHandler(name, function(){
    clearTimeout(timeout);
    self.emit.apply(self, ['complete:'+name].concat(arguments));
    return cb.apply(self, arguments);
  });
};

Ribbon.prototype.emit = function(){
  this.debugEvents(arguments[0]);
  EventEmitter.prototype.emit.apply(this, arguments);
};

// *** BEGIN ACTIONS ***
Ribbon.prototype.startUp = function(cb){
  var self = this, name = 'startUp';
  cb = cb || function(){};

  if(self.isShuttingDown()) return cb('shuttingDown');
  if(this.isUp()) return cb(null, this.getClient());

  this.startingUp = true;
  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err, client){
    // There could be multiple ways we can get a startup error. If we do we should make sure the callback queue is flushed with an error
    // If we get called while shutting down, don't do anything
    if(self.isShuttingDown()) return self.runCallbackQ(name, ['shuttingDown']);

    // If we get called with an error in 'up' state after a previously successful startup, and we're not shutting down, the connection has dropped.
    if(err && self.isUp()){
      return self.dropped();
    }

    // If we get called with no error, startup succeeded
    if(!err){
      this.startingUp = false;
      self.declareUp();
    }

    self.setClient(client);
    self.runCallbackQ(name, [err, client]);
    if(err && self.opts.autoRestart){
      self.restart();
    }
  });
};

Ribbon.prototype.shutDown = function(cb){
  var self = this, name = 'shutDown';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  // Declare we're shutting down
  this.shuttingDown = true;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

Ribbon.prototype.restart = function(cb){
  var self = this, name = 'restart';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;
  if(this.opts.maxRestartAttempts > -1 && ++this.restartAttempts > this.opts.maxRestartAttempts) return;

  var resetDelay = function(){
    self.restartDelay = self.opts.restartDelay;
  };

  setTimeout(function(){
    if(self.adaptor.restart){
      return self.runAction(name, function(err){
        if(!err) resetDelay();
        return self.runCallbackQ(name, [err]);
      });
    }

    var startUp = function(){
      self.startUp(function(err, client){
        self.runCallbackQ(name);
        if(!err) resetDelay();
        cb(err, client);
      });
    };

    // Make sure everything's shut down properly and then start up again
    return self.isUp() ? self.shutDown(startUp) : startUp();
  }, self.restartDelay);
  self.restartDelay = Math.floor(self.restartDelay * self.opts.backoffFactor);
};

/**
 * Forcefully close the connection and do not reconnect
 */
Ribbon.prototype.destroy = function(cb){
  var self = this, name = 'destroy';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

/**
 * An interruption in service could happen at any point, regardless of if other actions are in progress.
 * Therefore, we should establish what state Ribbon is in at the time of being informed of a service drop.
 * Any callbacks for outstanding actions should then be called with an error so the clients know an interruption happened.
 */
Ribbon.prototype.dropped = function(){
  var self = this;

  this.debug('dropped');

  if(this.isShuttingDown() || !this.isUp()) return;

  this.runCallbackQ('startUp', ['dropped']) && this.runCallbackQ('shutDown', ['dropped']);
  // Make sure the old client is obliterated. We don't want any more events coming out of it.
  this.destroy();
  this.emit('dropped');
};

// *** END ACTIONS ***

Ribbon.prototype.setClient = function(client){
  if(!client) return;
  this.client = client;
};

Ribbon.prototype.getClient = function(){
  return this.client || false;
};

module.exports.Ribbon = Ribbon;

module.exports.wrap = function(adaptor, opts) {
  if(typeof adaptor == 'string') {
    var err = null;
    var adaptorPath = __dirname+'/adaptors/'+adaptor+'/'+adaptor;
    try {
      adaptor = require(adaptorPath);
    }
    catch(e) {
      err = e;
    }
    if(!adaptor || err){
      throw new Error('Adapter not found. Failed loading '+adaptorPath+' '+err);
    }
  }
  else {
    adaptor = adaptor;
  }

  var r = new Ribbon(opts);
  r.setAdaptor(adaptor);
  return r;
};
<commit_msg>Tweak backoff<commit_after>var EventEmitter = require('events').EventEmitter,
    debug = require('debug'),
    util = require('util');

var Ribbon = function(opts) {
  EventEmitter.call(this);
  this.up = null;
  this.opts = {
    // Invoke a restart when dropped
    autoRestart: false,
    actionTimeout: 5000,
    backoffCoefficient: 1.05,
    restartDelay: 1000,
    maxRestartAttempts: -1
  };
  // Shallow copy
  if(typeof opts == 'object'){
    for(var i in opts){
      this.opts[i] = opts[i];
    }
  }
  this.tickerTape = '';
  this.callbackQs = {};
  this.adaptor = {};
  this.shuttingDown = false;
  this.startingUp = false;
  this.restartDelay = this.opts.restartDelay;
  this.restartAttempts = 0;
  this.instanceID = Math.floor(Math.random()*9999).toString(16);
  this.debugPrefix = 'ribbon:'+this.instanceID;
  this.debug = debug(this.debugPrefix);
  this.debugEvents = debug(this.debugPrefix+':events');
  this.debugAdaptor = debug(this.debugPrefix+':adaptor');

  if(this.opts.autoRestart) this.on('dropped', this.restart.bind(this));
};

util.inherits(Ribbon, EventEmitter);

Ribbon.prototype.declareUp = function(){
  this.up = true;
  this.ticker(1);
  this.emit('up');
  if(this.wasDown()) this.emit('revived');
};

Ribbon.prototype.declareDown = function(){
  this.up = false;
  this.ticker(0);
  this.emit('down');
};

Ribbon.prototype.isUp = function(){
  return !!this.up;
};

Ribbon.prototype.isDown = function(){
  return !this.up;
};

Ribbon.prototype.wasUp = function(){
  // By definition reflects penultimate state
  return this.tickerTape[this.tickerTape.length-2] == "1";
};

Ribbon.prototype.wasDown = function(){
  return this.tickerTape[this.tickerTape.length-2] == "0";
};

Ribbon.prototype.ticker = function(value){
  if(this.tickerTape.length >= 100) this.tickerTape = this.tickerTape.substr(1);
  this.tickerTape += value;
};

Ribbon.prototype.isShuttingDown = function(){
  return this.shuttingDown;
};

Ribbon.prototype.isStartingUp = function(){
  return this.startingUp;
};

Ribbon.prototype.runCallbackQ = function(name, args){
  var cb, q = this.callbackQs[name] || [];
  args = args || [];
  while((cb = q.shift())){
    cb.apply(cb, args);
  }
};

Ribbon.prototype.pushQ = function(name, item){
  var q;
  if(!(q = this.callbackQs[name])) q = this.callbackQs[name] = [];
  return q.push(item);
};

/**
 * Cycle through the adaptor's methods and bind Ribbon's context
 * @param {object} adaptor
 */
Ribbon.prototype.setAdaptor = function(adaptor){
  var self = this;
  this.adaptor = {};
  for(var i in adaptor){
    if(!adaptor.hasOwnProperty(i)) continue;
    this.adaptor[i] = adaptor[i].bind(this);
  }
};

Ribbon.prototype.invokeHandler = function(name, complete){
  var self = this;
  (this.adaptor[name] || function(){ return self.debug('Handler '+name+' not available'); })(complete);
};

Ribbon.prototype.runAction = function(name, cb){
  var self = this, timeout;

  this.emit('begin:'+name);
  if(this.opts.actionTimeout){
    timeout = setTimeout(function(){
      self.emit('timeout:'+name);
      cb('timeout');
      cb = function(){};
    }, this.opts.actionTimeout);
  }

  this.invokeHandler(name, function(){
    clearTimeout(timeout);
    self.emit.apply(self, ['complete:'+name].concat(arguments));
    return cb.apply(self, arguments);
  });
};

Ribbon.prototype.emit = function(){
  this.debugEvents(arguments[0]);
  EventEmitter.prototype.emit.apply(this, arguments);
};

// *** BEGIN ACTIONS ***
Ribbon.prototype.startUp = function(cb){
  var self = this, name = 'startUp';
  cb = cb || function(){};

  if(self.isShuttingDown()) return cb('shuttingDown');
  if(this.isUp()) return cb(null, this.getClient());

  this.startingUp = true;
  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err, client){
    // There could be multiple ways we can get a startup error. If we do we should make sure the callback queue is flushed with an error
    // If we get called while shutting down, don't do anything
    if(self.isShuttingDown()) return self.runCallbackQ(name, ['shuttingDown']);

    // If we get called with an error in 'up' state after a previously successful startup, and we're not shutting down, the connection has dropped.
    if(err && self.isUp()){
      return self.dropped();
    }

    // If we get called with no error, startup succeeded
    if(!err){
      this.startingUp = false;
      self.declareUp();
    }

    self.setClient(client);
    self.runCallbackQ(name, [err, client]);
    if(err && self.opts.autoRestart){
      self.restart();
    }
  });
};

Ribbon.prototype.shutDown = function(cb){
  var self = this, name = 'shutDown';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  // Declare we're shutting down
  this.shuttingDown = true;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

Ribbon.prototype.restart = function(cb){
  var self = this, name = 'restart';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;
  if(this.opts.maxRestartAttempts > -1 && ++this.restartAttempts > this.opts.maxRestartAttempts) return;

  var resetDelay = function(){
    self.restartDelay = self.opts.restartDelay;
  };

  setTimeout(function(){
    if(self.adaptor.restart){
      return self.runAction(name, function(err){
        if(!err) resetDelay();
        return self.runCallbackQ(name, [err]);
      });
    }

    var startUp = function(){
      self.startUp(function(err, client){
        self.runCallbackQ(name);
        if(!err) resetDelay();
        cb(err, client);
      });
    };

    // Make sure everything's shut down properly and then start up again
    return self.isUp() ? self.shutDown(startUp) : startUp();
  }, self.restartDelay);
  self.restartDelay = Math.floor(self.restartDelay * self.opts.backoffCoefficient);
};

/**
 * Forcefully close the connection and do not reconnect
 */
Ribbon.prototype.destroy = function(cb){
  var self = this, name = 'destroy';
  cb = cb || function(){};

  if(this.pushQ(name, cb) > 1) return;

  this.runAction(name, function(err){
    if(!err && self.isUp()) self.declareDown();
    self.shuttingDown = false;
    return self.runCallbackQ(name, [err]);
  });
};

/**
 * An interruption in service could happen at any point, regardless of if other actions are in progress.
 * Therefore, we should establish what state Ribbon is in at the time of being informed of a service drop.
 * Any callbacks for outstanding actions should then be called with an error so the clients know an interruption happened.
 */
Ribbon.prototype.dropped = function(){
  var self = this;

  this.debug('dropped');

  if(this.isShuttingDown() || !this.isUp()) return;

  this.runCallbackQ('startUp', ['dropped']) && this.runCallbackQ('shutDown', ['dropped']);
  // Make sure the old client is obliterated. We don't want any more events coming out of it.
  this.destroy();
  this.emit('dropped');
};

// *** END ACTIONS ***

Ribbon.prototype.setClient = function(client){
  if(!client) return;
  this.client = client;
};

Ribbon.prototype.getClient = function(){
  return this.client || false;
};

module.exports.Ribbon = Ribbon;

module.exports.wrap = function(adaptor, opts) {
  if(typeof adaptor == 'string') {
    var err = null;
    var adaptorPath = __dirname+'/adaptors/'+adaptor+'/'+adaptor;
    try {
      adaptor = require(adaptorPath);
    }
    catch(e) {
      err = e;
    }
    if(!adaptor || err){
      throw new Error('Adapter not found. Failed loading '+adaptorPath+' '+err);
    }
  }
  else {
    adaptor = adaptor;
  }

  var r = new Ribbon(opts);
  r.setAdaptor(adaptor);
  return r;
};
<|endoftext|>


<commit_before>var rollup = require('rollup');
var traverseTree = require('./arithmetic').traverseTree;
var extend = require('./utils').extend;

exports.rollupTree = function(tree, entryPoints, compileOpts) {
  // analyze the tree to determine es module subgraphs that can be rolled-up
  // entry points are protected from inlining

  // determine all the tree entry points
  var entryMap = {};
  // at the same time build up tree externals list
  var externals = [];

  // for each module in the tree, we traverse the whole tree
  // we then relate each module in the tree to the first traced entry point
  Object.keys(tree).forEach(function(entryPoint) {
    traverseTree(tree, entryPoint, function(depName, parentName) {
      // if we have a entryMap for the given module, then stop
      if (entryMap[depName])
        return false;

      if (!tree[depName] || tree[depName] === true)
        externals.push(depName);

      if (parentName)
        entryMap[depName] = entryPoint;
    });
  });

  // the entry points are then the modules not represented in entryMap
  Object.keys(tree).forEach(function(entryPoint) {
    if (!entryMap[entryPoint] && entryPoints.indexOf(entryPoint) == -1)
      entryPoints.push(entryPoint);
  });

  // the key property is that no one of these subgraph entry point is contained within the rollup optimization of another
  var subGraphEntryPoints = {};

  // initially all es modules are considered candidates
  Object.keys(tree).forEach(function(moduleName) {
    if (tree[moduleName].metadata.format != 'esm')
      return;

    subGraphEntryPoints[moduleName] = {
      externals: []
    };
  })

  // apply filtering to ensure key property above
  // traverse down from each entry module to form its esm subgraph
  Object.keys(subGraphEntryPoints).forEach(function(entryPoint) {
    var entryPointSubGraph = subGraphEntryPoints[entryPoint];

    if (!entryPointSubGraph)
      return;

    traverseTree(tree, entryPoint, function(moduleName, parent) {

      // dep outside of tree -> add to externals list
      if (!tree[moduleName]) {
        entryPointSubGraph.externals.push(moduleName);
        return false;
      }

      // if it is not inlinable, mark as an external and stop traversal
      if (tree[moduleName].metadata.format != 'esm' || parent && entryPoints.indexOf(moduleName) != -1) {
        entryPointSubGraph.externals.push(moduleName);
        return false;
      }

      // if we have an inlinable dependency that is an entry point
      // then note that it is no longer an entry point
      // and assume its externals and modules
      if (moduleName != entryPoint && subGraphEntryPoints[moduleName]) {
        subGraphEntryPoints[moduleName].externals.forEach(function(moduleName) {
          entryPointSubGraph.externals.push(moduleName);
        });
        subGraphEntryPoints[moduleName] = undefined;
        return false;
      }

    }, false);
  });

  var rolledUpTree = {};
  Object.keys(tree).forEach(function(moduleName) {
    rolledUpTree[moduleName] = tree[moduleName];
  });

  // now that we have the full filtered list of entry points
  // run rollup against each subgraph
  return Promise.all(Object.keys(subGraphEntryPoints).map(function(entryPoint) {
    if (!subGraphEntryPoints[entryPoint])
      return;

    if (entryPoint.indexOf('first.js') != -1)
      throw externals;

    return rollup.rollup({
      entry: entryPoint,
      external: externals.concat(subGraphEntryPoints[entryPoint].externals),
      plugins: [{
        resolveId: function(id, importer, options) {
          return importer ? tree[importer].depMap[id] : id;
        },
        load: function(path, options) {
          return tree[path].metadata.originalSource || tree[path].source;
        }
      }]
    })
    .then(function(bundle) {
      var output = bundle.generate({
        sourceMap: compileOpts.sourceMaps
      });

      var entryPointLoad = tree[entryPoint];

      // filter deps to just non-inlined
      var deps = entryPointLoad.deps.filter(function(dep) {
        var moduleName = entryPointLoad.depMap[dep];
        return tree[moduleName].metadata.format != 'esm' || entryPoints.indexOf(moduleName) != -1;
      });

      // replace the entry point module itself with the inlined subgraph module
      rolledUpTree[entryPoint] = extend(extend({}, entryPointLoad), {
        deps: deps,
        metadata: extend(extend({}, entryPointLoad.metadata), {
          originalSource: undefined,
          sourceMap: output.map
        }),
        source: output.code
      });
    });
  }))
  .then(function() {
    var seenModules = [];
    // traverse the whole graph to determine modules orphaned by the inlining operations
    entryPoints.forEach(function(entryPoint) {
      traverseTree(rolledUpTree, entryPoint, function(moduleName, parent) {
        if (seenModules.indexOf(moduleName) != -1)
          return false;
        seenModules.push(moduleName);
      });
    });

    // remove orphaned loads
    Object.keys(rolledUpTree).forEach(function(moduleName) {
      if (seenModules.indexOf(moduleName) == -1)
        delete rolledUpTree[moduleName];
    });

    return rolledUpTree;
  });
};<commit_msg>complete rollup integration<commit_after>var rollup = require('rollup');
var traverseTree = require('./arithmetic').traverseTree;
var getConditionModule = require('./trace').getConditionModule;
var extend = require('./utils').extend;

exports.rollupTree = function(tree, entryPoints, compileOpts) {
  /*
   * 1. Determine the tree entry points and optimization points
   *
   * eg for the tree:
   *
   * A -> B -> C
   * D -> C
   *
   * A and D are the entry points.
   * Optimization points are ES module entry points to be optimized
   *
   */

  entryPoints = entryPoints.concat([]);

  var optimizationPoints = [];

  var entryMap = {};

  function isESM(moduleName) {
    return tree[moduleName] && tree[moduleName].metadata && tree[moduleName].metadata.format == 'esm' && !tree[moduleName].metadata.originalSource;
  }

  // for each module in the tree, we traverse the whole tree
  // we then relate each module in the tree to the first traced entry point
  Object.keys(tree).forEach(function(entryPoint) {
    traverseTree(tree, entryPoint, function(depName, parentName) {
      // esm from a non-esm parent means this is an optimization entry point from the linking alogorithm perspective
      if (parentName && isESM(depName) && !isESM(parentName) && optimizationPoints.indexOf(depName) == -1)
        optimizationPoints.push(depName);

      // if we have a entryMap for the given module, then stop
      if (entryMap[depName])
        return false;

      if (parentName)
        entryMap[depName] = entryPoint;
    });
  });

  // the entry points are then the modules not represented in entryMap
  Object.keys(tree).forEach(function(entryPoint) {
    if (!entryMap[entryPoint] && entryPoints.indexOf(entryPoint) == -1)
      entryPoints.push(entryPoint);
  });

  // optimization points are es module entry points
  entryPoints.forEach(function(entryPoint) {
    if (isESM(entryPoint) && optimizationPoints.indexOf(entryPoint) == -1)
      optimizationPoints.push(entryPoint);
  })

  /*
   * 2. Determine unoptimizable modules, splitting them out into their own optimization points
   *
   * eg for the tree:
   *   A -> B -> C -> D
   *   E -> C -> D
   *
   * A, E are top-level entry points detected by the previous step
   *   (and hence optimization points if they are es modules)
   * C is not optimizable because it has two unique parent entry points
   *   (which is what this step looks for)
   * So C becomes its own optimization point
   * Leading to D inlining into C and B inlining into A
   *
   */

  // for each module in the tree, we track its parent optimization point
  // as soon as a module has two parent entry points, it is not optimizable
  // and we set it to undefined here. It then becomes its own optimizationPoint.
  var optimizationParentMap = {};

  // build up the parent entry point map as above
  // we use for over forEach because this list will grow as we go
  for (var i = 0; i < optimizationPoints.length; i++) {
    var entryPoint = optimizationPoints[i];
    traverseTree(tree, entryPoint, function(depName, parentName) {

      // we only traverse ES module tree subgraphs
      if (!isESM(depName))
        return false;

      if (depName == entryPoint)
        return;

      // dont traverse through other entry points
      if (optimizationPoints.indexOf(depName) != -1)
        return false;

      if (!optimizationParentMap[depName]) {
        optimizationParentMap[depName] = entryPoint;
        return;
      }

      // module in two separate entry point graphs -> it becomes its own optimization entry point
      if (optimizationParentMap[depName] != entryPoint) {
        optimizationParentMap[depName] = undefined;

        // this new optimization point will then be traversed in turn as part of this loop later
        optimizationPoints.push(depName);
      }
    });
  }

  /*
   * 3. Given complete optimization points, populate subgraph externals
   *
   * eg for the graph
   *    A -> B -> C
   *
   * Where A is the optimization point, and C is not ESM, another optimization point,
   * or not contained in our build tree, then we mark 'C' as an external.
   *
   * That is, optimizationGraphExternals[A] = [C]
   *
   * This externals input is used in the Rollup API.
   * This way we just optimize B into A, retaining an explicit dependency on C.
   */

  var inlinedModules = [];
  var optimizationGraphExternals = {};

  optimizationPoints.forEach(function(entryPoint) {
    // the subgraph object is the list of modules in the subgraph
    // and the list of modules that are "external" boundaries of the subgraph
    var externals = [];

    // never create an optimization from a single module
    var hasInlinableDeps = false;

    traverseTree(tree, entryPoint, function(depName, parentName) {
      if (depName == entryPoint)
        return;

      // anything not ESM, not in the tree, or an optimization point, is external
      if (!isESM(depName) || optimizationPoints.indexOf(depName) != -1) {
        externals.push(depName);
        return false;
      }

      inlinedModules.push(depName);
      hasInlinableDeps = true;
    });

    if (hasInlinableDeps)
      optimizationGraphExternals[entryPoint] = externals;
  });

  // finally we rollup each optimization graph
  var rolledUpTree = {};
  Object.keys(tree).forEach(function(moduleName) {
    if (inlinedModules.indexOf(moduleName) == -1)
      rolledUpTree[moduleName] = tree[moduleName];
  });

  return Promise.all(Object.keys(optimizationGraphExternals).map(function(entryPoint) {
    var externals = optimizationGraphExternals[entryPoint];

    return rollup.rollup({
      entry: entryPoint,
      external: externals,
      plugins: [{
        resolveId: function(id, importer, options) {
          var resolved = importer ? tree[importer].depMap[id] : id;
          // false return indicates not part of optimization tree in Rollup
          if (externals.indexOf(resolved) != -1)
            return false;
          return resolved;
        },
        load: function(id, options) {
          return tree[id].metadata.originalSource || tree[id].source;
        }
      }],
      onwarn: function(message) {}
    })
    .then(function(bundle) {
      var entryPointLoad = tree[entryPoint];

      var output = bundle.generate({
        sourceMap: compileOpts.sourceMaps,
        sourceMapFile: entryPointLoad.path
      });

      // replace the entry point module itself with the inlined subgraph module
      rolledUpTree[entryPoint] = extend(extend({}, entryPointLoad), {
        deps: entryPointLoad.deps.filter(function(dep) {
          return externals.indexOf(entryPointLoad.depMap[dep]) != -1;
        }),
        metadata: extend(extend({}, entryPointLoad.metadata), {
          originalSource: undefined,
          sourceMap: output.map
        }),
        source: output.code
      });
    });
  }))
  .then(function() {
    return rolledUpTree;
  });
};<|endoftext|>


<commit_before>var async = require("async");
var Roomba = module.exports = function() {
    this.tasks = [];
};

Roomba.prototype.tap = function(selector) {
    this.tasks.push({
        name: selector,
        type: "tap",
        tap: selector
    });
};

Roomba.prototype.start = Roomba.prototype.then = function(task) {
    this.tasks.push({
        name: task.name,
        type: "then",
        then: task
    });
};

Roomba.prototype.waitFor = function(params) {
    this.tasks.push({
        name: params.implicitEvent || params.view,
        type: "waitFor",
        waitFor: params
    });
};

Roomba.prototype.runAll = function() {
    var i = 0,
        that = this,
        currTaskName, currentRunId;
    getNewTestRunId(function(newRunId) {
        currentRunId = newRunId;
        next();
    });
    function next() {
        if (currTaskName) {
            save(currentRunId, currTaskName, true, {
                message: currTaskName + " completed successfully"
            });
        }
        async.series([
            function aTimeout(cb) {
                setTimeout(function() {
                    cb(null);
                }, 500);
            },

            function doTask() {
                if (i === that.tasks.length) {
                    return; //end recursion
                }
                var task = that.tasks[i++];
                console.warn(task.type + ": " + task.name);
                currTaskName = task.type + ": " + task.name;

                try {
                    if (task.then) {
                        task.then();
                        next();
                    } else if (task.tap) {
                        checkDialogAndDo(function() {
                            tap($(".uiPageActive " + task.tap));
                            next();
                        });

                    } else if (task.waitFor) {
                        if (task.waitFor.implicitEvent) {
                            I.event.subscribe(task.waitFor.implicitEvent, next);
                        } else if (task.waitFor.view) {
                            var checkDialogAndDoNext = function  () {
                                checkDialogAndDo(function() {
                                    next();
                                });
                            };
                            if ($('.uiPageActive').attr("id") === task.waitFor.view) {
                                checkDialogAndDoNext();
                            } else {
                                var viewEvent = "view:complete:" + task.waitFor.view;
                                I.event.subscribe(viewEvent, checkDialogAndDoNext);
                            }
                        }
                    }
                } catch(e) {
                    console.error(task.type + ": " + task.name + " failed: " + e);
                    save(currentRunId, currTaskName, false, {
                        error: e
                    });
                }
            }
        ]);

    }
};

function checkDialogAndDo (doWhat) {
    if (I.view.dialog.getIsShowing()) {
        I.event.subscribe("dialog:hide", function() {
            doWhat();
        });
    } else {
        doWhat();
    }
}

function tap($element) {
    var isTouchDevice =  !!('ontouchstart' in window) || // works on most browsers
      !!('onmsgesturechange' in window); // works on ie10

    if (isTouchDevice) {
        console.log("before event");
        var touchStartEvent = $.Event("touchstart");
        console.log("after event");
        $element.trigger(touchStartEvent);
        console.log("touchstart");
        setTimeout(function() {
            $element.trigger($.Event("touchend"));
                    console.log("touchend");

        }, 10);
    } //else{
    setTimeout(function() {
        $element.trigger($.Event("click"));
                console.log("click");

    }, 200);
    //}
}

function save (currentRunId, taskname, result, options) {
    var json = {
        "currentRunId": currentRunId,
        "name": taskname,
        "result": result,
        "options": options
    };

    $.post("http://localhost:3002/save", json);
}

function getNewTestRunId (cb) {
    $.get("http://192.168.1.133:3002/getRunId?platform=" + I.getPlatform(), cb);
}<commit_msg>removed hard coded roomba server url<commit_after>"use strict";
var async = require("async");
var Roomba = module.exports = function(serverUrl) {
    this.tasks = []
    this.serverUrl = serverUrl
};

Roomba.prototype.tap = function(selector) {
    this.tasks.push({
        name: selector,
        type: "tap",
        tap: selector
    });
};

Roomba.prototype.start = Roomba.prototype.then = function(task) {
    this.tasks.push({
        name: task.name,
        type: "then",
        then: task
    });
};

Roomba.prototype.waitFor = function(params) {
    this.tasks.push({
        name: params.implicitEvent || params.view,
        type: "waitFor",
        waitFor: params
    });
};

Roomba.prototype.runAll = function() {
    var i = 0,
        that = this,
        currTaskName, currentRunId;
    getNewTestRunId(function(newRunId) {
        currentRunId = newRunId;
        next();
    }, this.serverUrl);
    function next() {
        if (currTaskName) {
            save(currentRunId, currTaskName, true, that.serverUrl, {
                message: currTaskName + " completed successfully"
            });
        }
        async.series([
            function aTimeout(cb) {
                setTimeout(function() {
                    cb(null);
                }, 500);
            },

            function doTask() {
                if (i === that.tasks.length) {
                    return; //end recursion
                }
                var task = that.tasks[i++];
                console.warn(task.type + ": " + task.name);
                currTaskName = task.type + ": " + task.name;

                try {
                    if (task.then) {
                        task.then();
                        next();
                    } else if (task.tap) {
                        checkDialogAndDo(function() {
                            tap($(".uiPageActive " + task.tap));
                            next();
                        });

                    } else if (task.waitFor) {
                        if (task.waitFor.implicitEvent) {
                            window.I.event.subscribe(task.waitFor.implicitEvent, next);
                        } else if (task.waitFor.view) {
                            var checkDialogAndDoNext = function  () {
                                checkDialogAndDo(function() {
                                    next();
                                });
                            };
                            if ($('.uiPageActive').attr("id") === task.waitFor.view) {
                                checkDialogAndDoNext();
                            } else {
                                var viewEvent = "view:complete:" + task.waitFor.view;
                                window.I.event.subscribe(viewEvent, checkDialogAndDoNext);
                            }
                        }
                    }
                } catch(e) {
                    console.error(task.type + ": " + task.name + " failed: " + e);
                    save(currentRunId, currTaskName, false, that.serverUrl, {
                        error: e
                    });
                }
            }
        ]);

    }
};

function checkDialogAndDo (doWhat) {
    if (window.I.view.dialog.getIsShowing()) {
        window.I.event.subscribe("dialog:hide", function() {
            doWhat();
        });
    } else {
        doWhat();
    }
}

function tap($element) {
    var isTouchDevice =  !!('ontouchstart' in window) || // works on most browsers
      !!('onmsgesturechange' in window); // works on ie10

    if (isTouchDevice) {
        var touchStartEvent = $.Event("touchstart");
        $element.trigger(touchStartEvent);
        setTimeout(function() {
            $element.trigger($.Event("touchend"));
        }, 10);
    } //else{
    setTimeout(function() {
        $element.trigger($.Event("click"));
    }, 200);
}

function save (currentRunId, taskname, result, serverUrl, options) {
    var json = {
        "currentRunId": currentRunId,
        "name": taskname,
        "result": result,
        "options": options
    };

    $.post(serverUrl + "/save", json);
}

function getNewTestRunId (cb, serverUrl) {
    $.get(serverUrl + "/getRunId?platform=" + window.I.getPlatform(), cb);
}<|endoftext|>


<commit_before>// First thing to do is to prevent Iron.Router autostart as we are creating our own
// router in globals and need to prevent Router created in Iron.Router global.js to autoStart
//on Meteor startup.
//Router.options.autoStart = false;

var assert = Iron.utils.assert;

var Url = Iron.Url;

function getEnv() {
    return Meteor.isClient ? 'client' : 'server';
}

var superConfigure = Router.configure;

var superRoute = Router.route;

var superDispatch = Router.dispatch;

configurei18n = function(options) {

    var i18nDefaults = {

        language: null,

        hooks: {

            defaultLanguage: 'en',

            serverSide: false,

            redirectCode: 301,

            autoConfLanguage: false,

            compulsoryLangCode: true,

            client: {},

            server: {},

            getLangCode: function (url) {

                var path = Url.normalize(url);

                var langCode;
                var langSepPos = path.substr(1).indexOf('/');

                if (langSepPos != -1) {
                    langCode = path.substr(1, langSepPos);
                } else {
                    langCode = path.substr(1);
                }

                if (!_.contains(this.options.i18n.languages, langCode)) {
                    langCode = null;
                }

                return langCode;
            },

            addLangCode: function (url, lang) {

                return '/' + lang + (url !== '/' ? url : '');

            },

            langCodeAction: function (langCode) {

                var lang = this.getLanguage();
                if (lang) {
                    if (langCode != lang) {
                        this.setLanguage(langCode);
                    }
                } else {
                    console.log("Can't retrieve current language on " + getEnv() + ".")
                }
            },

            getDefaultLanguage: function () {
                return this.options.i18n.defaultLanguage;
            }

        }

    };

    // First time calling "configure", default configuration
    if (!this._i18n) {
        this._i18n = i18nDefaults;
        this._i18n.languageDep = new Deps.Dependency();
        this.options.i18n = {};
    }

    var overriddenHooks = {};

    if (Meteor.isServer) {
        _.extend(this._i18n.hooks.server, this.options.i18n.server);
        overriddenHooks = this._i18n.hooks.server;
    } else {
        _.extend(this._i18n.hooks.client, this.options.i18n.client);
        overriddenHooks = this._i18n.hooks.client;

    }

    _.extend(this._i18n.hooks, this.options.i18n);
    _.extend(this.options.i18n, this._i18n.hooks);
    _.extend(this.options.i18n, overriddenHooks);

    if (!this.options.i18n.languages) {
        this.options.i18n.languages = ['en'];
    }

    return this;

};

Router.configure = function (options) {

    superConfigure.call(this, options);

    return configurei18n.call(this, options);

};

Router.route = function (path, fn, opts) {

    var typeOf = function (val) {
        return Object.prototype.toString.call(val);
    };
    assert(typeOf(path) === '[object String]' || typeOf(path) === '[object RegExp]', "Router.route requires a path that is a string or regular expression.");

    var origFn = fn;

    if (typeof fn === 'object') {
        opts = fn;
        fn = opts.action;
    }

    var defaultLanguage = this.getDefaultLanguage();
    var routerLanguages = this.options.i18n.languages;
    var defaultOptions = _.clone(opts);
    var configuredLanguageRoutes = {};
    var routeLanguages = routerLanguages;

    var pathIsName = false;
    var origPath = opts && opts.path ? opts.path : path;
    var defaultPath = origPath;
    var route;

    if (typeof path === 'string' && path.charAt(0) !== '/') {
        pathIsName = true;
        origPath = opts && opts.path ? opts.path : '/' + path;
    }

    if (this.options.i18n.addLangCode && !this.options.i18n.getLangCode.call(this, origPath)) {
        defaultPath = this.options.i18n.addLangCode.call(this, origPath, defaultLanguage);
        if (opts && opts.path) {
            opts.path = this.options.i18n.addLangCode.call(this, opts.path, defaultLanguage);
        }
    }

    if (pathIsName) {
        opts = opts || {};
        opts.path = defaultPath;
        route = superRoute.call(this, path, fn, opts);
    } else {
        route = superRoute.call(this, defaultPath, fn, opts);
    }

    route._i18n = {};
    route._i18n.routes = {};
    route._i18n.routes[defaultLanguage] = route;
    route.language = defaultLanguage;

    if (opts && opts.i18n && opts.i18n.languages) {
        configuredLanguageRoutes = opts.i18n.languages;
        if (configuredLanguageRoutes[defaultLanguage]) {
            _.extend(opts, opts.i18n.languages[defaultLanguage]);
        }
        // Whether the route should be limited to the languages explicitly configured in its i18n section (limitToConfigured == true)
        routeLanguages = opts.i18n.limitToConfigured ? _.keys(configuredLanguageRoutes) : _.difference(routerLanguages, [defaultLanguage]);
    } else {
        routeLanguages = _.difference(routerLanguages, [defaultLanguage]);
    }

    var configuredLanguageRouteCodes = _.keys(configuredLanguageRoutes);

    var self = this;

    _.each(routeLanguages, function (lang) {

        var i18n_options = _.clone(defaultOptions);
        var newPath;
        var newAction = origFn;

        if (i18n_options) {

            delete(i18n_options.path);
            delete(i18n_options.name);
            delete(i18n_options.i18n);

            if (_.contains(configuredLanguageRouteCodes, lang)) {
                _.extend(i18n_options, configuredLanguageRoutes[lang]);
            }

            newPath = i18n_options.path;
            if (i18n_options.action) {
                newAction = i18n_options.action;
                delete(i18n_options.action);
            }

        }

        if (!newPath) {
            newPath = '/' + lang + (origPath !== '/' ? origPath : '');
        }

        if (self.options.i18n.addLangCode && !self.options.i18n.getLangCode.call(self, newPath)) {
            newPath = self.options.i18n.addLangCode.call(self, newPath, lang);
        }

        if (i18n_options && i18n_options.path) {
            i18n_options.path = newPath;
        }

        var newRoute;
        if (newAction && typeof newAction === 'function') {
            newRoute = superRoute.call(self, newPath, newAction, i18n_options);
        } else {
            newRoute = superRoute.call(self, newPath, i18n_options);

        }

        newRoute._i18n = {};
        newRoute.language = lang;
        newRoute._i18n.defaultRoute = route;
        route._i18n.routes[lang] = newRoute;

    });

    return route;
};

Router.dispatch = function (url, context, done) {

    if (this.options.i18n.compulsoryLangCode) {

        var langCode;

        if (this.options.i18n.getLangCode) {
            langCode = this.options.i18n.getLangCode.call(this, url);
        }

        if (!langCode) {

            if (_.isFunction(this.options.i18n.missingLangCodeAction)) {
                if (this.options.i18n.missingLangCodeAction.call(this, url)) {
                    return;
                }
            }

        }

    }

    if (this.options.i18n.langCodeAction) {
        var route = this.findFirstRoute(url);
        if (route && route.language) {
            this.options.i18n.langCodeAction.call(this, route.language);
        }
    }

    return superDispatch.call(this, url, context, done);

};

Router.setLanguage = function (lang) {

    this._i18n.language = lang;

    if (this.options.i18n.setLanguage) {
        this.options.i18n.setLanguage.call(this, lang);
    }

    if (this.options.i18n.setLangCode) {
        this.options.i18n.setLangCode.call(this, lang);
    }

    this._i18n.languageDep.changed();
};

Router.getLanguage = function () {

    this._i18n.languageDep.depend();

    var lang = null;

    if (this.options.i18n.getLanguage) {
        lang = this.options.i18n.getLanguage.call(this);
    } else {
        lang = this._i18n.language;
    }

    if (!lang) {
        lang = this.getDefaultLanguage();
    }

    return lang;
};

Router.getLanguageDep = function () {

    return this._i18n.languageDep;

};

Router.getDefaultLanguage = function () {

    if (this.options.i18n.getDefaultLanguage) {
        return this.options.i18n.getDefaultLanguage.call(this);
    }

    return 'en';

};

Iron.Router.prototype.configure = Router.configure;
Iron.Router.prototype.route = Router.route;
Iron.Router.prototype.dispatch = Router.dispatch;
Iron.Router.prototype.setLanguage = Router.setLanguage;
Iron.Router.prototype.getLanguage = Router.getLanguage;
Iron.Router.prototype.getLanguageDep = Router.getLanguageDep;
Iron.Router.prototype.getDefaultLanguage = Router.getDefaultLanguage;<commit_msg>Add Router property to know whether the language has been explicitly set or not<commit_after>// First thing to do is to prevent Iron.Router autostart as we are creating our own
// router in globals and need to prevent Router created in Iron.Router global.js to autoStart
//on Meteor startup.
//Router.options.autoStart = false;

var assert = Iron.utils.assert;

var Url = Iron.Url;

function getEnv() {
    return Meteor.isClient ? 'client' : 'server';
}

var superConfigure = Router.configure;

var superRoute = Router.route;

var superDispatch = Router.dispatch;

configurei18n = function(options) {

    var i18nDefaults = {

        language: null,

        hooks: {

            defaultLanguage: 'en',

            serverSide: false,

            redirectCode: 301,

            autoConfLanguage: false,

            compulsoryLangCode: true,

            client: {},

            server: {},

            getLangCode: function (url) {

                var path = Url.normalize(url);

                var langCode;
                var langSepPos = path.substr(1).indexOf('/');

                if (langSepPos != -1) {
                    langCode = path.substr(1, langSepPos);
                } else {
                    langCode = path.substr(1);
                }

                if (!_.contains(this.options.i18n.languages, langCode)) {
                    langCode = null;
                }

                return langCode;
            },

            addLangCode: function (url, lang) {

                return '/' + lang + (url !== '/' ? url : '');

            },

            langCodeAction: function (langCode) {

                var lang = this.getLanguage();
                if (lang) {
                    if (langCode != lang) {
                        this.setLanguage(langCode);
                    }
                } else {
                    console.log("Can't retrieve current language on " + getEnv() + ".")
                }
            },

            getDefaultLanguage: function () {
                return this.options.i18n.defaultLanguage;
            }

        }

    };

    // First time calling "configure", default configuration
    if (!this._i18n) {
        this._i18n = i18nDefaults;
        this._i18n.languageDep = new Deps.Dependency();
        this.options.i18n = {};
        this.isLanguageSet = false;
    }

    var overriddenHooks = {};

    if (Meteor.isServer) {
        _.extend(this._i18n.hooks.server, this.options.i18n.server);
        overriddenHooks = this._i18n.hooks.server;
    } else {
        _.extend(this._i18n.hooks.client, this.options.i18n.client);
        overriddenHooks = this._i18n.hooks.client;

    }

    _.extend(this._i18n.hooks, this.options.i18n);
    _.extend(this.options.i18n, this._i18n.hooks);
    _.extend(this.options.i18n, overriddenHooks);

    if (!this.options.i18n.languages) {
        this.options.i18n.languages = ['en'];
    }

    return this;

};

Router.configure = function (options) {

    superConfigure.call(this, options);

    return configurei18n.call(this, options);

};

Router.route = function (path, fn, opts) {

    var typeOf = function (val) {
        return Object.prototype.toString.call(val);
    };
    assert(typeOf(path) === '[object String]' || typeOf(path) === '[object RegExp]', "Router.route requires a path that is a string or regular expression.");

    var origFn = fn;

    if (typeof fn === 'object') {
        opts = fn;
        fn = opts.action;
    }

    var defaultLanguage = this.getDefaultLanguage();
    var routerLanguages = this.options.i18n.languages;
    var defaultOptions = _.clone(opts);
    var configuredLanguageRoutes = {};
    var routeLanguages = routerLanguages;

    var pathIsName = false;
    var origPath = opts && opts.path ? opts.path : path;
    var defaultPath = origPath;
    var route;

    if (typeof path === 'string' && path.charAt(0) !== '/') {
        pathIsName = true;
        origPath = opts && opts.path ? opts.path : '/' + path;
    }

    if (this.options.i18n.addLangCode && !this.options.i18n.getLangCode.call(this, origPath)) {
        defaultPath = this.options.i18n.addLangCode.call(this, origPath, defaultLanguage);
        if (opts && opts.path) {
            opts.path = this.options.i18n.addLangCode.call(this, opts.path, defaultLanguage);
        }
    }

    if (pathIsName) {
        opts = opts || {};
        opts.path = defaultPath;
        route = superRoute.call(this, path, fn, opts);
    } else {
        route = superRoute.call(this, defaultPath, fn, opts);
    }

    route._i18n = {};
    route._i18n.routes = {};
    route._i18n.routes[defaultLanguage] = route;
    route.language = defaultLanguage;

    if (opts && opts.i18n && opts.i18n.languages) {
        configuredLanguageRoutes = opts.i18n.languages;
        if (configuredLanguageRoutes[defaultLanguage]) {
            _.extend(opts, opts.i18n.languages[defaultLanguage]);
        }
        // Whether the route should be limited to the languages explicitly configured in its i18n section (limitToConfigured == true)
        routeLanguages = opts.i18n.limitToConfigured ? _.keys(configuredLanguageRoutes) : _.difference(routerLanguages, [defaultLanguage]);
    } else {
        routeLanguages = _.difference(routerLanguages, [defaultLanguage]);
    }

    var configuredLanguageRouteCodes = _.keys(configuredLanguageRoutes);

    var self = this;

    _.each(routeLanguages, function (lang) {

        var i18n_options = _.clone(defaultOptions);
        var newPath;
        var newAction = origFn;

        if (i18n_options) {

            delete(i18n_options.path);
            delete(i18n_options.name);
            delete(i18n_options.i18n);

            if (_.contains(configuredLanguageRouteCodes, lang)) {
                _.extend(i18n_options, configuredLanguageRoutes[lang]);
            }

            newPath = i18n_options.path;
            if (i18n_options.action) {
                newAction = i18n_options.action;
                delete(i18n_options.action);
            }

        }

        if (!newPath) {
            newPath = '/' + lang + (origPath !== '/' ? origPath : '');
        }

        if (self.options.i18n.addLangCode && !self.options.i18n.getLangCode.call(self, newPath)) {
            newPath = self.options.i18n.addLangCode.call(self, newPath, lang);
        }

        if (i18n_options && i18n_options.path) {
            i18n_options.path = newPath;
        }

        var newRoute;
        if (newAction && typeof newAction === 'function') {
            newRoute = superRoute.call(self, newPath, newAction, i18n_options);
        } else {
            newRoute = superRoute.call(self, newPath, i18n_options);

        }

        newRoute._i18n = {};
        newRoute.language = lang;
        newRoute._i18n.defaultRoute = route;
        route._i18n.routes[lang] = newRoute;

    });

    return route;
};

Router.dispatch = function (url, context, done) {

    if (this.options.i18n.compulsoryLangCode) {

        var langCode;

        if (this.options.i18n.getLangCode) {
            langCode = this.options.i18n.getLangCode.call(this, url);
        }

        if (!langCode) {

            if (_.isFunction(this.options.i18n.missingLangCodeAction)) {
                if (this.options.i18n.missingLangCodeAction.call(this, url)) {
                    return;
                }
            }

        }

    }

    if (this.options.i18n.langCodeAction) {
        var route = this.findFirstRoute(url);
        if (route && route.language) {
            this.options.i18n.langCodeAction.call(this, route.language);
        }
    }

    return superDispatch.call(this, url, context, done);

};

Router.setLanguage = function (lang) {

    this._i18n.language = lang;

    this.isLanguageSet = true;

    if (this.options.i18n.setLanguage) {
        this.options.i18n.setLanguage.call(this, lang);
    }

    if (this.options.i18n.setLangCode) {
        this.options.i18n.setLangCode.call(this, lang);
    }

    this._i18n.languageDep.changed();
};

Router.getLanguage = function () {

    this._i18n.languageDep.depend();

    var lang = null;

    if (this.options.i18n.getLanguage) {
        lang = this.options.i18n.getLanguage.call(this);
    } else {
        lang = this._i18n.language;
    }

    if (!lang) {
        lang = this.getDefaultLanguage();
    }

    return lang;
};

Router.getLanguageDep = function () {

    return this._i18n.languageDep;

};

Router.getDefaultLanguage = function () {

    if (this.options.i18n.getDefaultLanguage) {
        return this.options.i18n.getDefaultLanguage.call(this);
    }

    return 'en';

};

Router.isLanguageSet = false;

Iron.Router.prototype.configure = Router.configure;
Iron.Router.prototype.route = Router.route;
Iron.Router.prototype.dispatch = Router.dispatch;
Iron.Router.prototype.setLanguage = Router.setLanguage;
Iron.Router.prototype.getLanguage = Router.getLanguage;
Iron.Router.prototype.getLanguageDep = Router.getLanguageDep;
Iron.Router.prototype.getDefaultLanguage = Router.getDefaultLanguage;
<|endoftext|>


<commit_before>var subscriptions = new SubsManager();

Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'loading',
	notFoundTemplate: 'notFound'
});

Router.route('/', {
	name: 'home',
	waitOn: function() {
		return subscriptions.subscribe('allOwnedMangas', Meteor.userId());
	},
	fastRender: true
});

Router.route('/missingMangas', {
	name: 'missingMangas',
	waitOn: function() {
		return subscriptions.subscribe('allMissingMangas', Meteor.userId());
	}
});

Router.route('/tome/:_id', {
	name: 'tomeDetails',
	waitOn: function() {
		return subscriptions.subscribe('tomeDetails', this.params._id);
	},
	data: function() {
		return Mangas.findOne(this.params._id);
	}
});
<commit_msg>Add mangasName in the route<commit_after>var subscriptions = new SubsManager();

Router.configure({
	layoutTemplate: 'layout',
	loadingTemplate: 'loading',
	notFoundTemplate: 'notFound'
});

Router.route('/', {
	name: 'home',
	waitOn: function() {
		return subscriptions.subscribe('allOwnedMangas', Meteor.userId());
	},
	fastRender: true
});

Router.route('/missingMangas', {
	name: 'missingMangas',
	waitOn: function() {
		return subscriptions.subscribe('allMissingMangas', Meteor.userId());
	}
});

Router.route('/:mangasName/tome/:_id', {
	name: 'tomeDetails',
	waitOn: function() {
		return subscriptions.subscribe('tomeDetails', this.params._id);
	},
	data: function() {
		return Mangas.findOne(this.params._id);
	}
});
<|endoftext|>


<commit_before>'use strict';

const net = require('net'),
      Command = require('./command'),
      constants = require('./constants'),
      magic = require('./magic'),
      assert = require('assert'),
      Contact = require('./contact'),
      _ = require('lodash'),
      prettyjson = require('prettyjson'),
      utils = require('./utils'),
      Promise = require('bluebird');

Promise.config({ longStackTrace: true })

module.exports = node => {
  return new Promise((resolve, reject) => {
    /**** PRIVATE FIELDS ****/
    const pendingRequests = [];
    const buckets = [];
    const logger = node.logger;
    /**** END PRIVATE FIELDS ****/

    /**** METHODS ****/
    /**
     * @param  {Command} command
     * @return {Promise}
     */
    const sendCommand = command => {
      const p = new Promise((resolve, reject) => {
        logger.debug(`attempting to connect to ${command.destination.ip}:${command.destination.port}`);
        const socket = net.createConnection(command.destination.port, command.destination.ip);

        socket.on('error', err => {
          //console.log('port: ', command.destination.port);
          //console.log('ip: ', command.destination.ip);
          //console.log('command:\n', command);
          logger.error(`error connecting: ${err}`);
          reject(err);
        });

        socket.on('connect', () => {
          var data = Command.encryptAndSign(command, command.destination.publicKey, node.privateKey)
          data = Command.serialize(data);
          socket.write(data, () => {
            logger.debug(`destroyed socket connecting to ${command.destination.ip}:${command.destination.port}`);
            socket.destroy();
          });
          logger.info(`sent data from ${node.ip}:${node.port} to ${command.destination.ip}:${command.destination.port}\n${prettyjson.render(command, { noColor: true })}`);

          // wait for responses to some requests
          if (_.includes(['PING', 'MESSAGE', 'FIND', 'RETRIEVE_CONTACTS'], command.command)) {
            pendingRequests.push({ command, resolve, reject });
            setTimeout(() => rejectRequest(command.id), constants.RESPONSE_TIMEOUT);
          }
        });
      });

      return p;
    };

    /**
     * @param  {string} serializedData
     */
    const handleCommand = (serializedData) => {
      var command = Command.deserialize(serializedData);
      command = Command.decrypt(command, node.privateKey);
      logger.info(`${node.id}((${node.ip}:${node.port}) received\n ${prettyjson.render(command, { noColor: true })}`);

      switch(command.command)
      {
        case 'PING':
          sendCommand(Command.createPingRes(command, node.asContact()));
          break;
        case 'MESSAGE_RESPONSE':
        case 'PING_RESPONSE':
          resolveRequest(command.id, true);
          break;
        case 'MESSAGE':
          // TODO decrypt and verify
          logger.info(`received ${command.payload.messageString}`);
          sendCommand(Command.createMessageRes(command, node.asContact()));
          break;
        case 'FIND':
          // TODO: save pubkey
          const res = Command.createFindRes(command, node.asContact());
          sendCommand(res);
          break;
        case 'FIND_RESPONSE':
          resolveRequest(command.id, command.payload.publicKey);
          break;
        case 'RETRIEVE_CONTACTS':
          logger.warn(node.id, 'retrieve contact received from', command.payload.sourceId);
          logger.debug(`${node.id} received RETRIEVE_CONTACTS`);
          var state = command.payload.state;
          var distance = magic.getDistance(state.contact.id, node.id);
          updateContactP(state.contact).then(result => {
            state.contacted[node.id] = result;
          }, wee1 => { console.log(wee1); throw wee1; }).then(() => {
            if (magic.compareKeys(distance, magic.hexToBuffer(state.closestNodeDistance)) === -1) {
              state.previousClosestNode = state.closestNode;
              state.closestNode = node.id;
              state.closestNodeDistance = magic.bufferToHex(distance);
            }

            var contacts = getNearestContacts(state.key, state.limit, node.id);

            state.contactlist = state.contactlist.concat(contacts);
            state.contactlist = _.uniqBy(state.contactlist, 'id');
            state.contactlist = state.contactlist.filter(contact => {
              return contact.id !== state.contact.id;
            });

            if(state.closestNode && state.previousClosestNode) {
              var closestNodeUnchanged = state.closestNode.id === state.previousClosestNode.id;
            } else {
              var closestNodeUnchanged = state.closestNode === state.previousClosestNode;
            }

            var contactlistFull = state.contactlist.length >= constants.K;

            if (closestNodeUnchanged || contactlistFull) {
              command.payload.state = state;
              sendCommand(Command.createRetrieveContactRes(command, node.asContact()));
              return null;
            }

            var remainingContacts = _.reject(state.contactlist, function(c) {
              return state.contacted[c.id];
            });

            console.log('remainingContacts:');
            remainingContacts.forEach(contact => {
              console.log(contact.id);
            });

            if (remainingContacts.length === 0) {
              command.payload.state = state;
              sendCommand(Command.createRetrieveContactRes(command, node.asContact()));
              return null;
            }

            return iteritiveFindP(state, remainingContacts.splice(0, constants.ALPHA)).then(resultState => {
              state = resultState;
              command.payload.state = state;
              sendCommand(Command.createRetrieveContactRes(command, node.asContact()));
              logger.debug(`${node.id} received RETRIEVE_CONTACTS actually finished`)
            });
          }, wee2 => console.log(wee2));

          break;
        case 'RETRIEVE_CONTACTS_RESPONSE':
          logger.warn(node.id, 'received contacts response');
          logger.debug(`${node.id} received RETRIEVE_CONTACTS_RESPONSE`);
          resolveRequest(command.id, command.payload.state);
          break;
      }
    };

    const updateContactP = (contact) => {

      const bucketIndex = magic.getBucketIndex(node.id, contact.id);

      logger.debug(node.id, 'is updating buckets with contact', contact.id);
      assert(bucketIndex < constants.B);

      // Eli - create new bucket :(
      if (!buckets[bucketIndex]) buckets[bucketIndex] = []; // Dan - create new bucket :)

      const bucket = buckets[bucketIndex];

      const p = Promise.resolve();

      const foundAt = _.findIndex(bucket, e => e.id === contact.id);
      if (foundAt !== -1) {
        bucket[foundAt].lastSeen = Date.now();
      } else if (bucket.length < constants.K) {
        contact.lastSeen = Date.now();
        bucket.push(contact);
      } else {
        p.then(() => {
          return node.ping(bucket[0]).then(null, result => {
            bucket.splice(0);
            bucket.push(contact);
          }, wee3 => console.log(wee3));
        }, wee4 => console.log(wee4));
      }

      p.then(() => { bucket.sort(e => e.lastSeen); }, wee5 => console.log(wee5))

      return p.then(() => { return contact }, wee6 => console.log(wee6));
    };

    const getNearestNodesP = (contactID) => {
      var state = {
        key: contactID,
        contact: node.asContact(),
        limit: constants.ALPHA,
        contacted: {},
        contactlist: [],
        previousClosestNode: null
      };

      state.contacted[node.id] = node.asContact();

      getNearestContacts(contactID, state.limit, node.id).forEach(function addContact(contact) {
        state.contactlist.push(contact);
      });
      state.closestNode = state.contactlist[0];
      state.previousClosestNode = state.closestNode;

      assert(state.closestNode, 'Not connected to any peers');
      state.closestNodeDistance = magic.bufferToHex(magic.getDistance(state.key, state.closestNode.id));

      return iteritiveFindP(state, state.contactlist).then(resultState => {
        state = resultState;
        console.log('test3')
        return Promise.all(state.contactlist.map(e => updateContactP(e)));
      }, wee7 => console.log(wee7));
    };

    const iteritiveFindP = (state, contacts) => {
      console.log(constants.test,': CONTACTS:');
      constants.test += 1;
      contacts.forEach(contact => {
        console.log(contact.id);
      });
      var p = Promise.resolve();
      contacts.forEach(contact => {
        p = p.then(() => {
          return new Promise((resolve, reject) => {
            node.retrieveContacts(contact, state).then(result => {
              state = result;
              console.log('test2')
              resolve(state);
            });
          });
        }, wee8 => console.log(wee8));
      });

      return p;
    };

    const getNearestContacts = (contactID, limit, nodeID) => {
      var contacts = [];
      var index = magic.getBucketIndex(nodeID, contactID);
      var ascBucketIndex = index;
      var descBucketIndex = index;

      function addNearestFromBucket(bucket) {
        getNearestFromBucket(bucket, contactID, limit - contacts.length).forEach(function addToContacts(contact) {
            var isContact = contact.__classId === 'CONTACT';
            var poolNotFull = contacts.length < limit;
            var notRequester = contact.nodeID !== nodeID;

            if (isContact && poolNotFull && notRequester) { contacts.push(contact); }
          });
      }

      // add contacts from current bucket
      addNearestFromBucket(buckets[index]);

      // add contacts while decrementing counter
      while (contacts.length < limit && ascBucketIndex < constants.B) { ascBucketIndex++; addNearestFromBucket(buckets[ascBucketIndex]); }

      // add contacts while incrementing counter
      while (contacts.length < limit && descBucketIndex >= 0) { descBucketIndex--; addNearestFromBucket(buckets[descBucketIndex]); }

      return contacts;
    };

    const getNearestFromBucket = (bucket, key, limit) => {
      if (!bucket) { return []; }

      var nearest = bucket.map(function addDistance(contact) {
        return { contact: contact, distance: magic.getDistance(contact.id, key) };
      }).sort(function sortKeysByDistance(a, b) {
        return magic.compareKeys(a.distance, b.distance);
      }).splice(0, limit).map(function pluckContact(c) {
        return c.contact;
      });

      return nearest;
    };

    const refreshBucketsBeyondClosestP = (shortlist) => {
      var bucketIndexes = Object.keys(buckets);
      var leastBucket = _.min(bucketIndexes);

      function bucketFilter(index) { return index > leastBucket; }

      var refreshBuckets = bucketIndexes.filter(bucketFilter);

      logger.debug('refreshing buckets farthest than closest known');

      var p = Promise.resolve();

      refreshBuckets.forEach(index => {
        p = p.then(() => {
          return refreshBucketP(index)
        }, wee9 => console.log(wee9));
      });

      p.then(r => {
        console.log('refreshed all buckets')
        return r
      })

      return p;
    };

    const refreshBucketP = (index) => {
      var random = magic.getRandomInBucketRangeBuffer(index);
      console.log('calling getNearestNodesP from refreshBucket')
      return getNearestNodesP(random.toString('hex')).then(r => {
        console.log('finished getNearestNodesP in refreshBucket')
        return r
      });
    };

    /**** END METHODS ****/

    /**** HELPER METHODS ****/
    const completeRequest = (id, f) => {
      const i = _.findIndex(pendingRequests, e => e.command.id === id);
      if (i !== -1) {
        f(pendingRequests[i]);
        pendingRequests.splice(i, 1);
      }
    };
    const rejectRequest = id => completeRequest(id, request => request.reject(new Error('timeout')));
    const resolveRequest = (id, result) => completeRequest(id, request => request.resolve(result));
    /**** END HELPER METHODS ****/

    /**** CONSTRUCTOR ****/
    // create listening server
    const server = net.createServer(socket => {
      const addr = socket.remoteAddress;
      const port = socket.remotePort;

      logger.debug(`${node.id}((${node.ip}:${node.port}) connection from ${addr}:${port}`);

      socket.on('data', data => handleCommand(data));
      socket.on('close', () => logger.debug(`${node.id}(${node.ip}:${node.port}) closed connection with ${addr}:${port}`));
    });

    // reject promise if listening server fails to start
    server.on('error', err => reject(err));

    // resolve only after listening server has started
    server.on('listening', () => {
      resolve({
        close: (cb) => {
          server.close(() => {
            logger.debug(`stopped listening on ${node.ip}:${node.port}`);
            if (_.isFunction(cb)) cb();
          });
        },
        sendCommand,
        updateContactP,
        getNearestNodesP,
        refreshBucketsBeyondClosestP,
        _getNearestContacts: getNearestContacts,
        _buckets: buckets
      });
    });

    // attempt to start listening server
    logger.debug(`${node.id} is attempting to listen on ${node.ip}:${node.port}`);
    server.listen(node.port, node.ip);
    /**** END CONSTRUCTOR ****/
  });
};<commit_msg>death to the Wees<commit_after>'use strict';

const net = require('net'),
      Command = require('./command'),
      constants = require('./constants'),
      magic = require('./magic'),
      assert = require('assert'),
      Contact = require('./contact'),
      _ = require('lodash'),
      prettyjson = require('prettyjson'),
      utils = require('./utils'),
      Promise = require('bluebird');

Promise.config({ longStackTrace: true })

module.exports = node => {
  return new Promise((resolve, reject) => {
    /**** PRIVATE FIELDS ****/
    const pendingRequests = [];
    const buckets = [];
    const logger = node.logger;
    /**** END PRIVATE FIELDS ****/

    /**** METHODS ****/
    /**
     * @param  {Command} command
     * @return {Promise}
     */
    const sendCommand = command => {
      const p = new Promise((resolve, reject) => {
        logger.debug(`attempting to connect to ${command.destination.ip}:${command.destination.port}`);
        const socket = net.createConnection(command.destination.port, command.destination.ip);

        socket.on('error', err => {
          logger.error(`error connecting: ${err}`);
          reject(err);
        });

        socket.on('connect', () => {
          var data = Command.encryptAndSign(command, command.destination.publicKey, node.privateKey)
          data = Command.serialize(data);
          socket.write(data, () => {
            logger.debug(`destroyed socket connecting to ${command.destination.ip}:${command.destination.port}`);
            socket.destroy();
          });
          logger.info(`sent data from ${node.ip}:${node.port} to ${command.destination.ip}:${command.destination.port}\n${prettyjson.render(command, { noColor: true })}`);

          // wait for responses to some requests
          if (_.includes(['PING', 'MESSAGE', 'FIND', 'RETRIEVE_CONTACTS'], command.command)) {
            pendingRequests.push({ command, resolve, reject });
            setTimeout(() => rejectRequest(command.id), constants.RESPONSE_TIMEOUT);
          }
        });
      });

      return p;
    };

    /**
     * @param  {string} serializedData
     */
    const handleCommand = (serializedData) => {
      var command = Command.deserialize(serializedData);
      command = Command.decrypt(command, node.privateKey);
      logger.info(`${node.id}((${node.ip}:${node.port}) received\n ${prettyjson.render(command, { noColor: true })}`);

      switch(command.command)
      {
        case 'PING':
          sendCommand(Command.createPingRes(command, node.asContact()));
          break;
        case 'MESSAGE_RESPONSE':
        case 'PING_RESPONSE':
          resolveRequest(command.id, true);
          break;
        case 'MESSAGE':
          // TODO decrypt and verify
          logger.info(`received ${command.payload.messageString}`);
          sendCommand(Command.createMessageRes(command, node.asContact()));
          break;
        case 'FIND':
          // TODO: save pubkey
          const res = Command.createFindRes(command, node.asContact());
          sendCommand(res);
          break;
        case 'FIND_RESPONSE':
          resolveRequest(command.id, command.payload.publicKey);
          break;
        case 'RETRIEVE_CONTACTS':
          logger.warn(node.id, 'retrieve contact received from', command.payload.sourceId);
          logger.debug(`${node.id} received RETRIEVE_CONTACTS`);
          var state = command.payload.state;
          var distance = magic.getDistance(state.contact.id, node.id);
          updateContactP(state.contact).then(result => {
            state.contacted[node.id] = result;
          }).then(() => {
            if (magic.compareKeys(distance, magic.hexToBuffer(state.closestNodeDistance)) === -1) {
              state.previousClosestNode = state.closestNode;
              state.closestNode = node.id;
              state.closestNodeDistance = magic.bufferToHex(distance);
            }

            var contacts = getNearestContacts(state.key, state.limit, node.id);

            state.contactlist = state.contactlist.concat(contacts);
            state.contactlist = _.uniqBy(state.contactlist, 'id');
            state.contactlist = state.contactlist.filter(contact => {
              return contact.id !== state.contact.id;
            });

            if(state.closestNode && state.previousClosestNode) {
              var closestNodeUnchanged = state.closestNode.id === state.previousClosestNode.id;
            } else {
              var closestNodeUnchanged = state.closestNode === state.previousClosestNode;
            }

            var contactlistFull = state.contactlist.length >= constants.K;

            if (closestNodeUnchanged || contactlistFull) {
              command.payload.state = state;
              sendCommand(Command.createRetrieveContactRes(command, node.asContact()));
              return null;
            }

            var remainingContacts = _.reject(state.contactlist, function(c) {
              return state.contacted[c.id];
            });

            if (remainingContacts.length === 0) {
              command.payload.state = state;
              sendCommand(Command.createRetrieveContactRes(command, node.asContact()));
              return null;
            }

            return iteritiveFindP(state, remainingContacts.splice(0, constants.ALPHA)).then(resultState => {
              state = resultState;
              command.payload.state = state;
              sendCommand(Command.createRetrieveContactRes(command, node.asContact()));
              logger.debug(`${node.id} received RETRIEVE_CONTACTS actually finished`)
            });
          });

          break;
        case 'RETRIEVE_CONTACTS_RESPONSE':
          logger.warn(node.id, 'received contacts response');
          logger.debug(`${node.id} received RETRIEVE_CONTACTS_RESPONSE`);
          resolveRequest(command.id, command.payload.state);
          break;
      }
    };

    const updateContactP = (contact) => {

      const bucketIndex = magic.getBucketIndex(node.id, contact.id);

      logger.debug(node.id, 'is updating buckets with contact', contact.id);
      assert(bucketIndex < constants.B);

      // Eli - create new bucket :(
      if (!buckets[bucketIndex]) buckets[bucketIndex] = []; // Dan - create new bucket :)

      const bucket = buckets[bucketIndex];

      const p = Promise.resolve();

      const foundAt = _.findIndex(bucket, e => e.id === contact.id);
      if (foundAt !== -1) {
        bucket[foundAt].lastSeen = Date.now();
      } else if (bucket.length < constants.K) {
        contact.lastSeen = Date.now();
        bucket.push(contact);
      } else {
        p.then(() => {
          return node.ping(bucket[0]).then(null, result => {
            bucket.splice(0);
            bucket.push(contact);
          });
        });
      }

      p.then(() => { bucket.sort(e => e.lastSeen); })

      return p.then(() => { return contact });
    };

    const getNearestNodesP = (contactID) => {
      var state = {
        key: contactID,
        contact: node.asContact(),
        limit: constants.ALPHA,
        contacted: {},
        contactlist: [],
        previousClosestNode: null
      };

      state.contacted[node.id] = node.asContact();

      getNearestContacts(contactID, state.limit, node.id).forEach(function addContact(contact) {
        state.contactlist.push(contact);
      });
      state.closestNode = state.contactlist[0];
      state.previousClosestNode = state.closestNode;

      assert(state.closestNode, 'Not connected to any peers');
      state.closestNodeDistance = magic.bufferToHex(magic.getDistance(state.key, state.closestNode.id));

      return iteritiveFindP(state, state.contactlist).then(resultState => {
        state = resultState;
        return Promise.all(state.contactlist.map(e => updateContactP(e)));
      });
    };

    const iteritiveFindP = (state, contacts) => {
      var p = Promise.resolve();
      contacts.forEach(contact => {
        p = p.then(() => {
          return new Promise((resolve, reject) => {
            node.retrieveContacts(contact, state).then(result => {
              state = result;
              resolve(state);
            });
          });
        });
      });

      return p;
    };

    const getNearestContacts = (contactID, limit, nodeID) => {
      var contacts = [];
      var index = magic.getBucketIndex(nodeID, contactID);
      var ascBucketIndex = index;
      var descBucketIndex = index;

      function addNearestFromBucket(bucket) {
        getNearestFromBucket(bucket, contactID, limit - contacts.length).forEach(function addToContacts(contact) {
            var isContact = contact.__classId === 'CONTACT';
            var poolNotFull = contacts.length < limit;
            var notRequester = contact.nodeID !== nodeID;

            if (isContact && poolNotFull && notRequester) { contacts.push(contact); }
          });
      }

      // add contacts from current bucket
      addNearestFromBucket(buckets[index]);

      // add contacts while decrementing counter
      while (contacts.length < limit && ascBucketIndex < constants.B) { ascBucketIndex++; addNearestFromBucket(buckets[ascBucketIndex]); }

      // add contacts while incrementing counter
      while (contacts.length < limit && descBucketIndex >= 0) { descBucketIndex--; addNearestFromBucket(buckets[descBucketIndex]); }

      return contacts;
    };

    const getNearestFromBucket = (bucket, key, limit) => {
      if (!bucket) { return []; }

      var nearest = bucket.map(function addDistance(contact) {
        return { contact: contact, distance: magic.getDistance(contact.id, key) };
      }).sort(function sortKeysByDistance(a, b) {
        return magic.compareKeys(a.distance, b.distance);
      }).splice(0, limit).map(function pluckContact(c) {
        return c.contact;
      });

      return nearest;
    };

    const refreshBucketsBeyondClosestP = (shortlist) => {
      var bucketIndexes = Object.keys(buckets);
      var leastBucket = _.min(bucketIndexes);

      function bucketFilter(index) { return index > leastBucket; }

      var refreshBuckets = bucketIndexes.filter(bucketFilter);

      logger.debug('refreshing buckets farthest than closest known');

      var p = Promise.resolve();

      refreshBuckets.forEach(index => {
        p = p.then(() => {
          return refreshBucketP(index)
        });
      });

      p.then(r => {
        logger.debug('refreshed all buckets');
        return r
      })

      return p;
    };

    const refreshBucketP = (index) => {
      var random = magic.getRandomInBucketRangeBuffer(index);
      return getNearestNodesP(random.toString('hex')).then(r => {
        return r
      });
    };

    /**** END METHODS ****/

    /**** HELPER METHODS ****/
    const completeRequest = (id, f) => {
      const i = _.findIndex(pendingRequests, e => e.command.id === id);
      if (i !== -1) {
        f(pendingRequests[i]);
        pendingRequests.splice(i, 1);
      }
    };
    const rejectRequest = id => completeRequest(id, request => request.reject(new Error('timeout')));
    const resolveRequest = (id, result) => completeRequest(id, request => request.resolve(result));
    /**** END HELPER METHODS ****/

    /**** CONSTRUCTOR ****/
    // create listening server
    const server = net.createServer(socket => {
      const addr = socket.remoteAddress;
      const port = socket.remotePort;

      logger.debug(`${node.id}((${node.ip}:${node.port}) connection from ${addr}:${port}`);

      socket.on('data', data => handleCommand(data));
      socket.on('close', () => logger.debug(`${node.id}(${node.ip}:${node.port}) closed connection with ${addr}:${port}`));
    });

    // reject promise if listening server fails to start
    server.on('error', err => reject(err));

    // resolve only after listening server has started
    server.on('listening', () => {
      resolve({
        close: (cb) => {
          server.close(() => {
            logger.debug(`stopped listening on ${node.ip}:${node.port}`);
            if (_.isFunction(cb)) cb();
          });
        },
        sendCommand,
        updateContactP,
        getNearestNodesP,
        refreshBucketsBeyondClosestP,
        _getNearestContacts: getNearestContacts,
        _buckets: buckets
      });
    });

    // attempt to start listening server
    logger.debug(`${node.id} is attempting to listen on ${node.ip}:${node.port}`);
    server.listen(node.port, node.ip);
    /**** END CONSTRUCTOR ****/
  });
};<|endoftext|>


<commit_before>Router.configure({
  layoutTemplate: 'layout',
  loadingTemplate: 'loading',
  notFoundTemplate: 'notFound',
  waitOn: function() {
    return [Meteor.subscribe('notifications')]
  }
});

Router.route('/posts/:_id', {
  name: 'postPage',
  waitOn: function() {
    return Meteor.subscribe('comments', this.params._id);
  },
  data: function() { return Posts.findOne(this.params._id); }
});

Router.route('/posts/:_id/edit', {
  name: 'postEdit',
  data: function() { return Posts.findOne(this.params._id); }
});

Router.route('/submit', {name: 'postSubmit'});

Router.route('/:postsLimit?', {
  name: 'postsList',
  waitOn: function() {
    var limit = parseInt(this.params.postsLimit) || 5;
    return Meteor.subscribe('posts', {sort: {submitted: -1}, limit: limit});
  },
  data: function() {
    var limit = parseInt(this.params.postsLimit) || 5;
    return {
      posts: Posts.find({}, {sort: {submitted: -1}, limit: limit})
    };
  }
});

var requireLogin = function() {
  if (! Meteor.user()) {
    if (Meteor.loggingIn()) {
      this.render(this.loadingTemplate);
    } else {
      this.render('accessDenied');
    }
  } else {
    this.next();
  }
}

Router.onBeforeAction('dataNotFound', {only: 'postPage'});
Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
<commit_msg>Refactored postsLists route into a RouteController<commit_after>Router.configure({
  layoutTemplate: 'layout',
  loadingTemplate: 'loading',
  notFoundTemplate: 'notFound',
  waitOn: function() {
    return [Meteor.subscribe('notifications')]
  }
});

PostsListController = RouteController.extend({
  template: 'postsList',
  increment: 5,
  postsLimit: function() {
    return parseInt(this.params.postsLimit) || this.increment;
  },
  findOptions: function() {
    return {sort: {submitted: -1}, limit: this.postsLimit()};
  },
  waitOn: function() {
    return Meteor.subscribe('posts', this.findOptions());
  },
  data: function() {
    return {posts: Posts.find({}, this.findOptions())};
  }
});

Router.route('/posts/:_id', {
  name: 'postPage',
  waitOn: function() {
    return Meteor.subscribe('comments', this.params._id);
  },
  data: function() { return Posts.findOne(this.params._id); }
});

Router.route('/posts/:_id/edit', {
  name: 'postEdit',
  data: function() { return Posts.findOne(this.params._id); }
});

Router.route('/submit', {name: 'postSubmit'});

Router.route('/:postsLimit?', {
  name: 'postsList'
});

var requireLogin = function() {
  if (! Meteor.user()) {
    if (Meteor.loggingIn()) {
      this.render(this.loadingTemplate);
    } else {
      this.render('accessDenied');
    }
  } else {
    this.next();
  }
}

Router.onBeforeAction('dataNotFound', {only: 'postPage'});
Router.onBeforeAction(requireLogin, {only: 'postSubmit'});
<|endoftext|>