<commit_before>PubsubModel = function() {
  this.metricsByMinute = {};
  this.subscriptions = {};
}

PubsubModel.prototype._trackSub = function(session, msg) {
  console.log('SUB:', session.id, msg.id, msg.name, msg.params);
  var publication = msg.name;
  var subscriptionId = msg.id;
  var timestamp = Apm.syncedDate.getTime();
  var metrics = this._getMetrics(timestamp, publication);

  metrics.subs++;
  this.subscriptions[msg.id] = {
    startTime: timestamp,
    publication: publication,
    params: msg.params,
    id: msg.id
  };
};

PubsubModel.prototype._trackUnsub = function(session, sub) {
  console.log('UNSUB:', session.id, sub._subscriptionId);
  var publication = sub._name;
  var subscriptionId = sub._subscriptionId;
  var subscriptionState = this.subscriptions[subscriptionId];

  //sometime, we don't have these states
  if(subscriptionState) {
    var timestamp = Apm.syncedDate.getTime();
    var metrics = this._getMetrics(timestamp, publication);

    //track the count
    metrics.unsubs++;
    //use the current date to get the lifeTime of the subscription
    metrics.lifeTime += timestamp - this.subscriptions[subscriptionId].startTime;
    delete this.subscriptions[subscriptionId];
  }
};

PubsubModel.prototype._trackReady = function(session, sub) {
  console.log('READY:', session.id, sub._subscriptionId);
  //use the current time to track the response time
  var publication = sub._name;
  var subscriptionId = sub._subscriptionId;
  var timestamp = Apm.syncedDate.getTime();
  var metrics = this._getMetrics(timestamp, publication);

  metrics.resTime += timestamp - this.subscriptions[subscriptionId].startTime;
};

PubsubModel.prototype._trackDataImpact = function(session, sub, event, collection, id, fields) {
  console.log('DI:' + event, session.id, sub._subscriptionId, collection, id, fields);
  if(event != 'removed' && _.keys(fields).length > 0) {
    var publication = sub._name;
    var subscriptionId = sub._subscriptionId;
    var timestamp = Apm.syncedDate.getTime();
    var metrics = this._getMetrics(timestamp, publication);

    metrics.dataImpact += Buffer.byteLength(JSON.stringify(fields));
  }
};

PubsubModel.prototype._getMetrics = function(timestamp, publication) {
  var date = new Date(timestamp);
  var dateId = date.getHours() + ':' + date.getMinutes();

  this.metricsByMinute[dateId] = this.metricsByMinute[dateId] || {
    startTime: timestamp,
    pubs: {}
  };

  this.metricsByMinute[dateId].pubs[publication] = this.metricsByMinute[dateId].pubs[publication] || {
    subs: 0,
    unsubs: 0,
    resTime: 0,
    dataImpact: 0,
    count: 0,
    lifeTime: 0
  };

  return this.metricsByMinute[dateId].pubs[publication];
};

PubsubModel.prototype.buildPayload = function(buildDetailInfo) {
  var metricsByMinute = this.metricsByMinute;
  this.metricsByMinute = {};

  var payload = {
    pubMetrics: []
  };

  //to the averaging
  for(var dateId in metricsByMinute) {
    for(var publication in metricsByMinute[dateId].pubs) {
      var singlePubMetrics = metricsByMinute[dateId].pubs[publication];
      //we only calculate resTime for new subscriptions only
      singlePubMetrics.resTime /= singlePubMetrics.subs;
      //we only track lifeTime in the unsubs
      singlePubMetrics.lifeTime /= singlePubMetrics.unsubs;
    }

    payload.pubMetrics.push(metricsByMinute[dateId]);
  }

  return payload;
};<commit_msg>check local subscriptionState inside the ._trackReady()<commit_after>PubsubModel = function() {
  this.metricsByMinute = {};
  this.subscriptions = {};
}

PubsubModel.prototype._trackSub = function(session, msg) {
  console.log('SUB:', session.id, msg.id, msg.name, msg.params);
  var publication = msg.name;
  var subscriptionId = msg.id;
  var timestamp = Apm.syncedDate.getTime();
  var metrics = this._getMetrics(timestamp, publication);

  metrics.subs++;
  this.subscriptions[msg.id] = {
    startTime: timestamp,
    publication: publication,
    params: msg.params,
    id: msg.id
  };
};

PubsubModel.prototype._trackUnsub = function(session, sub) {
  console.log('UNSUB:', session.id, sub._subscriptionId);
  var publication = sub._name;
  var subscriptionId = sub._subscriptionId;
  var subscriptionState = this.subscriptions[subscriptionId];

  //sometime, we don't have these states
  if(subscriptionState) {
    var timestamp = Apm.syncedDate.getTime();
    var metrics = this._getMetrics(timestamp, publication);

    //track the count
    metrics.unsubs++;
    //use the current date to get the lifeTime of the subscription
    metrics.lifeTime += timestamp - subscriptionState.startTime;

    //this is place we can clean the subscriptionState if exists
    delete this.subscriptions[subscriptionId];
  }
};

PubsubModel.prototype._trackReady = function(session, sub) {
  console.log('READY:', session.id, sub._subscriptionId);
  //use the current time to track the response time
  var publication = sub._name;
  var subscriptionId = sub._subscriptionId;
  var timestamp = Apm.syncedDate.getTime();
  var metrics = this._getMetrics(timestamp, publication);

  var subscriptionState = this.subscriptions[subscriptionId];
  if(subscriptionState) {
    metrics.resTime += timestamp - subscriptionState.startTime;
  }
};

PubsubModel.prototype._trackDataImpact = function(session, sub, event, collection, id, fields) {
  console.log('DI:' + event, session.id, sub._subscriptionId, collection, id, fields);
  if(event != 'removed' && _.keys(fields).length > 0) {
    var publication = sub._name;
    var subscriptionId = sub._subscriptionId;
    var timestamp = Apm.syncedDate.getTime();
    var metrics = this._getMetrics(timestamp, publication);

    metrics.dataImpact += Buffer.byteLength(JSON.stringify(fields));
  }
};

PubsubModel.prototype._getMetrics = function(timestamp, publication) {
  var date = new Date(timestamp);
  var dateId = date.getHours() + ':' + date.getMinutes();

  this.metricsByMinute[dateId] = this.metricsByMinute[dateId] || {
    startTime: timestamp,
    pubs: {}
  };

  this.metricsByMinute[dateId].pubs[publication] = this.metricsByMinute[dateId].pubs[publication] || {
    subs: 0,
    unsubs: 0,
    resTime: 0,
    dataImpact: 0,
    count: 0,
    lifeTime: 0
  };

  return this.metricsByMinute[dateId].pubs[publication];
};

PubsubModel.prototype.buildPayload = function(buildDetailInfo) {
  var metricsByMinute = this.metricsByMinute;
  this.metricsByMinute = {};

  var payload = {
    pubMetrics: []
  };

  //to the averaging
  for(var dateId in metricsByMinute) {
    for(var publication in metricsByMinute[dateId].pubs) {
      var singlePubMetrics = metricsByMinute[dateId].pubs[publication];
      //we only calculate resTime for new subscriptions only
      singlePubMetrics.resTime /= singlePubMetrics.subs;
      //we only track lifeTime in the unsubs
      singlePubMetrics.lifeTime /= singlePubMetrics.unsubs;
    }

    payload.pubMetrics.push(metricsByMinute[dateId]);
  }

  return payload;
};<|endoftext|>


<commit_before>import a11y from './a11y';
import keys from './keys';
import location from './location';
import next from './next';
import progress from './progress';
import scale from './scale';
import timer from './timer';
import title from './title';
import view from './view';

export default (shower) => {
    a11y(shower);
    keys(shower); // should come before `timer`
    progress(shower);
    next(shower); // should come before `timer`
    timer(shower);
    title(shower); // should come before `location`
    location(shower);
    view(shower);
    scale(shower);

    // maintains invariant: active slide always exists in `full` mode
    if (shower.isFullMode && !shower.activeSlide) {
        shower.first();
    }
};
<commit_msg>Clarify comments on module order<commit_after>import a11y from './a11y';
import keys from './keys';
import location from './location';
import next from './next';
import progress from './progress';
import scale from './scale';
import timer from './timer';
import title from './title';
import view from './view';

export default (shower) => {
    a11y(shower);
    keys(shower);
    progress(shower);
    next(shower);
    timer(shower); // should come after `keys` and `next`
    title(shower);
    location(shower); // should come after `title`
    view(shower);
    scale(shower); // should come after `view`

    // maintains invariant: active slide always exists in `full` mode
    if (shower.isFullMode && !shower.activeSlide) {
        shower.first();
    }
};
<|endoftext|>


<commit_before>/**
 * jQuery Async Me
 * Un plug-in jQuery pour charger de manière asynchrone vos contenu multimédia.
 * http://github.com/Jok3rDev/async-me
 *
 * Licensed under the MIT license.
 * Copyright 2016 Fabrice Trimech
 * https://github.com/Jok3rDev/
 */

(function($) {
  $.fn.asyncMe = function() {
    return this;
  }
})(window.jQuery);
<commit_msg>Affectation de this à la variable $element<commit_after>/**
 * jQuery Async Me
 * Un plug-in jQuery pour charger de manière asynchrone vos contenu multimédia.
 * http://github.com/Jok3rDev/async-me
 *
 * Licensed under the MIT license.
 * Copyright 2016 Fabrice Trimech
 * https://github.com/Jok3rDev/
 */

(function($) {
  $.fn.asyncMe = function() {
    console.log('Le plug-in est exécuté');
    var $element = this;
    console.log($element);
    return this;
  }
})(window.jQuery);
<|endoftext|>


<commit_before>(function($) {
	var defaults = {
		'stripeType': 'skew',
		'stripeSize': '16px',
		'stripeSpeed': '60s',
	};
	var initialized = false;

	function init() {
		if (! initialized) {
			initialized = true;
			$('head').append('');
		}
	}

	function syncScroll (sender) {
		var t = $(sender.contentWindow).scrollTop();
		$(sender).siblings().each(function() {
			$(this.contentWindow).scrollTop(t);
		});
	}

	function registerScrollSync(iframe) {
		$(iframe.contentWindow).scroll(function() { syncScroll(iframe); });
	}

	function styleCover($cover, type, size, speed) {
		var deg, px, duration;
		switch (type) {
			case 'v':
			case 'vertical':
				deg = '';
				break;
			case 'h':
			case 'horizontal':
				deg = '-90deg';
				break;
			case 'skew':
			default:
				deg = '-45deg';
				break;
		}
		px = size ? size : '16px';
		duration = speed ? speed : '60s';
		$cover.css({
			'mix-blend-mode': 'multiply',
			'background-image': 'linear-gradient(' + deg + ', #fff 25%, #000 25%, #000 50%, #fff 50%, #fff 75%, #000 75%, #000)',
			'background-size': px + ' ' + px,
			'animation': 'diffpageAnim ' + duration + ' linear infinite'
		});
	}

	var methods = {
		'init': function(options) {
			init();
			var opts = $.extend(true, {}, defaults, options);
			return this.each(function() {
				var $this = $(this);
				var $ifOld = $('');
				var $ifNew = $('');
				var $ifOrg = $('');
				var $cover = $('
');
				var ifCss = {
					'position':'absolute',
					'width': '100%',
					'height': '100%',
					'top': '0',
					'left': '0',
					'border': 'none',
				};
				$this.html('');
				$this.css({
					'position' : 'relative',
					'overflow' : 'hidden'
				});
				$ifOld.css($.extend({}, ifCss, {'background-color':'#fff'}));
				$ifNew.css($.extend({}, ifCss, {'background-color':'#fff', 'mix-blend-mode':'difference'}));
				$ifOrg.css($.extend({}, ifCss, {'background-color':'#fff', 'mix-blend-mode':'difference'}));
				$cover.css(ifCss);
				styleCover($cover, opts.stripeType, opts.stripeSize, opts.stripeSpeed);
				$this.append($ifOld);
				$this.append($ifNew);
				$this.append($cover);
				$this.append($ifOrg);
				$this.children('iframe').on('load', function() {
					registerScrollSync(this);
				});
				$ifOld.attr('src', opts.oldUrl);
				$ifNew.attr('src', opts.newUrl);
				$ifOrg.attr('src', opts.newUrl);
			});
		},
		'mode': function(mode) {
			return this.each(function() {
				var $this = $(this);
				var $ifOld = $this.children('.diffpageOld');
				var $ifNew = $this.children('.diffpageNew');
				var $ifOrg = $this.children('.diffpageOrg');
				var $cover = $this.children('.diffpageCover');
				switch (mode) {
					case 'old':
						$ifOld.show();
						$ifNew.hide();
						$cover.hide();
						$ifOrg.hide();
						break;
					case 'new':
						$ifOld.hide();
						$ifNew.show();
						$cover.hide();
						$ifOrg.hide();
						break;
					case 'diff':
						$ifOld.show();
						$ifNew.show();
						$cover.hide();
						$ifOrg.hide();
						break;
					default:
						$ifOld.show();
						$ifNew.show();
						$cover.show();
						$ifOrg.show();
						break;
				}
			});
		}
	};

	$.fn.diffpage = function(method) {
		if (methods[method]) {
			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
		}
		else if (typeof method === 'object' || ! method) {
			return methods.init.apply(this, arguments);
		}
		else {
			$.error('jQuery.diffpage: Method does not exists');
		}
	};
})(jQuery);
<commit_msg>Fixed stripes type<commit_after>(function($) {
	var defaults = {
		'stripeType': 'skew',
		'stripeSize': '16px',
		'stripeSpeed': '60s',
	};
	var initialized = false;

	function init() {
		if (! initialized) {
			initialized = true;
			$('head').append('');
		}
	}

	function syncScroll (sender) {
		var t = $(sender.contentWindow).scrollTop();
		$(sender).siblings().each(function() {
			$(this.contentWindow).scrollTop(t);
		});
	}

	function registerScrollSync(iframe) {
		$(iframe.contentWindow).scroll(function() { syncScroll(iframe); });
	}

	function styleCover($cover, type, size, speed) {
		var deg, px, duration;
		switch (type) {
			case 'v':
			case 'vertical':
				deg = '-90deg';
				break;
			case 'h':
			case 'horizontal':
				deg = '0deg';
				break;
			case 'skew':
			default:
				deg = '-45deg';
				break;
		}
		px = size ? size : '16px';
		duration = speed ? speed : '60s';
		$cover.css({
			'mix-blend-mode': 'multiply',
			'background-image': 'linear-gradient(' + deg + ', #fff 25%, #000 25%, #000 50%, #fff 50%, #fff 75%, #000 75%, #000)',
			'background-size': px + ' ' + px,
			'animation': 'diffpageAnim ' + duration + ' linear infinite'
		});
	}

	var methods = {
		'init': function(options) {
			init();
			var opts = $.extend(true, {}, defaults, options);
			return this.each(function() {
				var $this = $(this);
				var $ifOld = $('');
				var $ifNew = $('');
				var $ifOrg = $('');
				var $cover = $('
');
				var ifCss = {
					'position':'absolute',
					'width': '100%',
					'height': '100%',
					'top': '0',
					'left': '0',
					'border': 'none',
				};
				$this.html('');
				$this.css({
					'position' : 'relative',
					'overflow' : 'hidden'
				});
				$ifOld.css($.extend({}, ifCss, {'background-color':'#fff'}));
				$ifNew.css($.extend({}, ifCss, {'background-color':'#fff', 'mix-blend-mode':'difference'}));
				$ifOrg.css($.extend({}, ifCss, {'background-color':'#fff', 'mix-blend-mode':'difference'}));
				$cover.css(ifCss);
				styleCover($cover, opts.stripeType, opts.stripeSize, opts.stripeSpeed);
				$this.append($ifOld);
				$this.append($ifNew);
				$this.append($cover);
				$this.append($ifOrg);
				$this.children('iframe').on('load', function() {
					registerScrollSync(this);
				});
				$ifOld.attr('src', opts.oldUrl);
				$ifNew.attr('src', opts.newUrl);
				$ifOrg.attr('src', opts.newUrl);
			});
		},
		'mode': function(mode) {
			return this.each(function() {
				var $this = $(this);
				var $ifOld = $this.children('.diffpageOld');
				var $ifNew = $this.children('.diffpageNew');
				var $ifOrg = $this.children('.diffpageOrg');
				var $cover = $this.children('.diffpageCover');
				switch (mode) {
					case 'old':
						$ifOld.show();
						$ifNew.hide();
						$cover.hide();
						$ifOrg.hide();
						break;
					case 'new':
						$ifOld.hide();
						$ifNew.show();
						$cover.hide();
						$ifOrg.hide();
						break;
					case 'diff':
						$ifOld.show();
						$ifNew.show();
						$cover.hide();
						$ifOrg.hide();
						break;
					default:
						$ifOld.show();
						$ifNew.show();
						$cover.show();
						$ifOrg.show();
						break;
				}
			});
		}
	};

	$.fn.diffpage = function(method) {
		if (methods[method]) {
			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
		}
		else if (typeof method === 'object' || ! method) {
			return methods.init.apply(this, arguments);
		}
		else {
			$.error('jQuery.diffpage: Method does not exists');
		}
	};
})(jQuery);
<|endoftext|>


<commit_before>'use strict';

const async = require('async');
const assert = require('assert');
const crypto = require('crypto');
const storj = require('storj-lib');
const MonitorConfig = require('./config');
const Storage = require('storj-service-storage-models');
const ComplexClient = require('storj-complex').createClient;
const MongoDBStorageAdapter = require('storj-mongodb-adapter');
const ms = require('ms');
const log = require('../logger');
const errors = require('storj-service-error-types');

/**
 * A long running daemon that will monitor farmers uptime and will replace
 * contracts associated with a farmer once the farmer is confirmed to be
 * offline for a duration of time.
 * @param {MonitorConfig} config - An instance of MonitorConfig
 */
function Monitor(config) {
  if (!(this instanceof Monitor)) {
    return new Monitor(config);
  }

  assert(config instanceof MonitorConfig, 'Invalid config supplied');

  this.storage = null;
  this.network = null;
  this.contracts = null;

  this._config = config;
  this._timeout = null;
  this._running = false;
}

Monitor.SIGINT_CHECK_INTERVAL = 1000;
Monitor.MAX_SIGINT_WAIT = 5000;

/**
 * Starts the Bridge instance
 * @param {Function} callback
 */
Monitor.prototype.start = function(callback) {
  log.info('Farmer monitor service is starting');

  this.storage = new Storage(
    this._config.storage.mongoUrl,
    this._config.storage.mongoOpts,
    { logger: log }
  );

  this.network = new ComplexClient(this._config.complex);

  this.contracts = new storj.StorageManager(
    new MongoDBStorageAdapter(this.storage),
    { disableReaper: true }
  );

  // setup next run event
  this.wait();

  callback();
  process.on('SIGINT', this._handleSIGINT.bind(this));
  process.on('exit', this._handleExit.bind(this));
  process.on('uncaughtException', this._handleUncaughtException.bind(this));
};

Monitor.sortByTimeoutRate = function(a, b) {
  const a1 = a.contact.timeoutRate >= 0 ? a.contact.timeoutRate : Infinity;
  const b1 = b.contact.timeoutRate >= 0 ? b.contact.timeoutRate : Infinity;
  return (a1 === b1) ? 0 : (a1 > b1) ? 1 : -1;
};

Monitor.prototype._fetchDestinations = function(shard, callback) {
  this.storage.models.Mirror
    .find({ shardHash: shard.hash })
    .populate('contact')
    .exec((err, results) => {
      if (err) {
        return callback(err);
      }
      const mirrors = results.filter((m) => {
        if (!m.contact) {
          log.warn('Mirror %s is missing contact in database', m._id);
          return false;
        } else if (!m.isEstablished) {
          return true;
        }
      });
      mirrors.sort(Monitor.sortByTimeoutRate);
      callback(null, mirrors);
    });
};

Monitor.prototype._fetchSources = function(shard, callback) {
  let farmers = Object.keys(shard.contracts);

  this.storage.models.Contact
    .find({ _id: { $in: farmers }})
    .sort({ lastSeen: -1 })
    .exec((err, results) => {
      if (err) {
        return callback(err);
      }

      let contacts = [];
      for (let i = 0; i < results.length; i++) {
        let c = results[i];
        let contact = null;
        try {
          contact = storj.Contact(c.toObject());
        } catch(e) {
          log.warn('Unable to fetch source, invalid contact: %j', c.toObject());
        }
        if (contact) {
          contacts.push(contact);
        }
      }

      callback(null, contacts);
    });
};

Monitor.prototype._saveShard = function(shard, destination) {

  const contract = storj.Contract(destination.contract);
  const contact = storj.Contact(destination.contact);
  shard.addContract(contact, contract);

  this.contracts.save(shard, (err) => {
    if (err) {
      log.error('Unable to replicate shard %s, reason: %s',
                shard.hash, 'Unable save contract to shard');
      return;
    }

    log.info('Successfully replicated shard %s', shard.hash);
    destination.isEstablished = true;
    destination.save((err) => {
      if (err) {
        log.error('Unable to update mirror as established, reason: %s',
                  err.message);
      }
    });
  });
};

Monitor.prototype._transferShard = function(shard, state) {
  const source = state.sources[0];
  const destination = state.destinations[0];

  if (!source) {
    return log.error('Unable to replicate shard %s, reason: %s',
                     shard.hash, 'sources exhausted');
  }

  if (!destination) {
    return log.error('Unable to replicate shard %s, reason: %s',
                     shard.hash, 'destinations exhausted');
  }

  let contract = null;
  try {
    contract = storj.Contract(destination.contract);
  } catch(e) {
    log.warn('Unable to transfer shard, invalid contract: %j',
             destination.contract);
    state.destinations.shift();
    this._transferShard(shard, state);
    return;
  }

  this.network.getRetrievalPointer(source, contract, (err, pointer) => {
    if (err || !pointer) {
      log.warn('Failed to get retrieval pointer from farmer %s, reason: %s',
               source, err ? err.message : null);

      state.sources.shift();
      this._transferShard(shard, state);
      return;
    }

    const farmer = storj.Contact(destination.contact);

    this.network.getMirrorNodes([pointer], [farmer], (err) => {
      if (err) {
        log.warn('Unable to mirror to farmer %s, reason: %s',
                 destination, err.message);
        state.destinations.shift();
        this._transferShard(shard, state);
        return;
      }

      this._saveShard(shard, destination);
    });
  });
};

Monitor.prototype._replicateShard = function(shard) {
  async.parallel({
    destinations: (next) => {
      this._fetchDestinations(shard, next);
    },
    sources: (next) => {
      this._fetchSources(shard, next);
    }
  }, (err, state) => {
    if (err) {
      return log.error('Unable to replicate shard %s, reason: %s',
                       shard.hash, err.message);
    }
    this._transferShard(shard, state);
  });
};

Monitor.prototype._replicateFarmer = function(contact) {
  log.info('Starting to replicate farmer %s', contact.nodeID);

  const query = {
    'contracts.nodeID': contact.nodeID
  };
  const cursor = this.storage.models.Shard.find(query).cursor();

  cursor
    .on('error', (err) => {
      log.error('Unable to replicate farmer %s, reason: %s',
                contact.nodeID, err.message);
    })
    .on('data', (data) => {
      const shard = storj.StorageItem(data.toObject());
      log.info('Replicating shard %s for farmer', shard.hash, contact.nodeID);
      this._replicateShard(shard);
    })
    .on('close', () => {
      log.info('Ending replication of farmer %s', contact.nodeID);
    });
};

Monitor.prototype.run = function() {
  if (this._running) {
    return this.wait();
  }

  let fail = 0;
  let success = 0;
  let total = 0;
  const limit = this._config.application.queryNumber || 10;
  const pingConcurrency = this._config.application.pingConcurrency || 10;
  const timeoutRateThreshold = this._config.application.timeoutRateThreshold;

  const finish = (err) => {
    if (err) {
      log.error(err);
    }
    log.info('Ending farmer monitor round with failure rate of %s/%s from %s',
             fail, success, total);
    this._running = false;
    this.wait();
  };

  log.info('Starting farmer monitor round for %s contacts', limit);
  this._running = true;

  // Query the least seen contacts with timeout rates below threshold
  const Contact = this.storage.models.Contact;
  const query = {
    $or: [
      { timeoutRate: { $lt: timeoutRateThreshold } },
      { timeoutRate: { $exists: false } }
    ]
  };

  const cursor = Contact.find(query).limit(limit).sort({lastSeen: 1});
  cursor.exec((err, contacts) => {
    if (err) {
      return finish(err);
    }

    if (!contacts) {
      return finish(
        new errors.InternalError('No contacts in contacts collection')
      );
    }

    // Update total length of contacts
    total = contacts.length;

    // Ping the least seen contacts
    async.eachLimit(contacts, pingConcurrency, (contactData, next) => {

      const contact = storj.Contact(contactData);

      this.network.ping(contact, (err) => {
        if (err) {
          fail += 1;
          log.error('Farmer %s failed ping, reason: %s',
                    contact.nodeID, err.message);

          contactData.recordTimeoutFailure().save((err) => {
            if (err) {
              log.error('Unable to save ping failure, farmer: %s, reason: %s',
                        contact.nodeID, err.message);
            }
          });

          if (contactData.timeoutRate >= timeoutRateThreshold) {
            log.warn('Shards need replication, farmer: %s, timeoutRate: %s',
                     contact.nodeID, contactData.timeoutRate);
            this._replicateFarmer(contact);
          }

        } else {
          success += 1;
        }

        next();
      });

    }, finish);

  });

};

Monitor.prototype._randomTime = function(max, min) {
  const range = max - min;

  assert(Number.isSafeInteger(range));
  assert(range > 0, 'maxInterval is expected to be greater than minInterval');

  const entropy = crypto.randomBytes(8).toString('hex');
  const offset = Math.round(parseInt('0x' + entropy) / Math.pow(2, 64) * range);

  return min + offset;
};

/**
 * Will wait and then call `run` after a random amount of time
 */
Monitor.prototype.wait = function() {
  clearTimeout(this._timeout);

  const max = ms(this._config.application.maxInterval);
  const min = ms(this._config.application.minInterval);

  const milliseconds = this._randomTime(max, min);
  const minutes = Number(milliseconds / 1000 / 60).toFixed(2);

  log.info('Scheduling next round in %s minutes', minutes);

  this._timeout = setTimeout(() => this.run(), milliseconds);
};

/**
 * Handles uncaught exceptions
 * @private
 */
/* istanbul ignore next */
Monitor.prototype._handleUncaughtException = function(err) {
  if (process.env.NODE_ENV === 'test') {
    throw err;
  }

  log.error('An unhandled exception occurred:', err);
  process.exit(1);
};

/**
 * Handles exit event from process
 * @private
 */
/* istanbul ignore next */
Monitor.prototype._handleExit = function() {
  log.info('Farmer monitor service is shutting down');
};

/**
 * Postpones process exit until requests are fullfilled
 * @private
 */
/* istanbul ignore next */
Monitor.prototype._handleSIGINT = function() {
  let waitTime = 0;

  log.info('Received shutdown signal, checking for running monitor');
  setInterval(function() {
    waitTime += Monitor.SIGINT_CHECK_INTERVAL;

    if (!this._running) {
      process.exit();
    }

    if (waitTime > Monitor.MAX_SIGINT_WAIT) {
      process.exit();
    }
  }, Monitor.SIGINT_CHECK_INTERVAL);
};

module.exports = Monitor;
<commit_msg>Explicit return false for filter<commit_after>'use strict';

const async = require('async');
const assert = require('assert');
const crypto = require('crypto');
const storj = require('storj-lib');
const MonitorConfig = require('./config');
const Storage = require('storj-service-storage-models');
const ComplexClient = require('storj-complex').createClient;
const MongoDBStorageAdapter = require('storj-mongodb-adapter');
const ms = require('ms');
const log = require('../logger');
const errors = require('storj-service-error-types');

/**
 * A long running daemon that will monitor farmers uptime and will replace
 * contracts associated with a farmer once the farmer is confirmed to be
 * offline for a duration of time.
 * @param {MonitorConfig} config - An instance of MonitorConfig
 */
function Monitor(config) {
  if (!(this instanceof Monitor)) {
    return new Monitor(config);
  }

  assert(config instanceof MonitorConfig, 'Invalid config supplied');

  this.storage = null;
  this.network = null;
  this.contracts = null;

  this._config = config;
  this._timeout = null;
  this._running = false;
}

Monitor.SIGINT_CHECK_INTERVAL = 1000;
Monitor.MAX_SIGINT_WAIT = 5000;

/**
 * Starts the Bridge instance
 * @param {Function} callback
 */
Monitor.prototype.start = function(callback) {
  log.info('Farmer monitor service is starting');

  this.storage = new Storage(
    this._config.storage.mongoUrl,
    this._config.storage.mongoOpts,
    { logger: log }
  );

  this.network = new ComplexClient(this._config.complex);

  this.contracts = new storj.StorageManager(
    new MongoDBStorageAdapter(this.storage),
    { disableReaper: true }
  );

  // setup next run event
  this.wait();

  callback();
  process.on('SIGINT', this._handleSIGINT.bind(this));
  process.on('exit', this._handleExit.bind(this));
  process.on('uncaughtException', this._handleUncaughtException.bind(this));
};

Monitor.sortByTimeoutRate = function(a, b) {
  const a1 = a.contact.timeoutRate >= 0 ? a.contact.timeoutRate : Infinity;
  const b1 = b.contact.timeoutRate >= 0 ? b.contact.timeoutRate : Infinity;
  return (a1 === b1) ? 0 : (a1 > b1) ? 1 : -1;
};

Monitor.prototype._fetchDestinations = function(shard, callback) {
  this.storage.models.Mirror
    .find({ shardHash: shard.hash })
    .populate('contact')
    .exec((err, results) => {
      if (err) {
        return callback(err);
      }
      const mirrors = results.filter((m) => {
        if (!m.contact) {
          log.warn('Mirror %s is missing contact in database', m._id);
          return false;
        } else if (!m.isEstablished) {
          return true;
        }
        return false;
      });
      mirrors.sort(Monitor.sortByTimeoutRate);
      callback(null, mirrors);
    });
};

Monitor.prototype._fetchSources = function(shard, callback) {
  let farmers = Object.keys(shard.contracts);

  this.storage.models.Contact
    .find({ _id: { $in: farmers }})
    .sort({ lastSeen: -1 })
    .exec((err, results) => {
      if (err) {
        return callback(err);
      }

      let contacts = [];
      for (let i = 0; i < results.length; i++) {
        let c = results[i];
        let contact = null;
        try {
          contact = storj.Contact(c.toObject());
        } catch(e) {
          log.warn('Unable to fetch source, invalid contact: %j', c.toObject());
        }
        if (contact) {
          contacts.push(contact);
        }
      }

      callback(null, contacts);
    });
};

Monitor.prototype._saveShard = function(shard, destination) {

  const contract = storj.Contract(destination.contract);
  const contact = storj.Contact(destination.contact);
  shard.addContract(contact, contract);

  this.contracts.save(shard, (err) => {
    if (err) {
      log.error('Unable to replicate shard %s, reason: %s',
                shard.hash, 'Unable save contract to shard');
      return;
    }

    log.info('Successfully replicated shard %s', shard.hash);
    destination.isEstablished = true;
    destination.save((err) => {
      if (err) {
        log.error('Unable to update mirror as established, reason: %s',
                  err.message);
      }
    });
  });
};

Monitor.prototype._transferShard = function(shard, state) {
  const source = state.sources[0];
  const destination = state.destinations[0];

  if (!source) {
    return log.error('Unable to replicate shard %s, reason: %s',
                     shard.hash, 'sources exhausted');
  }

  if (!destination) {
    return log.error('Unable to replicate shard %s, reason: %s',
                     shard.hash, 'destinations exhausted');
  }

  let contract = null;
  try {
    contract = storj.Contract(destination.contract);
  } catch(e) {
    log.warn('Unable to transfer shard, invalid contract: %j',
             destination.contract);
    state.destinations.shift();
    this._transferShard(shard, state);
    return;
  }

  this.network.getRetrievalPointer(source, contract, (err, pointer) => {
    if (err || !pointer) {
      log.warn('Failed to get retrieval pointer from farmer %s, reason: %s',
               source, err ? err.message : null);

      state.sources.shift();
      this._transferShard(shard, state);
      return;
    }

    const farmer = storj.Contact(destination.contact);

    this.network.getMirrorNodes([pointer], [farmer], (err) => {
      if (err) {
        log.warn('Unable to mirror to farmer %s, reason: %s',
                 destination, err.message);
        state.destinations.shift();
        this._transferShard(shard, state);
        return;
      }

      this._saveShard(shard, destination);
    });
  });
};

Monitor.prototype._replicateShard = function(shard) {
  async.parallel({
    destinations: (next) => {
      this._fetchDestinations(shard, next);
    },
    sources: (next) => {
      this._fetchSources(shard, next);
    }
  }, (err, state) => {
    if (err) {
      return log.error('Unable to replicate shard %s, reason: %s',
                       shard.hash, err.message);
    }
    this._transferShard(shard, state);
  });
};

Monitor.prototype._replicateFarmer = function(contact) {
  log.info('Starting to replicate farmer %s', contact.nodeID);

  const query = {
    'contracts.nodeID': contact.nodeID
  };
  const cursor = this.storage.models.Shard.find(query).cursor();

  cursor
    .on('error', (err) => {
      log.error('Unable to replicate farmer %s, reason: %s',
                contact.nodeID, err.message);
    })
    .on('data', (data) => {
      const shard = storj.StorageItem(data.toObject());
      log.info('Replicating shard %s for farmer', shard.hash, contact.nodeID);
      this._replicateShard(shard);
    })
    .on('close', () => {
      log.info('Ending replication of farmer %s', contact.nodeID);
    });
};

Monitor.prototype.run = function() {
  if (this._running) {
    return this.wait();
  }

  let fail = 0;
  let success = 0;
  let total = 0;
  const limit = this._config.application.queryNumber || 10;
  const pingConcurrency = this._config.application.pingConcurrency || 10;
  const timeoutRateThreshold = this._config.application.timeoutRateThreshold;

  const finish = (err) => {
    if (err) {
      log.error(err);
    }
    log.info('Ending farmer monitor round with failure rate of %s/%s from %s',
             fail, success, total);
    this._running = false;
    this.wait();
  };

  log.info('Starting farmer monitor round for %s contacts', limit);
  this._running = true;

  // Query the least seen contacts with timeout rates below threshold
  const Contact = this.storage.models.Contact;
  const query = {
    $or: [
      { timeoutRate: { $lt: timeoutRateThreshold } },
      { timeoutRate: { $exists: false } }
    ]
  };

  const cursor = Contact.find(query).limit(limit).sort({lastSeen: 1});
  cursor.exec((err, contacts) => {
    if (err) {
      return finish(err);
    }

    if (!contacts) {
      return finish(
        new errors.InternalError('No contacts in contacts collection')
      );
    }

    // Update total length of contacts
    total = contacts.length;

    // Ping the least seen contacts
    async.eachLimit(contacts, pingConcurrency, (contactData, next) => {

      const contact = storj.Contact(contactData);

      this.network.ping(contact, (err) => {
        if (err) {
          fail += 1;
          log.error('Farmer %s failed ping, reason: %s',
                    contact.nodeID, err.message);

          contactData.recordTimeoutFailure().save((err) => {
            if (err) {
              log.error('Unable to save ping failure, farmer: %s, reason: %s',
                        contact.nodeID, err.message);
            }
          });

          if (contactData.timeoutRate >= timeoutRateThreshold) {
            log.warn('Shards need replication, farmer: %s, timeoutRate: %s',
                     contact.nodeID, contactData.timeoutRate);
            this._replicateFarmer(contact);
          }

        } else {
          success += 1;
        }

        next();
      });

    }, finish);

  });

};

Monitor.prototype._randomTime = function(max, min) {
  const range = max - min;

  assert(Number.isSafeInteger(range));
  assert(range > 0, 'maxInterval is expected to be greater than minInterval');

  const entropy = crypto.randomBytes(8).toString('hex');
  const offset = Math.round(parseInt('0x' + entropy) / Math.pow(2, 64) * range);

  return min + offset;
};

/**
 * Will wait and then call `run` after a random amount of time
 */
Monitor.prototype.wait = function() {
  clearTimeout(this._timeout);

  const max = ms(this._config.application.maxInterval);
  const min = ms(this._config.application.minInterval);

  const milliseconds = this._randomTime(max, min);
  const minutes = Number(milliseconds / 1000 / 60).toFixed(2);

  log.info('Scheduling next round in %s minutes', minutes);

  this._timeout = setTimeout(() => this.run(), milliseconds);
};

/**
 * Handles uncaught exceptions
 * @private
 */
/* istanbul ignore next */
Monitor.prototype._handleUncaughtException = function(err) {
  if (process.env.NODE_ENV === 'test') {
    throw err;
  }

  log.error('An unhandled exception occurred:', err);
  process.exit(1);
};

/**
 * Handles exit event from process
 * @private
 */
/* istanbul ignore next */
Monitor.prototype._handleExit = function() {
  log.info('Farmer monitor service is shutting down');
};

/**
 * Postpones process exit until requests are fullfilled
 * @private
 */
/* istanbul ignore next */
Monitor.prototype._handleSIGINT = function() {
  let waitTime = 0;

  log.info('Received shutdown signal, checking for running monitor');
  setInterval(function() {
    waitTime += Monitor.SIGINT_CHECK_INTERVAL;

    if (!this._running) {
      process.exit();
    }

    if (waitTime > Monitor.MAX_SIGINT_WAIT) {
      process.exit();
    }
  }, Monitor.SIGINT_CHECK_INTERVAL);
};

module.exports = Monitor;
<|endoftext|>


<commit_before>/*
 * My Wallet
 * It is a Money Coontroll application with Node.JS and Angular.js
 * This software is free for General Porpouses
 * @author: Max Andriani <max.andriani@gmail.com>
 */

/**
 * List Standard Object
 * It is the List. This object should storage the lists's basic data and do some single commands
 *
 * @param {List} data An List Object or a SQL abstract Param Object
 */
var List = function( data ){
    if (typeof data !== 'undefined'){
        for (var x in data){
            switch(x){
                case 'list_id':
                case 'list_name':
                case 'user_id':
                    this[x] = data[x];
                    break;
            }
        }
    }
};

/**
 * List.delete()
 * Remove the current list from Database. This action is irreversible.
 *
 * @param {function} next A callback function
 * @returns {undefined}
 */
List.prototype.delete = function( next ){
    try{
        // Load DB
        var db = require(global.pathTo('/db/dbModel.js'));
        var clearTransactionsQuery, removeListQuery;
        var listId = this.list_id;

        clearTransactionsQuery =
            'Update '+
                'transactions as t '+
            'Set '+
                'list_id = NULL '+
            'Where '+
                't.list_id = ? ;';

        removeListQuery =
                'Delete From '+
                    'lists '+
                'Where '+
                    'lists.id = ? Limit 1;';

        // Callbacks
        var clearTransactions = function(){
            db.query( clearTransactionsQuery, [listId], removeList );
        };

        var removeList = function(err){
            if (err){
                next(new Error('ERROR_LISTS_FAILURE_CLEAR_TRANSACTIONS_RELATIONSHIP'));
                return;
            }
            db.query( removeListQuery, [listId], function(err){
                if (err){
                    next(new Error('ERROR_LIST_FAILURE_ON_REMOVE_LIST'));
                } else {
                    next(false);
                }
            });
        };

        // Do the Magic Start recurvive callbacks
        clearTransactions();

    } catch (e) {
        next(e);
    }
};

/**
 * List.removeTransaction
 * Remove an Transaction relationship with the current list
 *
 * @param {numeric} transactionId The transaction reference that should be removed
 * @param {function} next The callback function
 * @returns {undefined}
 */
List.prototype.removeTransaction = function( transactionId, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var query =
        'Update '+
            'transactions as t '+
        'Set '+
            't.list_id = NULL '+
        'Where '+
            't.id = ? '+
        'Limit 1;';

    db.query( query, transactionId, function(err){
        if (err){
            next(new Error('ERROR_LISTS_UNNABLE_TO_REMOVE_TRANSACTION'));
        } else {
            next(false);
        }
    });
};

/**
 * List.addTransaction()
 * Add an transaction into the current list
 *
 * @param {number} transactionId The transaction reference
 * @param {function} next Callback function
 * @returns {undefined}
 */
List.prototype.addTransaction = function( transactionId, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var query =
        'Update '+
            'transactions as t '+
        'Set '+
            't.list_id = ? '+
        'Where '+
            't.id = ? '+
        'Limit 1;';
    var values = [ this.list_id, transactionId ];

    db.query( query, values, function(err){
        if (err){
            next(new Error('ERROR_LISTS_UNNABLE_TO_ADD_TRANSACTION'));
        } else {
            next(false);
        }
    });
};

/**
 * List.save()
 * Save the current metadata in database.
 *
 * @param {function} next A callback function
 * @returns {undefined}
 */
List.prototype.save = function( next ){
    try{
        var _self = this;
        var db = require(global.pathTo('/db/dbModel.js'));
        var query =
            'Update lists '+
                'Set '+
                   'name = \'?\' '+
                'Where '+
                    'lists.id = \'?\' '+
                'Limit 1;';
        db.query( query, [_self.list_name, _self.list_id], function(err){
            if (err){
                next(err);
            } else {
                next(false);
            }
        });

    } catch (e) {
        next(e);
    }
};

/**
 * Lists
 *
 * This object will manage the plural List behaviour
 */
var Lists = function(){};

/**
 * Lists.find()
 * Search in the database for an list, that factory an User object to each result
 *
 * @param {object} find The search param object
 * {
 *      search: {string}, The search query
 *      user_id: {number}, The user reference
 *      page: {number}, The pagination reference
 * }
 * @param {function} next A callback function
 * @returns {undefined}
 */
Lists.prototype.find = function( find, next ){

    var db = require(global.pathTo('/db/dbModel.js'));
    var query;
    var values = [];

    try{
        if (typeof find.user_id === 'undefined'){
            throw new Error('ERROR_LISTS_INVALID_USER_ID');
        }

        if (typeof find.search === 'undefined'){
            find.search = null;
        }

        if (typeof find.page === 'undefined'){
            find.page = 0;
        }

        // Start tu build query
        query =
            'Select '+
                'l.id as list_id,'+
                'l.user_id as user_id,'+
                'l.name as list_name '+
            'From '+
                'lists as l '+
            'Where '+
                'l.user_id = ? ';

        values.push(find.user_id);

        if (find.search){
            // Find by key
            var nameString = find.search.replace(/\s/g, '%');
            query += "And l.name Like '%?%' ";
            values.push(nameString);
        } else {
            // List all
            // Nothing to do
        }

        // Sort and order
        query += ' Order By l.name Asc ';

        // Pagination
        query += ' Limit ?, 9';
        values.push((find.page * 9));

        // End query
        query += ';';

        // Do the magic
        db.query( query, values, function(err, data){
            if (err){
                next(err);
                return;
            }

            var listList = [];
            for(var x in data){
                listList.push(new List(data[x]));
            }

            next(false, listList);
        });
    } catch(err) {
        next(err);
    }
};

/**
 * Lists.getListById()
 * Get lists's info from database and factory an List Object
 *
 * @param {number} user_id The user reference
 * @param {number} list_id The List reference
 * @param {function} next Callback function
 * @returns {undefined}
 */
Lists.prototype.getListById = function( user_id, list_id, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var query;

    query = 'Select '+
                'l.id as list_id,'+
                'l.name as list_name,'+
                'l.user_id as user_id '+
            'From '+
                'lists as l '+
            'Where '+
                "l.id = ? And "+
                "l.user_id = ?"+
            'Limit 1;';

    // Do the magic
    db.query( query, [list_id, user_id], function(err, user){
        if (err){
            next(err);
            return;
        }

        if (user.length == 1){
            next(false, new User(user[0]));
        } else {
            next( new Error('404: ERROR_LIST_NOT_FOUND') );
        }
    });
};

/**
 * Lists.getTransactions
 * Get all transactions with this list
 * Alias to transactionModel.getTransactionsByList(user_id, list_id, next);
 *
 * @param {number} user_id The user reference
 * @param {number} list_id The lisst reference
 * @param {type} next The callback function
 * @returns {undefined}
 */
Lists.prototype.getTransactions = function(user_id, list_id, next){
    var transactionModel = require(global.pathTo('/transactions/transactionModel.js'));
    transactionModel.getTransactionsByList(user_id, list_id, next);
};

/**
 * Lists.insertList()
 * Insert a new list in the database
 *
 * @param {object} list_data An List Object or an abstract list object
 * {
 *     user_id: {number}
 *     list_name: {string}
 * }
 * @param {function} next Callback function
 * @returns {undefined}
 */
Lists.prototype.insertList = function( data, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var insertQuery;
    var insertValues = [];

    insertQuery = 'Insert Into '+
                    'lists '+
                        '(name, user_id) '+
                    'Values '+
                        "('?', ?);";
    insertValues.push(data.list_name);
    insertValues.push(data.user_id);

    db.query( insertQuery, insertValues, function(err, db_data, fields){
        if (err){
            next(err);
        }

        data.list_id = db_data.insertId;

        next(false, new List(data));
    });

};

/**
 * List.factory
 * Factory an list object with an list abstract object data
 * @param {object} list_data List abstract object data
 * {
 *     list_id: {number},
 *     list_name: {string},
 *     user_id: {number}
 * }
 * @returns {List}
 */
Lists.prototype.factory = function( list_data ){
    return new List( list_data );
};

module.exports = new Lists();<commit_msg>Add export method<commit_after>/*
 * My Wallet
 * It is a Money Coontroll application with Node.JS and Angular.js
 * This software is free for General Porpouses
 * @author: Max Andriani <max.andriani@gmail.com>
 */

/**
 * List Standard Object
 * It is the List. This object should storage the lists's basic data and do some single commands
 *
 * @param {List} data An List Object or a SQL abstract Param Object
 */
var List = function( data ){
    if (typeof data !== 'undefined'){
        for (var x in data){
            switch(x){
                case 'list_id':
                case 'list_name':
                case 'user_id':
                    this[x] = data[x];
                    break;
            }
        }
    }
};

/**
 * List.delete()
 * Remove the current list from Database. This action is irreversible.
 *
 * @param {function} next A callback function
 * @returns {undefined}
 */
List.prototype.delete = function( next ){
    try{
        // Load DB
        var db = require(global.pathTo('/db/dbModel.js'));
        var clearTransactionsQuery, removeListQuery;
        var listId = this.list_id;

        clearTransactionsQuery =
            'Update '+
                'transactions as t '+
            'Set '+
                'list_id = NULL '+
            'Where '+
                't.list_id = ? ;';

        removeListQuery =
                'Delete From '+
                    'lists '+
                'Where '+
                    'lists.id = ? Limit 1;';

        // Callbacks
        var clearTransactions = function(){
            db.query( clearTransactionsQuery, [listId], removeList );
        };

        var removeList = function(err){
            if (err){
                next(new Error('ERROR_LISTS_FAILURE_CLEAR_TRANSACTIONS_RELATIONSHIP'));
                return;
            }
            db.query( removeListQuery, [listId], function(err){
                if (err){
                    next(new Error('ERROR_LIST_FAILURE_ON_REMOVE_LIST'));
                } else {
                    next(false);
                }
            });
        };

        // Do the Magic Start recurvive callbacks
        clearTransactions();

    } catch (e) {
        next(e);
    }
};

/**
 * List.removeTransaction
 * Remove an Transaction relationship with the current list
 *
 * @param {numeric} transactionId The transaction reference that should be removed
 * @param {function} next The callback function
 * @returns {undefined}
 */
List.prototype.removeTransaction = function( transactionId, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var query =
        'Update '+
            'transactions as t '+
        'Set '+
            't.list_id = NULL '+
        'Where '+
            't.id = ? '+
        'Limit 1;';

    db.query( query, transactionId, function(err){
        if (err){
            next(new Error('ERROR_LISTS_UNNABLE_TO_REMOVE_TRANSACTION'));
        } else {
            next(false);
        }
    });
};

/**
 * List.addTransaction()
 * Add an transaction into the current list
 *
 * @param {number} transactionId The transaction reference
 * @param {function} next Callback function
 * @returns {undefined}
 */
List.prototype.addTransaction = function( transactionId, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var query =
        'Update '+
            'transactions as t '+
        'Set '+
            't.list_id = ? '+
        'Where '+
            't.id = ? '+
        'Limit 1;';
    var values = [ this.list_id, transactionId ];

    db.query( query, values, function(err){
        if (err){
            next(new Error('ERROR_LISTS_UNNABLE_TO_ADD_TRANSACTION'));
        } else {
            next(false);
        }
    });
};

/**
 * List.save()
 * Save the current metadata in database.
 *
 * @param {function} next A callback function
 * @returns {undefined}
 */
List.prototype.save = function( next ){
    try{
        var _self = this;
        var db = require(global.pathTo('/db/dbModel.js'));
        var query =
            'Update lists '+
                'Set '+
                   'name = \'?\' '+
                'Where '+
                    'lists.id = \'?\' '+
                'Limit 1;';
        db.query( query, [_self.list_name, _self.list_id], function(err){
            if (err){
                next(err);
            } else {
                next(false);
            }
        });

    } catch (e) {
        next(e);
    }
};

/**
 * List.export()
 * Create an abstract object to List
 *
 * @returns {nm$_listModel.List.prototype.export.listModelAnonym$3}
 */
List.prototype.export = function(){
    return {
        list_id: this.list_id,
        list_name: this.list_name,
        user_id: this.user_id
    };
};

/**
 * Lists
 *
 * This object will manage the plural List behaviour
 */
var Lists = function(){};

/**
 * Lists.find()
 * Search in the database for an list, that factory an User object to each result
 *
 * @param {object} find The search param object
 * {
 *      search: {string}, The search query
 *      user_id: {number}, The user reference
 *      page: {number}, The pagination reference
 * }
 * @param {function} next A callback function
 * @returns {undefined}
 */
Lists.prototype.find = function( find, next ){

    var db = require(global.pathTo('/db/dbModel.js'));
    var query;
    var values = [];

    try{
        if (typeof find.user_id === 'undefined'){
            throw new Error('ERROR_LISTS_INVALID_USER_ID');
        }

        if (typeof find.search === 'undefined'){
            find.search = null;
        }

        if (typeof find.page === 'undefined'){
            find.page = 0;
        }

        // Start tu build query
        query =
            'Select '+
                'l.id as list_id,'+
                'l.user_id as user_id,'+
                'l.name as list_name '+
            'From '+
                'lists as l '+
            'Where '+
                'l.user_id = ? ';

        values.push(find.user_id);

        if (find.search){
            // Find by key
            var nameString = find.search.replace(/\s/g, '%');
            query += "And l.name Like '%?%' ";
            values.push(nameString);
        } else {
            // List all
            // Nothing to do
        }

        // Sort and order
        query += ' Order By l.name Asc ';

        // Pagination
        query += ' Limit ?, 9';
        values.push((find.page * 9));

        // End query
        query += ';';

        // Do the magic
        db.query( query, values, function(err, data){
            if (err){
                next(err);
                return;
            }

            var listList = [];
            for(var x in data){
                listList.push(new List(data[x]));
            }

            next(false, listList);
        });
    } catch(err) {
        next(err);
    }
};

/**
 * Lists.getListById()
 * Get lists's info from database and factory an List Object
 *
 * @param {number} user_id The user reference
 * @param {number} list_id The List reference
 * @param {function} next Callback function
 * @returns {undefined}
 */
Lists.prototype.getListById = function( user_id, list_id, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var query;

    query = 'Select '+
                'l.id as list_id,'+
                'l.name as list_name,'+
                'l.user_id as user_id '+
            'From '+
                'lists as l '+
            'Where '+
                "l.id = ? And "+
                "l.user_id = ?"+
            'Limit 1;';

    // Do the magic
    db.query( query, [list_id, user_id], function(err, user){
        if (err){
            next(err);
            return;
        }

        if (user.length == 1){
            next(false, new User(user[0]));
        } else {
            next( new Error('404: ERROR_LIST_NOT_FOUND') );
        }
    });
};

/**
 * Lists.getTransactions
 * Get all transactions with this list
 * Alias to transactionModel.getTransactionsByList(user_id, list_id, next);
 *
 * @param {number} user_id The user reference
 * @param {number} list_id The lisst reference
 * @param {type} next The callback function
 * @returns {undefined}
 */
Lists.prototype.getTransactions = function(user_id, list_id, next){
    var transactionModel = require(global.pathTo('/transactions/transactionModel.js'));
    transactionModel.getTransactionsByList(user_id, list_id, next);
};

/**
 * Lists.insertList()
 * Insert a new list in the database
 *
 * @param {object} list_data An List Object or an abstract list object
 * {
 *     user_id: {number}
 *     list_name: {string}
 * }
 * @param {function} next Callback function
 * @returns {undefined}
 */
Lists.prototype.insertList = function( data, next ){
    var db = require(global.pathTo('/db/dbModel.js'));
    var insertQuery;
    var insertValues = [];

    insertQuery = 'Insert Into '+
                    'lists '+
                        '(name, user_id) '+
                    'Values '+
                        "('?', ?);";
    insertValues.push(data.list_name);
    insertValues.push(data.user_id);

    db.query( insertQuery, insertValues, function(err, db_data, fields){
        if (err){
            next(err);
        }

        data.list_id = db_data.insertId;

        next(false, new List(data));
    });

};

/**
 * List.factory
 * Factory an list object with an list abstract object data
 * @param {object} list_data List abstract object data
 * {
 *     list_id: {number},
 *     list_name: {string},
 *     user_id: {number}
 * }
 * @returns {List}
 */
Lists.prototype.factory = function( list_data ){
    return new List( list_data );
};

module.exports = new Lists();<|endoftext|>


<commit_before>import moment       from 'moment';
import { localize } from '_common/localize';

/**
 * Convert epoch to moment object
 * @param  {Number} epoch
 * @return {moment} the moment object of provided epoch
 */
export const epochToMoment = epoch => moment.unix(epoch).utc();

/**
 * Convert date string or epoch to moment object
 * @param  {Number} value   the date in epoch format
 * @param  {String} value   the date in string format
 * @return {moment} the moment object of 'now' or the provided date epoch or string
 */
export const toMoment = value => {
    if (!value) return moment().utc(); // returns 'now' moment object
    if (value instanceof moment && value.isValid() && value.isUTC()) return value; // returns if already a moment object
    const moment_obj = epochToMoment(value);
    return moment_obj.isValid() ? moment_obj : moment.utc(value);
};

/**
 * Set specified time on moment object
 * @param  {moment} moment_obj  the moment to set the time on
 * @param  {String} time        24 hours format, may or may not include seconds
 * @return {moment} a new moment object of result
 */
export const setTime = (moment_obj, time) => {
    const [hour, minute, second] = time ? time.split(':') : [0, 0, 0];
    moment_obj.hour(hour).minute(minute || 0).second(second || 0);
    return moment_obj;
};

/**
 * return the unix value of provided epoch and time
 * @param  {Number} epoch  the date to update with provided time
 * @param  {String} time   the time to set on the date
 * @return {Number} unix value of the result
 */
export const convertToUnix = (epoch, time) => setTime(toMoment(epoch), time).unix();

export const toGMTFormat = (time) => moment(time || undefined).utc().format('YYYY-MM-DD HH:mm:ss [GMT]');

export const formatDate = (date, date_format = 'YYYY-MM-DD') => toMoment(date).format(date_format);

/**
 * return the number of days from today to date specified
 * @param  {String} date   the date to calculate number of days from today
 * @return {Number} an integer of the number of days
 */
export const daysFromTodayTo = (date) => {
    const diff = toMoment(date).startOf('day').diff(toMoment().startOf('day'), 'days');
    return (!date || diff < 0) ? '' : diff;
};

/**
 * return moment duration between two dates
 * @param  {Number} epoch start time
 * @param  {Number} epoch end time
 * @return {moment.duration} moment duration between start time and end time
 */
export const getDiffDuration = (start_time, end_time) =>
    moment.duration(moment.unix(end_time).diff(moment.unix(start_time)));

/**
 * return formatted duration `2 days 01:23:59`
 * @param  {moment.duration} moment duration object
 * @return {String} formatted display string
 */
export const formatDuration = (duration) => {
    const d = Math.floor(duration.asDays()); // duration.days() does not include months/years
    const h = duration.hours();
    const m = duration.minutes();
    const s = duration.seconds();
    let formatted_str = moment(0).hour(h).minute(m).seconds(s).format('HH:mm:ss');
    if (d > 0) {
        formatted_str = `${d} ${d > 1 ? localize('days') : localize('day')} ${formatted_str}`;
    }
    return formatted_str;
};

/**
 * return true if the time_str is in "HH:MM" format, else return false
 * @param {String} time_str time
 */
export const isTimeValid = time_str => /^([0-9]|[0-1][0-9]|2[0-3]):([0-9]|[0-5][0-9])(:([0-9]|[0-5][0-9]))?$/.test(time_str);

/**
 * return true if the time_str's hour is between 0 and 23, else return false
 * @param {String} time_str time
 */
export const isHourValid = time_str => isTimeValid(time_str) && /^([01][0-9]|2[0-3])$/.test(time_str.split(':')[0]);

/**
 * return true if the time_str's minute is between 0 and 59, else return false
 * @param {String} time_str time
 */
export const isMinuteValid = time_str => isTimeValid(time_str) && /^[0-5][0-9]$/.test(time_str.split(':')[1]);

/**
 * return true if the date is typeof string and a valid moment date, else return false
 * @param {String|moment} date date
 */
export const isDateValid = date => moment(date, 'YYYY-MM-DD').isValid();

/**
 * add the specified number of days to the given date
 * @param {String} date        date
 * @param {Number} num_of_days number of days to add
 */
export const addDays = (date, num_of_days) => toMoment(date).clone().add(num_of_days, 'day');

/**
 * add the specified number of months to the given date
 * @param {String} date        date
 * @param {Number} num_of_months number of months to add
 */
export const addMonths = (date, num_of_months) => toMoment(date).clone().add(num_of_months, 'month');

/**
 * add the specified number of years to the given date
 * @param {String} date        date
 * @param {Number} num_of_years number of years to add
 */
export const addYears = (date, num_of_years) => toMoment(date).clone().add(num_of_years, 'year');

/**
 * subtract the specified number of days from the given date
 * @param {String} date        date
 * @param {Number} num_of_days number of days to subtract
 */
export const subDays = (date, num_of_days) => toMoment(date).clone().subtract(num_of_days, 'day');

/**
 * subtract the specified number of months from the given date
 * @param {String} date        date
 * @param {Number} num_of_months number of months to subtract
 */
export const subMonths = (date, num_of_months) => toMoment(date).clone().subtract(num_of_months, 'month');

/**
 * subtract the specified number of years from the given date
 * @param {String} date        date
 * @param {Number} num_of_years number of years to subtract
 */
export const subYears = (date, num_of_years) => toMoment(date).clone().subtract(num_of_years, 'year');

/**
 * returns the minimum moment between the two passing parameters
 * @param {moment|string|epoch} first datetime parameter
 * @param {moment|string|epoch} second datetime parameter
 */
export const minDate = (date_1, date_2) => moment.min(toMoment(date_1), toMoment(date_2));

/**
 * returns a new date
 * @param {moment|string|epoch} date date
 */
export const getStartOfMonth = (date) => toMoment(date).clone().startOf('month').format('YYYY-MM-DD');
<commit_msg>fix MomentJS deprecation warning<commit_after>import moment       from 'moment';
import { localize } from '_common/localize';

/**
 * Convert epoch to moment object
 * @param  {Number} epoch
 * @return {moment} the moment object of provided epoch
 */
export const epochToMoment = epoch => moment.unix(epoch).utc();

/**
 * Convert date string or epoch to moment object
 * @param  {Number} value   the date in epoch format
 * @param  {String} value   the date in string format
 * @return {moment} the moment object of 'now' or the provided date epoch or string
 */
export const toMoment = value => {
    if (!value) return moment().utc(); // returns 'now' moment object
    if (value instanceof moment && value.isValid() && value.isUTC()) return value; // returns if already a moment object
    const is_number      = typeof value === 'number';
    // need to explicitly convert date string to a JS Date object then pass that into Moment
    // to get rid of the warning: Deprecation warning: moment construction falls back to js Date
    const formatted_date = moment(new Date(value)).format('YYYY-MM-DD');
    return is_number ? epochToMoment(value) : moment.utc(formatted_date);
};

/**
 * Set specified time on moment object
 * @param  {moment} moment_obj  the moment to set the time on
 * @param  {String} time        24 hours format, may or may not include seconds
 * @return {moment} a new moment object of result
 */
export const setTime = (moment_obj, time) => {
    const [hour, minute, second] = time ? time.split(':') : [0, 0, 0];
    moment_obj.hour(hour).minute(minute || 0).second(second || 0);
    return moment_obj;
};

/**
 * return the unix value of provided epoch and time
 * @param  {Number} epoch  the date to update with provided time
 * @param  {String} time   the time to set on the date
 * @return {Number} unix value of the result
 */
export const convertToUnix = (epoch, time) => setTime(toMoment(epoch), time).unix();

export const toGMTFormat = (time) => moment(time || undefined).utc().format('YYYY-MM-DD HH:mm:ss [GMT]');

export const formatDate = (date, date_format = 'YYYY-MM-DD') => toMoment(date).format(date_format);

/**
 * return the number of days from today to date specified
 * @param  {String} date   the date to calculate number of days from today
 * @return {Number} an integer of the number of days
 */
export const daysFromTodayTo = (date) => {
    const diff = toMoment(date).startOf('day').diff(toMoment().startOf('day'), 'days');
    return (!date || diff < 0) ? '' : diff;
};

/**
 * return moment duration between two dates
 * @param  {Number} epoch start time
 * @param  {Number} epoch end time
 * @return {moment.duration} moment duration between start time and end time
 */
export const getDiffDuration = (start_time, end_time) =>
    moment.duration(moment.unix(end_time).diff(moment.unix(start_time)));

/**
 * return formatted duration `2 days 01:23:59`
 * @param  {moment.duration} moment duration object
 * @return {String} formatted display string
 */
export const formatDuration = (duration) => {
    const d = Math.floor(duration.asDays()); // duration.days() does not include months/years
    const h = duration.hours();
    const m = duration.minutes();
    const s = duration.seconds();
    let formatted_str = moment(0).hour(h).minute(m).seconds(s).format('HH:mm:ss');
    if (d > 0) {
        formatted_str = `${d} ${d > 1 ? localize('days') : localize('day')} ${formatted_str}`;
    }
    return formatted_str;
};

/**
 * return true if the time_str is in "HH:MM" format, else return false
 * @param {String} time_str time
 */
export const isTimeValid = time_str => /^([0-9]|[0-1][0-9]|2[0-3]):([0-9]|[0-5][0-9])(:([0-9]|[0-5][0-9]))?$/.test(time_str);

/**
 * return true if the time_str's hour is between 0 and 23, else return false
 * @param {String} time_str time
 */
export const isHourValid = time_str => isTimeValid(time_str) && /^([01][0-9]|2[0-3])$/.test(time_str.split(':')[0]);

/**
 * return true if the time_str's minute is between 0 and 59, else return false
 * @param {String} time_str time
 */
export const isMinuteValid = time_str => isTimeValid(time_str) && /^[0-5][0-9]$/.test(time_str.split(':')[1]);

/**
 * return true if the date is typeof string and a valid moment date, else return false
 * @param {String|moment} date date
 */
export const isDateValid = date => moment(date, 'YYYY-MM-DD').isValid();

/**
 * add the specified number of days to the given date
 * @param {String} date        date
 * @param {Number} num_of_days number of days to add
 */
export const addDays = (date, num_of_days) => toMoment(date).clone().add(num_of_days, 'day');

/**
 * add the specified number of months to the given date
 * @param {String} date        date
 * @param {Number} num_of_months number of months to add
 */
export const addMonths = (date, num_of_months) => toMoment(date).clone().add(num_of_months, 'month');

/**
 * add the specified number of years to the given date
 * @param {String} date        date
 * @param {Number} num_of_years number of years to add
 */
export const addYears = (date, num_of_years) => toMoment(date).clone().add(num_of_years, 'year');

/**
 * subtract the specified number of days from the given date
 * @param {String} date        date
 * @param {Number} num_of_days number of days to subtract
 */
export const subDays = (date, num_of_days) => toMoment(date).clone().subtract(num_of_days, 'day');

/**
 * subtract the specified number of months from the given date
 * @param {String} date        date
 * @param {Number} num_of_months number of months to subtract
 */
export const subMonths = (date, num_of_months) => toMoment(date).clone().subtract(num_of_months, 'month');

/**
 * subtract the specified number of years from the given date
 * @param {String} date        date
 * @param {Number} num_of_years number of years to subtract
 */
export const subYears = (date, num_of_years) => toMoment(date).clone().subtract(num_of_years, 'year');

/**
 * returns the minimum moment between the two passing parameters
 * @param {moment|string|epoch} first datetime parameter
 * @param {moment|string|epoch} second datetime parameter
 */
export const minDate = (date_1, date_2) => moment.min(toMoment(date_1), toMoment(date_2));

/**
 * returns a new date
 * @param {moment|string|epoch} date date
 */
export const getStartOfMonth = (date) => toMoment(date).clone().startOf('month').format('YYYY-MM-DD');
<|endoftext|>


<commit_before>var net = require('net'),
  crypto = require('crypto'),
  util = require('util'),
  EventEmitter = require('events').EventEmitter,
  _ = require('underscore'),
  async = require('async'),
  Peer = require('./peer'),
  pjson = require('../../package.json');

/**
 * Creates new Network object
 * @class Implements Ethereum's Wire Protocol and provides networking functions.
 * @param {Object} options
 * @param {Number} [options.protocolVersion=1] The network version
 * @param {String} [options.publicIp] The public ip address of this instance
 * @param {Object} [options.caps] A hash containing the capbilities of this node and their corrisponding version numbers
 * @param {Number} [options.timeout=20000] The length of time in milliseconds to wait for a peer to response after connecting to it
 * @param {Number} [options.maxPeers=10] The max number of peer the network will try to connect to
 * @param {String} [options.clientId] Specifies the client software identity, as a human-readable string
 * @param {String} [options.nodeId] the Unique Identity of the node and specifies a 512-bit hash that identifies this node.
 * @property {Array.<Peer>} peers an array of connected peers
 * @property {Array.<Peer>} knownPeers an array of peers the server knows about but is not connected to. The server uses this list to replace peers that disconnect.
 */
var Network = exports = module.exports = function(options) {

  //Register as event emitter
  EventEmitter.call(this);

  //setup defaults
  var optionDefaults = {
    protocolVersion: 2,
    timeout: 20000, //10 seconds
    maxPeers: 10,
    clientId: 'Ethereum Node.js/' + pjson.version,
    networkID: 0,
    capabilities: {
      'eth': 42
    }
  };

  options = options ? options : {};
  _.defaults(options, optionDefaults);
  _.defaults(this, options);

  if (!this.id) {
    //generate a node id
    var hash = crypto.createHash('sha512');
    hash.update((Math.random())
      .toString());

    this.id = new Buffer(hash.digest('hex'), 'hex');
  }

  this._peers = {}; //list of peer connected to
  this._peersList = {}; //list of know but not connected to peers
  this._connectingPeers = {}; //open socket connections
  this._stopping = false;
  this.port = 0;

  Object.defineProperties(this, {
    knownPeers: {
      get: function() {
        return _.values(this._peersList);
      }
    },
    peers: {
      get: function() {
        return _.values(this._peers);
      }
    }
  });

  this.server = net.createServer(this._onConnect.bind(this));
};

util.inherits(Network, EventEmitter);

/**
 * start the server
 * @method listen
 * @param {Number} [port=30303] The hostname or IP address the server is bound to. Defaults to 0.0.0.0 which means any available network
 * @param {String} [host='0.0.0.0'] The TPC port the server is listening to. Defaults to port 30303
 */
Network.prototype.listen = function(port, host, cb) {
  var self = this;
  this.host = host ? host : '0.0.0.0';
  this.port = port ? port : 30303;
  this.server.listen(this.port, this.host, function() {
    self._listening = true;
    if (_.isFunction(cb)) {
      cb();
    }
  });
};

/**
 * connects to a peer
 * @method connect
 * @param {Number} port the port of the peer
 * @param {String} host the hostname or IP of the peer
 * @param {Function} cb the callback
 */
Network.prototype.connect = function(port, host, cb) {
  var socket = new net.Socket(),
    self = this,
    _cb;

  if (!cb) {
    _cb = function() {};
  } else {
    _cb = function(err) {
      cb(err);
      cb = function() {};
    };
  }

  function onError(e) {
    socket.destroy();
    _cb(e);
  }

  socket.setTimeout(this.timeout);
  socket.on('timeout', onError);
  socket.on('error', onError);
  socket.on('connect', function() {
    self._onConnect(socket);
    _cb();
  });
  socket.connect(port, host);
  return socket;
};

//creates a new peer object and adds it to the peer hash
Network.prototype._onConnect = function(socket) {

  if (!this.publicIp) {
    this.publicIp = socket.localAddress;
  }

  //save the socket until it full connects
  socket.id = socket.remoteAddress + ':' + socket.remotePort;
  this._connectingPeers[socket.id] = socket;

  var peer = new Peer(socket, this),
    self = this;

  //disconnect delete peers
  socket.on('close', function() {
    self.emit('closing', peer);
    //delete refrances to the peer
    delete self._peers[peer.id];
    self._popPeerList();
  });

  peer.on('message.hello', function(hello) {
    delete self._connectingPeers[socket.id];
    self._peers[hello.id] = peer;
  });

  peer.on('message.peers', function(peers) {
    var peersToBroadcast = [];
    for (var i = 0; i < peers.length; i++) {
      //save only the peers that are listening and to peers we are not already connected to
      if (peers[i].port !== 0 && !self._peers[peers[i].id] && self.id !== peers[i].id) {
        //create uid and save to peerlist
        self._peersList[peers[i].id] = peers[i];
        peersToBroadcast.push(peers[i]);
      }
    }

    self.broadcastPeers(peersToBroadcast);
    //connects to new peers
    self._popPeerList();
  });

  this.emit('connect', peer);
};

/**
 * stops the tcp server and disconnects any peers
 * @method stop
 * @param {Function} cb the callback
 */
Network.prototype.stop = function(cb) {
  var self = this;
  this._stopping = true;

  for (var peer in this._connectingPeers){
    self._connectingPeers[peer].end();
  }

  //disconnect all the peers
  async.each(this.peers, function(peer, cb2) {
    peer.socket.once('end', cb2);
    //0x08 Client quitting.
    peer.sendDisconnect(0x08, function() {
      peer.socket.end();
    });
  }, function() {
    //disconnect peers
    if (self._listening) {
      self.server.close(cb);
    } else if (cb) {
      cb();
    }
  });
};

//broadcast an array of blocks to the network
Network.prototype.broadcastBlocks = function(blocks, cb) {
  this._broadcast('sendBlocks', blocks, cb);
};

//broadcast an array of transactions to the network
Network.prototype.broadcastTransactions = function(txs, cb) {
  this._broadcast('sendTransactions', txs, cb);
};

Network.prototype.broadcastGetPeers = function(cb) {
  this._broadcast('sendGetPeers', cb);
};

Network.prototype.broadcastPing = function(cb) {
  this._broadcast('sendPing', cb);
};

Network.prototype.broadcastGetChain = function(parents, count, cb) {
  this._broadcast('sendGetChain', parents, count, cb);
};

Network.prototype.broadcastGetTransactions = function(cb) {
  this._broadcast('sendGetTransactions', cb);
};

Network.prototype.broadcastDisconnect = function(reason, cb) {
  this._broadcast('sendDisconnect', reason, cb);
};

Network.prototype.broadcastPeers = function(peers, cb) {
  this._broadcast('_sendPeers', peers, cb);
};

Network.prototype.broadcastNewBlock = function(block, td, cb) {
  this._broadcast('sendNewBlock', block, td, cb);
};

/**
 * broadcast messages to the network
 * @method _broadcast
 * @param {String} functionName - one peer's sending functions
 * @param {..} - the argments for the function
 * @param cb - a callback
 * @private
 */
Network.prototype._broadcast = function() {
  var args = Array.prototype.slice.call(arguments),
    cb,
    fn = args.shift();

  if (_.isFunction(arguments[arguments.length - 1])) {
    cb = arguments.pop();
  }

  async.each(this.peers, function(peer, cb2) {
    var fargs = args.slice();
    fargs.push(cb2);
    peer[fn].apply(peer, fargs);
  }, cb);
};

/**
 * Pops peers off the peer list and connects to them untill we reach maxPeers
 * or we run out of peer in the peer list
 * @private
 */
Network.prototype._popPeerList = function() {

  var openSlots = this.maxPeers - this.peers.length,
    self = this;

  if (this.knownPeers.length > 0 && openSlots > 0 && !this._stopping) {
    var peers = this.knownPeers.splice(0, openSlots);
    async.each(peers, function(peer, done) {
      delete self._peersList[peer.id];
      self.connect(peer.port, peer.publicIp, done);
    });
  }
};
<commit_msg>bump 43<commit_after>var net = require('net'),
  crypto = require('crypto'),
  util = require('util'),
  EventEmitter = require('events').EventEmitter,
  _ = require('underscore'),
  async = require('async'),
  Peer = require('./peer'),
  pjson = require('../../package.json');

/**
 * Creates new Network object
 * @class Implements Ethereum's Wire Protocol and provides networking functions.
 * @param {Object} options
 * @param {Number} [options.protocolVersion=1] The network version
 * @param {String} [options.publicIp] The public ip address of this instance
 * @param {Object} [options.caps] A hash containing the capbilities of this node and their corrisponding version numbers
 * @param {Number} [options.timeout=20000] The length of time in milliseconds to wait for a peer to response after connecting to it
 * @param {Number} [options.maxPeers=10] The max number of peer the network will try to connect to
 * @param {String} [options.clientId] Specifies the client software identity, as a human-readable string
 * @param {String} [options.nodeId] the Unique Identity of the node and specifies a 512-bit hash that identifies this node.
 * @property {Array.<Peer>} peers an array of connected peers
 * @property {Array.<Peer>} knownPeers an array of peers the server knows about but is not connected to. The server uses this list to replace peers that disconnect.
 */
var Network = exports = module.exports = function(options) {

  //Register as event emitter
  EventEmitter.call(this);

  //setup defaults
  var optionDefaults = {
    protocolVersion: 2,
    timeout: 20000, //10 seconds
    maxPeers: 10,
    clientId: 'Ethereum Node.js/' + pjson.version,
    networkID: 0,
    capabilities: {
      'eth': 43
    }
  };

  options = options ? options : {};
  _.defaults(options, optionDefaults);
  _.defaults(this, options);

  if (!this.id) {
    //generate a node id
    var hash = crypto.createHash('sha512');
    hash.update((Math.random())
      .toString());

    this.id = new Buffer(hash.digest('hex'), 'hex');
  }

  this._peers = {}; //list of peer connected to
  this._peersList = {}; //list of know but not connected to peers
  this._connectingPeers = {}; //open socket connections
  this._stopping = false;
  this.port = 0;

  Object.defineProperties(this, {
    knownPeers: {
      get: function() {
        return _.values(this._peersList);
      }
    },
    peers: {
      get: function() {
        return _.values(this._peers);
      }
    }
  });

  this.server = net.createServer(this._onConnect.bind(this));
};

util.inherits(Network, EventEmitter);

/**
 * start the server
 * @method listen
 * @param {Number} [port=30303] The hostname or IP address the server is bound to. Defaults to 0.0.0.0 which means any available network
 * @param {String} [host='0.0.0.0'] The TPC port the server is listening to. Defaults to port 30303
 */
Network.prototype.listen = function(port, host, cb) {
  var self = this;
  this.host = host ? host : '0.0.0.0';
  this.port = port ? port : 30303;
  this.server.listen(this.port, this.host, function() {
    self._listening = true;
    if (_.isFunction(cb)) {
      cb();
    }
  });
};

/**
 * connects to a peer
 * @method connect
 * @param {Number} port the port of the peer
 * @param {String} host the hostname or IP of the peer
 * @param {Function} cb the callback
 */
Network.prototype.connect = function(port, host, cb) {
  var socket = new net.Socket(),
    self = this,
    _cb;

  if (!cb) {
    _cb = function() {};
  } else {
    _cb = function(err) {
      cb(err);
      cb = function() {};
    };
  }

  function onError(e) {
    socket.destroy();
    _cb(e);
  }

  socket.setTimeout(this.timeout);
  socket.on('timeout', onError);
  socket.on('error', onError);
  socket.on('connect', function() {
    self._onConnect(socket);
    _cb();
  });
  socket.connect(port, host);
  return socket;
};

//creates a new peer object and adds it to the peer hash
Network.prototype._onConnect = function(socket) {

  if (!this.publicIp) {
    this.publicIp = socket.localAddress;
  }

  //save the socket until it full connects
  socket.id = socket.remoteAddress + ':' + socket.remotePort;
  this._connectingPeers[socket.id] = socket;

  var peer = new Peer(socket, this),
    self = this;

  //disconnect delete peers
  socket.on('close', function() {
    self.emit('closing', peer);
    //delete refrances to the peer
    delete self._peers[peer.id];
    self._popPeerList();
  });

  peer.on('message.hello', function(hello) {
    delete self._connectingPeers[socket.id];
    self._peers[hello.id] = peer;
  });

  peer.on('message.peers', function(peers) {
    var peersToBroadcast = [];
    for (var i = 0; i < peers.length; i++) {
      //save only the peers that are listening and to peers we are not already connected to
      if (peers[i].port !== 0 && !self._peers[peers[i].id] && self.id !== peers[i].id) {
        //create uid and save to peerlist
        self._peersList[peers[i].id] = peers[i];
        peersToBroadcast.push(peers[i]);
      }
    }

    self.broadcastPeers(peersToBroadcast);
    //connects to new peers
    self._popPeerList();
  });

  this.emit('connect', peer);
};

/**
 * stops the tcp server and disconnects any peers
 * @method stop
 * @param {Function} cb the callback
 */
Network.prototype.stop = function(cb) {
  var self = this;
  this._stopping = true;

  for (var peer in this._connectingPeers){
    self._connectingPeers[peer].end();
  }

  //disconnect all the peers
  async.each(this.peers, function(peer, cb2) {
    peer.socket.once('end', cb2);
    //0x08 Client quitting.
    peer.sendDisconnect(0x08, function() {
      peer.socket.end();
    });
  }, function() {
    //disconnect peers
    if (self._listening) {
      self.server.close(cb);
    } else if (cb) {
      cb();
    }
  });
};

//broadcast an array of blocks to the network
Network.prototype.broadcastBlocks = function(blocks, cb) {
  this._broadcast('sendBlocks', blocks, cb);
};

//broadcast an array of transactions to the network
Network.prototype.broadcastTransactions = function(txs, cb) {
  this._broadcast('sendTransactions', txs, cb);
};

Network.prototype.broadcastGetPeers = function(cb) {
  this._broadcast('sendGetPeers', cb);
};

Network.prototype.broadcastPing = function(cb) {
  this._broadcast('sendPing', cb);
};

Network.prototype.broadcastGetChain = function(parents, count, cb) {
  this._broadcast('sendGetChain', parents, count, cb);
};

Network.prototype.broadcastGetTransactions = function(cb) {
  this._broadcast('sendGetTransactions', cb);
};

Network.prototype.broadcastDisconnect = function(reason, cb) {
  this._broadcast('sendDisconnect', reason, cb);
};

Network.prototype.broadcastPeers = function(peers, cb) {
  this._broadcast('_sendPeers', peers, cb);
};

Network.prototype.broadcastNewBlock = function(block, td, cb) {
  this._broadcast('sendNewBlock', block, td, cb);
};

/**
 * broadcast messages to the network
 * @method _broadcast
 * @param {String} functionName - one peer's sending functions
 * @param {..} - the argments for the function
 * @param cb - a callback
 * @private
 */
Network.prototype._broadcast = function() {
  var args = Array.prototype.slice.call(arguments),
    cb,
    fn = args.shift();

  if (_.isFunction(arguments[arguments.length - 1])) {
    cb = arguments.pop();
  }

  async.each(this.peers, function(peer, cb2) {
    var fargs = args.slice();
    fargs.push(cb2);
    peer[fn].apply(peer, fargs);
  }, cb);
};

/**
 * Pops peers off the peer list and connects to them untill we reach maxPeers
 * or we run out of peer in the peer list
 * @private
 */
Network.prototype._popPeerList = function() {

  var openSlots = this.maxPeers - this.peers.length,
    self = this;

  if (this.knownPeers.length > 0 && openSlots > 0 && !this._stopping) {
    var peers = this.knownPeers.splice(0, openSlots);
    async.each(peers, function(peer, done) {
      delete self._peersList[peer.id];
      self.connect(peer.port, peer.publicIp, done);
    });
  }
};
<|endoftext|>