<commit_before>// TODO:
// - Support magnet metadata protocol for trackerless torrents
// - What happens when a peer connects and handhsakes for an infoHash that we don't have?
// - Add reconnect behavior for incoming connections, too
//   - Need to discover their listening port first

module.exports = Swarm
// module.exports.Peer = Peer
// module.exports.Pool = Pool

var EventEmitter = require('events').EventEmitter
var inherits = require('inherits')
var net = require('net')
var once = require('once')
var Wire = require('bittorrent-protocol')

var MAX_SIZE = 100
var HANDSHAKE_TIMEOUT = 5000
var RECONNECT_WAIT = [1000, 5000, 15000, 30000, 60000, 120000, 300000, 600000]

/**
 * Peer
 * ====
 * A peer in the swarm. Comprised of a `net.Socket` and a `Wire`.
 *
 * @param {string} addr
 */
function Peer (addr) {
  this.addr = addr

  this.conn = null
  this.wire = null

  this._retries = 0
  this._timeout = null
}

/**
 * Called once the peer's `conn` has connected (i.e. fired 'connect')
 * @param {Socket} conn
 */
Peer.prototype.onconnect = function (conn) {
  this.conn = conn
  this.retries = 0

  var wire = this.wire = new Wire()
  wire.remoteAddress = this.addr
  var destroy = once(function () {
    this.conn.destroy()
    this.conn = null
  }.bind(this))

  // Close the wire when the connection is destroyed
  conn.once('end', function () { destroy() })
  conn.once('error', function () { destroy() })
  conn.once('close', function () { wire.end() })

  wire.once('end', function () {
    this.wire = null
  }.bind(this))

  // Duplex streaming magic!
  conn.pipe(wire).pipe(conn)
}

/**
 * Pool
 * ====
 * A "pool" is a bunch of swarms all listening on the same TCP port for
 * incoming connections from peers who are interested in one of our swarms.
 * There is one Pool for every port that a swarm is listening on, and they are
 * all stored in the `Pool.pools` object. When a connection comes in, the pool
 * does the wire protocol handshake with the peer to determine which swarm they
 * are interested in, and routes the connection to the right swarm.
 *
 * @param {number} port
 */
function Pool (port) {
  this.port = port
  this.swarms = {} // infoHash -> Swarm
  this.listening = false

  // Keep track of incoming connections so we can destroy them if we need to
  // close the server later.
  this.conns = []

  this.server = net.createServer(this._onconn.bind(this))
  this.server.listen(this.port, this._onlistening.bind(this))
  this.server.on('error', this._onerror.bind(this))

  this._retries = 0
}

/**
 * In-use Pools (port -> Pool)
 */
Pool.pools = {}

/**
 * STATIC METHOD: Add a swarm to a pool, creating a new pool if necessary.
 * @param {Swarm} swarm
 */
Pool.add = function (swarm) {
  var port = swarm.port
  var pool = Pool.pools[port]

  if (!pool)
    pool = Pool.pools[port] = new Pool(port)

  pool.addSwarm(swarm)
}

/**
 * STATIC METHOD: Remove a swarm from its pool.
 * @param  {Swarm} swarm
 */
Pool.remove = function (swarm) {
  var port = swarm.port
  var pool = Pool.pools[port]
  if (!pool) return

  pool.removeSwarm(swarm)

  if (Object.keys(pool.swarms).length === 0)
    delete Pool.pools[port]
}

Pool.prototype._onlistening = function () {
  this.listening = true
  for (var infoHash in this.swarms) {
    var swarm = this.swarms[infoHash]
    swarm.emit('listening')
  }
}

Pool.prototype._onconn = function (conn) {
  // Track all conns in this pool
  this.conns.push(conn)
  conn.on('close', function () {
    this.conns.splice(this.conns.indexOf(conn))
  }.bind(this))

  var addr = conn.remoteAddress + ':' + conn.remotePort

  // On incoming connections, we expect the remote peer to send a handshake
  // first. Based on the infoHash in that handshake, route the peer to the
  // right swarm.
  var peer = new Peer(addr)
  peer.onconnect(conn)

  // TODO: add timeout to wait for handshake for

  peer.wire.on('handshake', function (infoHash, peerId, extensions) {
    var swarm = this.swarms[infoHash.toString('hex')]
    if (!swarm)
      return conn.destroy()

    swarm._onincoming(peer)
  }.bind(this))
}

Pool.prototype._onerror = function (err) {
  if (err.code == 'EADDRINUSE' && this._retries < 5) {
    console.error('Address in use, retrying...')
    setTimeout(function () {
      this._retries += 1
      this.server.close()
      this.server.listen(this.port)
    }.bind(this), 1000)
  } else {
    this.swarms.forEach(function (swarm) {
      swarm.emit('error', 'Swarm listen error: ' + err.message)
    })
  }
}

/**
 * Destroy this pool.
 * @param  {function} cb
 */
Pool.prototype.destroy = function (cb) {
  // Destroy all open connections & wire objects so the server can gracefully
  // close without waiting for timeout or the remote peer to disconnect.
  this.conns.forEach(function (conn) {
    conn.destroy()
  })
  this.server.close(cb)
}

/**
 * Add a swarm to this pool.
 * @param {Swarm} swarm
 */
Pool.prototype.addSwarm = function (swarm) {
  var infoHash = swarm.infoHash.toString('hex')

  if (this.listening) {
    process.nextTick(function () {
      swarm.emit('listening')
    })
  }

  if (this.swarms[infoHash]) {
    process.nextTick(function() {
      swarm.emit('error', new Error('Swarm listen error: There is already a ' +
        'swarm with infoHash ' + swarm.infoHash + ' listening on port ' +
        swarm.port))
    })
    return
  }

  this.swarms[infoHash] = swarm
}

/**
 * Remove a swarm from this pool.
 * @param  {Swarm} swarm
 */
Pool.prototype.removeSwarm = function (swarm) {
  var infoHash = swarm.infoHash.toString('hex')
  delete this.swarms[infoHash]

  if (Object.keys(this.swarms).length === 0)
    this.destroy()
}

inherits(Swarm, EventEmitter)

/**
 * Swarm
 * =====
 * Abstraction of a BitTorrent "swarm", which is handy for managing all peer
 * connections for a given torrent download. This handles connecting to peers,
 * listening for incoming connections, and doing the initial peer wire protocol
 * handshake with peers. It also tracks total data uploaded/downloaded to/from
 * the swarm.
 *
 * Events: wire, download, upload, error, close
 *
 * @param {Buffer|string} infoHash
 * @param {Buffer|string} peerId
 * @param {Object} extensions
 */
function Swarm (infoHash, peerId, extensions) {
  if (!(this instanceof Swarm)) return new Swarm(infoHash, peerId)

  EventEmitter.call(this)

  this.infoHash = typeof infoHash === 'string'
    ? new Buffer(infoHash, 'hex')
    : infoHash

  this.peerId = typeof peerId === 'string'
    ? new Buffer(peerId, 'utf8')
    : peerId

  this.extensions = extensions

  this.port = 0
  this.downloaded = 0
  this.uploaded = 0

  this.wires = [] // open wires (added *after* handshake)

  this._queue = [] // queue of peers to connect to
  this._peers = {} // connected peers (addr -> Peer)

  this._paused = false
  this._destroyed = false
}

Object.defineProperty(Swarm.prototype, 'numQueued', {
  get: function () {
    return this._queue.length
  }
})

Object.defineProperty(Swarm.prototype, 'numConns', {
  get: function () {
    return Object.keys(this._peers)
      .map(function (addr) {
        return this._peers[addr].conn ? 1 : 0
      }.bind(this))
      .reduce(function (prev, current) {
        return prev + current
      }, 0)
  }
})

/**
 * Add a peer to the swarm.
 * @param {string} addr  ip address and port (ex: 12.34.56.78:12345)
 */
Swarm.prototype.add = function (addr) {
  if (this._destroyed || this._peers[addr]) return

  var peer = new Peer(addr)
  this._peers[addr] = peer
  this._queue.push(peer)

  this._drain()
}

/**
 * Temporarily stop connecting to new peers. Note that this does not pause new
 * incoming connections, nor does it pause the streams of existing connections
 * or their wires.
 */
Swarm.prototype.pause = function () {
  this._paused = true
}

/**
 * Resume connecting to new peers.
 */
Swarm.prototype.resume = function () {
  this._paused = false
  this._drain()
}

/**
 * Remove a peer from the swarm.
 * @param  {string} addr  ip address and port (ex: 12.34.56.78:12345)
 */
Swarm.prototype.remove = function (addr) {
  this._remove(addr)
  this._drain()
}

/**
 * Private method to remove a peer from the swarm without calling _drain().
 * @param  {string} addr  ip address and port (ex: 12.34.56.78:12345)
 */
Swarm.prototype._remove = function (addr) {
  var peer = this._peers[addr]
  if (!peer) return
  delete this._peers[addr]
  if (peer.node)
    this._queue.splice(this._queue.indexOf(peer), 1)
  if (peer._timeout)
    clearTimeout(peer._timeout)
  if (peer.wire)
    peer.wire.destroy()
}

/**
 * Listen on the given port for peer connections.
 * @param  {number} port
 * @param  {function} onlistening
 */
Swarm.prototype.listen = function (port, onlistening) {
  this.port = port
  if (onlistening)
    this.once('listening', onlistening)

  Pool.add(this)
}

/**
 * Destroy the swarm, close all open peer connections, and do cleanup.
 */
Swarm.prototype.destroy = function() {
  this._destroyed = true

  for (var addr in this._peers) {
    this._remove(addr)
  }

  Pool.remove(this)

  process.nextTick(function() {
    this.emit('close')
  }.bind(this))
}

/**
 * Pop a peer off the FIFO queue and connect to it. When _drain() gets called,
 * the queue will usually have only one peer in it, except when there are too
 * many peers (over `this.maxSize`) in which case they will just sit in the queue
 * until another connection closes.
 */
Swarm.prototype._drain = function () {
  if (this.numConns >= MAX_SIZE || this._paused) return

  var peer = this._queue.shift()
  if (!peer) return

  if (this.timeout) {
    clearTimeout(this.timeout)
    this.timeout = null
  }

  var parts = peer.addr.split(':')
  var conn = net.connect(parts[1], parts[0])

  var onhandshake = function (infoHash) {
    if (infoHash.toString('hex') !== this.infoHash.toString('hex'))
      return peer.conn.destroy()
    this._onwire(peer)
  }.bind(this)

  var onconnect = function () {
    peer.onconnect(conn)
    this._onconn(peer)

    var wire = peer.wire
    wire.on('handshake', onhandshake)

    // When wire dies, repeatedly attempt to reconnect to the peer, after a
    // timeout, with exponential backoff.
    wire.on('end', function () {
      if (this._destroyed
          || wire.destroyed
          || peer.retries >= RECONNECT_WAIT.length)
        return this._remove(peer.addr)

      var readd = function () {
        this._queue.push(peer)
        this._drain()
      }.bind(this)

      peer.timeout = setTimeout(readd, RECONNECT_WAIT[peer.retries++])
    }.bind(this))

    wire.handshake(this.infoHash, this.peerId, this.extensions)
  }.bind(this)

  conn.on('connect', onconnect)

  // Handshake
  // var timeout = setTimeout(function () {
  //   conn.destroy()
  // }, HANDSHAKE_TIMEOUT)

  // wire.once('handshake', function (infoHash, peerId, extensions) {
  //   console.log('HANDSHAKE SUCCESS, extensions:')
  //   console.log(extensions)
  //   clearTimeout(timeout)

  //   if (extensions.extended) {
  //     wire.extended(0, {
  //       m: {
  //         ut_metadata: 1
  //       }
  //       // TODO - this should be set once we have metadata
  //       // metadata_size: xx
  //     })
  //   }
}

/**
 * Called whenever a new peer wants to connects to this swarm. Called with a
 * peer that has already sent us a handshake.
 * @param  {Peer} peer
 */
Swarm.prototype._onincoming = function (peer) {
  this._peers[peer.wire.remoteAddress] = peer
  peer.wire.handshake(this.infoHash, this.peerId, this.extensions)

  this._onconn(peer)
  this._onwire(peer)
}

//
// HELPER METHODS
//

/**
 * Called whenever a new connection is connected.
 * @param  {Socket} conn
 */
Swarm.prototype._onconn = function (peer) {
  peer.conn.once('close', function () {
    this._drain() // allow another connection to be opened
  }.bind(this))
}

/**
 * Called whenever we've handshaken with a new wire.
 * @param  {Peer} peer
 */
Swarm.prototype._onwire = function (peer) {
  var conn = peer.conn
  var wire = peer.wire

  // Track total bytes downloaded by the swarm
  wire.on('download', function (downloaded) {
    this.downloaded += downloaded
    this.emit('download', downloaded)
  }.bind(this))

  // Track total bytes uploaded by the swarm
  wire.on('upload', function (uploaded) {
    this.uploaded += uploaded
    this.emit('upload', uploaded)
  }.bind(this))

  var cleanup = once(function () {
    this.wires.splice(this.wires.indexOf(wire), 1)
    conn.destroy()
  }.bind(this))

  wire.on('end', cleanup)
  wire.on('close', cleanup)
  wire.on('error', cleanup)
  wire.on('finish', cleanup)

  this.wires.push(wire)
  this.emit('wire', wire)
}
<commit_msg>remove dead code for metadata - moved to webtorrent<commit_after>// TODO:
// - Support magnet metadata protocol for trackerless torrents
// - What happens when a peer connects and handhsakes for an infoHash that we don't have?
// - Add reconnect behavior for incoming connections, too
//   - Need to discover their listening port first

module.exports = Swarm
// module.exports.Peer = Peer
// module.exports.Pool = Pool

var EventEmitter = require('events').EventEmitter
var inherits = require('inherits')
var net = require('net')
var once = require('once')
var Wire = require('bittorrent-protocol')

var MAX_SIZE = 100
var HANDSHAKE_TIMEOUT = 5000
var RECONNECT_WAIT = [1000, 5000, 15000, 30000, 60000, 120000, 300000, 600000]

/**
 * Peer
 * ====
 * A peer in the swarm. Comprised of a `net.Socket` and a `Wire`.
 *
 * @param {string} addr
 */
function Peer (addr) {
  this.addr = addr

  this.conn = null
  this.wire = null

  this._retries = 0
  this._timeout = null
}

/**
 * Called once the peer's `conn` has connected (i.e. fired 'connect')
 * @param {Socket} conn
 */
Peer.prototype.onconnect = function (conn) {
  this.conn = conn
  this.retries = 0

  var wire = this.wire = new Wire()
  wire.remoteAddress = this.addr
  var destroy = once(function () {
    this.conn.destroy()
    this.conn = null
  }.bind(this))

  // Close the wire when the connection is destroyed
  conn.once('end', function () { destroy() })
  conn.once('error', function () { destroy() })
  conn.once('close', function () { wire.end() })

  wire.once('end', function () {
    this.wire = null
  }.bind(this))

  // Duplex streaming magic!
  conn.pipe(wire).pipe(conn)
}

/**
 * Pool
 * ====
 * A "pool" is a bunch of swarms all listening on the same TCP port for
 * incoming connections from peers who are interested in one of our swarms.
 * There is one Pool for every port that a swarm is listening on, and they are
 * all stored in the `Pool.pools` object. When a connection comes in, the pool
 * does the wire protocol handshake with the peer to determine which swarm they
 * are interested in, and routes the connection to the right swarm.
 *
 * @param {number} port
 */
function Pool (port) {
  this.port = port
  this.swarms = {} // infoHash -> Swarm
  this.listening = false

  // Keep track of incoming connections so we can destroy them if we need to
  // close the server later.
  this.conns = []

  this.server = net.createServer(this._onconn.bind(this))
  this.server.listen(this.port, this._onlistening.bind(this))
  this.server.on('error', this._onerror.bind(this))

  this._retries = 0
}

/**
 * In-use Pools (port -> Pool)
 */
Pool.pools = {}

/**
 * STATIC METHOD: Add a swarm to a pool, creating a new pool if necessary.
 * @param {Swarm} swarm
 */
Pool.add = function (swarm) {
  var port = swarm.port
  var pool = Pool.pools[port]

  if (!pool)
    pool = Pool.pools[port] = new Pool(port)

  pool.addSwarm(swarm)
}

/**
 * STATIC METHOD: Remove a swarm from its pool.
 * @param  {Swarm} swarm
 */
Pool.remove = function (swarm) {
  var port = swarm.port
  var pool = Pool.pools[port]
  if (!pool) return

  pool.removeSwarm(swarm)

  if (Object.keys(pool.swarms).length === 0)
    delete Pool.pools[port]
}

Pool.prototype._onlistening = function () {
  this.listening = true
  for (var infoHash in this.swarms) {
    var swarm = this.swarms[infoHash]
    swarm.emit('listening')
  }
}

Pool.prototype._onconn = function (conn) {
  // Track all conns in this pool
  this.conns.push(conn)
  conn.on('close', function () {
    this.conns.splice(this.conns.indexOf(conn))
  }.bind(this))

  var addr = conn.remoteAddress + ':' + conn.remotePort

  // On incoming connections, we expect the remote peer to send a handshake
  // first. Based on the infoHash in that handshake, route the peer to the
  // right swarm.
  var peer = new Peer(addr)
  peer.onconnect(conn)

  // TODO: add timeout to wait for handshake for

  peer.wire.on('handshake', function (infoHash, peerId, extensions) {
    var swarm = this.swarms[infoHash.toString('hex')]
    if (!swarm)
      return conn.destroy()

    swarm._onincoming(peer)
  }.bind(this))
}

Pool.prototype._onerror = function (err) {
  if (err.code == 'EADDRINUSE' && this._retries < 5) {
    console.error('Address in use, retrying...')
    setTimeout(function () {
      this._retries += 1
      this.server.close()
      this.server.listen(this.port)
    }.bind(this), 1000)
  } else {
    this.swarms.forEach(function (swarm) {
      swarm.emit('error', 'Swarm listen error: ' + err.message)
    })
  }
}

/**
 * Destroy this pool.
 * @param  {function} cb
 */
Pool.prototype.destroy = function (cb) {
  // Destroy all open connections & wire objects so the server can gracefully
  // close without waiting for timeout or the remote peer to disconnect.
  this.conns.forEach(function (conn) {
    conn.destroy()
  })
  this.server.close(cb)
}

/**
 * Add a swarm to this pool.
 * @param {Swarm} swarm
 */
Pool.prototype.addSwarm = function (swarm) {
  var infoHash = swarm.infoHash.toString('hex')

  if (this.listening) {
    process.nextTick(function () {
      swarm.emit('listening')
    })
  }

  if (this.swarms[infoHash]) {
    process.nextTick(function() {
      swarm.emit('error', new Error('Swarm listen error: There is already a ' +
        'swarm with infoHash ' + swarm.infoHash + ' listening on port ' +
        swarm.port))
    })
    return
  }

  this.swarms[infoHash] = swarm
}

/**
 * Remove a swarm from this pool.
 * @param  {Swarm} swarm
 */
Pool.prototype.removeSwarm = function (swarm) {
  var infoHash = swarm.infoHash.toString('hex')
  delete this.swarms[infoHash]

  if (Object.keys(this.swarms).length === 0)
    this.destroy()
}

inherits(Swarm, EventEmitter)

/**
 * Swarm
 * =====
 * Abstraction of a BitTorrent "swarm", which is handy for managing all peer
 * connections for a given torrent download. This handles connecting to peers,
 * listening for incoming connections, and doing the initial peer wire protocol
 * handshake with peers. It also tracks total data uploaded/downloaded to/from
 * the swarm.
 *
 * Events: wire, download, upload, error, close
 *
 * @param {Buffer|string} infoHash
 * @param {Buffer|string} peerId
 * @param {Object} extensions
 */
function Swarm (infoHash, peerId, extensions) {
  if (!(this instanceof Swarm)) return new Swarm(infoHash, peerId)

  EventEmitter.call(this)

  this.infoHash = typeof infoHash === 'string'
    ? new Buffer(infoHash, 'hex')
    : infoHash

  this.peerId = typeof peerId === 'string'
    ? new Buffer(peerId, 'utf8')
    : peerId

  this.extensions = extensions

  this.port = 0
  this.downloaded = 0
  this.uploaded = 0

  this.wires = [] // open wires (added *after* handshake)

  this._queue = [] // queue of peers to connect to
  this._peers = {} // connected peers (addr -> Peer)

  this._paused = false
  this._destroyed = false
}

Object.defineProperty(Swarm.prototype, 'numQueued', {
  get: function () {
    return this._queue.length
  }
})

Object.defineProperty(Swarm.prototype, 'numConns', {
  get: function () {
    return Object.keys(this._peers)
      .map(function (addr) {
        return this._peers[addr].conn ? 1 : 0
      }.bind(this))
      .reduce(function (prev, current) {
        return prev + current
      }, 0)
  }
})

/**
 * Add a peer to the swarm.
 * @param {string} addr  ip address and port (ex: 12.34.56.78:12345)
 */
Swarm.prototype.add = function (addr) {
  if (this._destroyed || this._peers[addr]) return

  var peer = new Peer(addr)
  this._peers[addr] = peer
  this._queue.push(peer)

  this._drain()
}

/**
 * Temporarily stop connecting to new peers. Note that this does not pause new
 * incoming connections, nor does it pause the streams of existing connections
 * or their wires.
 */
Swarm.prototype.pause = function () {
  this._paused = true
}

/**
 * Resume connecting to new peers.
 */
Swarm.prototype.resume = function () {
  this._paused = false
  this._drain()
}

/**
 * Remove a peer from the swarm.
 * @param  {string} addr  ip address and port (ex: 12.34.56.78:12345)
 */
Swarm.prototype.remove = function (addr) {
  this._remove(addr)
  this._drain()
}

/**
 * Private method to remove a peer from the swarm without calling _drain().
 * @param  {string} addr  ip address and port (ex: 12.34.56.78:12345)
 */
Swarm.prototype._remove = function (addr) {
  var peer = this._peers[addr]
  if (!peer) return
  delete this._peers[addr]
  if (peer.node)
    this._queue.splice(this._queue.indexOf(peer), 1)
  if (peer._timeout)
    clearTimeout(peer._timeout)
  if (peer.wire)
    peer.wire.destroy()
}

/**
 * Listen on the given port for peer connections.
 * @param  {number} port
 * @param  {function} onlistening
 */
Swarm.prototype.listen = function (port, onlistening) {
  this.port = port
  if (onlistening)
    this.once('listening', onlistening)

  Pool.add(this)
}

/**
 * Destroy the swarm, close all open peer connections, and do cleanup.
 */
Swarm.prototype.destroy = function() {
  this._destroyed = true

  for (var addr in this._peers) {
    this._remove(addr)
  }

  Pool.remove(this)

  process.nextTick(function() {
    this.emit('close')
  }.bind(this))
}

/**
 * Pop a peer off the FIFO queue and connect to it. When _drain() gets called,
 * the queue will usually have only one peer in it, except when there are too
 * many peers (over `this.maxSize`) in which case they will just sit in the queue
 * until another connection closes.
 */
Swarm.prototype._drain = function () {
  if (this.numConns >= MAX_SIZE || this._paused) return

  var peer = this._queue.shift()
  if (!peer) return

  if (this.timeout) {
    clearTimeout(this.timeout)
    this.timeout = null
  }

  var parts = peer.addr.split(':')
  var conn = net.connect(parts[1], parts[0])

  var onhandshake = function (infoHash) {
    if (infoHash.toString('hex') !== this.infoHash.toString('hex'))
      return peer.conn.destroy()
    this._onwire(peer)
  }.bind(this)

  var onconnect = function () {
    peer.onconnect(conn)
    this._onconn(peer)

    var wire = peer.wire
    wire.on('handshake', onhandshake)

    // When wire dies, repeatedly attempt to reconnect to the peer, after a
    // timeout, with exponential backoff.
    wire.on('end', function () {
      if (this._destroyed
          || wire.destroyed
          || peer.retries >= RECONNECT_WAIT.length)
        return this._remove(peer.addr)

      var readd = function () {
        this._queue.push(peer)
        this._drain()
      }.bind(this)

      peer.timeout = setTimeout(readd, RECONNECT_WAIT[peer.retries++])
    }.bind(this))

    wire.handshake(this.infoHash, this.peerId, this.extensions)
  }.bind(this)

  conn.on('connect', onconnect)

  // Handshake
  // var timeout = setTimeout(function () {
  //   conn.destroy()
  // }, HANDSHAKE_TIMEOUT)

  // wire.once('handshake', function (infoHash, peerId, extensions) {
  //   console.log('HANDSHAKE SUCCESS, extensions:')
  //   console.log(extensions)
  //   clearTimeout(timeout)

  //   }
}

/**
 * Called whenever a new peer wants to connects to this swarm. Called with a
 * peer that has already sent us a handshake.
 * @param  {Peer} peer
 */
Swarm.prototype._onincoming = function (peer) {
  this._peers[peer.wire.remoteAddress] = peer
  peer.wire.handshake(this.infoHash, this.peerId, this.extensions)

  this._onconn(peer)
  this._onwire(peer)
}

//
// HELPER METHODS
//

/**
 * Called whenever a new connection is connected.
 * @param  {Socket} conn
 */
Swarm.prototype._onconn = function (peer) {
  peer.conn.once('close', function () {
    this._drain() // allow another connection to be opened
  }.bind(this))
}

/**
 * Called whenever we've handshaken with a new wire.
 * @param  {Peer} peer
 */
Swarm.prototype._onwire = function (peer) {
  var conn = peer.conn
  var wire = peer.wire

  // Track total bytes downloaded by the swarm
  wire.on('download', function (downloaded) {
    this.downloaded += downloaded
    this.emit('download', downloaded)
  }.bind(this))

  // Track total bytes uploaded by the swarm
  wire.on('upload', function (uploaded) {
    this.uploaded += uploaded
    this.emit('upload', uploaded)
  }.bind(this))

  var cleanup = once(function () {
    this.wires.splice(this.wires.indexOf(wire), 1)
    conn.destroy()
  }.bind(this))

  wire.on('end', cleanup)
  wire.on('close', cleanup)
  wire.on('error', cleanup)
  wire.on('finish', cleanup)

  this.wires.push(wire)
  this.emit('wire', wire)
}
<|endoftext|>


<commit_before>'use strict'
const os = require('os')
const stringifySafe = require('fast-safe-stringify')
const serializers = require('pino-std-serializers')
const SonicBoom = require('sonic-boom')
const events = require('./lib/events')
const redaction = require('./lib/redaction')
const timeFmt = require('./lib/time')
const proto = require('./lib/proto')
const { chindingsSym } = require('./lib/symbols')
const { setLevelState, mappings } = require('./lib/levels')
const { createArgsNormalizer } = require('./lib/tools')
const { LOG_VERSION } = require('./lib/meta')
const { epochTime, nullTime } = timeFmt

const defaultOptions = {
  safe: true,
  name: undefined,
  serializers: {},
  redact: null,
  timestamp: epochTime,
  level: 'info',
  levelVal: undefined,
  prettyPrint: false,
  base: {
    pid: process.pid,
    hostname: os.hostname()
  },
  enabled: true,
  onTerminated: function (eventName, err) {
    if (err) return process.exit(1)
    process.exit(0)
  },
  messageKey: 'msg'
}

const normalize = createArgsNormalizer(defaultOptions)

function pino (...args) {
  const { opts, stream } = normalize(...args)
  const {
    safe,
    redact,
    crlf,
    serializers,
    timestamp,
    onTerminated,
    messageKey,
    base,
    name,
    level,
    levelVal
  } = opts

  const stringify = safe ? stringifySafe : JSON.stringify
  const stringifiers = redact ? redaction(redact, stringify) : {}
  const formatOpts = redact
    ? {stringify: stringifiers[redaction.format]}
    : { stringify }
  const messageKeyString = `,"${messageKey}":`
  const end = ',"v":' + LOG_VERSION + '}' + (crlf ? '\r\n' : '\n')
  const chindings = ''
  const time = (timestamp && timestamp instanceof Function)
    ? timestamp : (timestamp ? epochTime : nullTime)
  const levels = mappings()

  const core = {
    levels,
    time,
    stream,
    stringify,
    stringifiers,
    serializers,
    end,
    timestamp,
    formatOpts,
    onTerminated,
    messageKey,
    messageKeyString,
    [chindingsSym]: chindings
  }
  Object.setPrototypeOf(core, proto)

  events(core)
  setLevelState(core, level, levelVal)

  const instance = base === null
    ? core
    : name === undefined
      ? core.child(base)
      : core.child(Object.assign({}, base, { name }))

  return instance
}

pino.extreme = (dest = process.stdout.fd) => new SonicBoom(dest, 4096)
pino.destination = (dest = process.stdout.fd) => new SonicBoom(dest)
pino.levels = mappings()
pino.stdSerializers = Object.assign({}, serializers)
pino.stdTimeFunctions = Object.assign({}, timeFmt)

pino.LOG_VERSION = LOG_VERSION

module.exports = pino
<commit_msg>simplify name<commit_after>'use strict'
const os = require('os')
const stringifySafe = require('fast-safe-stringify')
const serializers = require('pino-std-serializers')
const SonicBoom = require('sonic-boom')
const events = require('./lib/events')
const redaction = require('./lib/redaction')
const time = require('./lib/time')
const proto = require('./lib/proto')
const { chindingsSym } = require('./lib/symbols')
const { setLevelState, mappings } = require('./lib/levels')
const { createArgsNormalizer } = require('./lib/tools')
const { LOG_VERSION } = require('./lib/meta')
const { epochTime, nullTime } = time

const defaultOptions = {
  safe: true,
  name: undefined,
  serializers: {},
  redact: null,
  timestamp: epochTime,
  level: 'info',
  levelVal: undefined,
  prettyPrint: false,
  base: {
    pid: process.pid,
    hostname: os.hostname()
  },
  enabled: true,
  onTerminated: function (eventName, err) {
    if (err) return process.exit(1)
    process.exit(0)
  },
  messageKey: 'msg'
}

const normalize = createArgsNormalizer(defaultOptions)

function pino (...args) {
  const { opts, stream } = normalize(...args)
  const {
    safe,
    redact,
    crlf,
    serializers,
    timestamp,
    onTerminated,
    messageKey,
    base,
    name,
    level,
    levelVal
  } = opts

  const stringify = safe ? stringifySafe : JSON.stringify
  const stringifiers = redact ? redaction(redact, stringify) : {}
  const formatOpts = redact
    ? {stringify: stringifiers[redaction.format]}
    : { stringify }
  const messageKeyString = `,"${messageKey}":`
  const end = ',"v":' + LOG_VERSION + '}' + (crlf ? '\r\n' : '\n')
  const chindings = ''
  const time = (timestamp && timestamp instanceof Function)
    ? timestamp : (timestamp ? epochTime : nullTime)
  const levels = mappings()

  const core = {
    levels,
    time,
    stream,
    stringify,
    stringifiers,
    serializers,
    end,
    timestamp,
    formatOpts,
    onTerminated,
    messageKey,
    messageKeyString,
    [chindingsSym]: chindings
  }
  Object.setPrototypeOf(core, proto)

  events(core)
  setLevelState(core, level, levelVal)

  const instance = base === null
    ? core
    : name === undefined
      ? core.child(base)
      : core.child(Object.assign({}, base, { name }))

  return instance
}

pino.extreme = (dest = process.stdout.fd) => new SonicBoom(dest, 4096)
pino.destination = (dest = process.stdout.fd) => new SonicBoom(dest)
pino.levels = mappings()
pino.stdSerializers = Object.assign({}, serializers)
pino.stdTimeFunctions = Object.assign({}, time)
pino.LOG_VERSION = LOG_VERSION

module.exports = pino
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');
var net = require('net');

var TChannelConnection = require('./connection');
var errors = require('./errors');
var Request = require('./request');

var DEFAULT_REPORT_INTERVAL = 1000;

function TChannelPeer(channel, hostPort, options) {
    if (!(this instanceof TChannelPeer)) {
        return new TChannelPeer(channel, hostPort, options);
    }
    var self = this;
    EventEmitter.call(self);

    self.stateChangedEvent = self.defineEvent('stateChanged');
    self.allocConnectionEvent = self.defineEvent('allocConnection');

    assert(hostPort !== '0.0.0.0:0', 'Cannot create ephemeral peer');

    self.channel = channel;
    self.logger = self.channel.logger;
    self.timers = self.channel.timers;
    self.random = self.channel.random;
    self.options = options || {};
    self.hostPort = hostPort;
    self.connections = [];
    self.pendingIdentified = 0;
    self.heapElements = [];
    self.handler = new PreferOutgoingHandler(self);

    self.reportInterval = self.options.reportInterval || DEFAULT_REPORT_INTERVAL;
    if (self.reportInterval > 0 && self.channel.emitConnectionMetrics) {
        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }

    function onReport() {
        if (!self.hostPort) {
            return;
        }

        var count = self.countConnections('out');
        if (self.channel.emitConnectionMetrics) {
            self.channel.connectionsActiveStat.update(count, {
                'host-port': self.channel.hostPort,
                'peer-host-port': self.hostPort
            });
        }

        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }
}

inherits(TChannelPeer, EventEmitter);

TChannelPeer.prototype.invalidateScore = function invalidateScore() {
    var self = this;

    if (!self.heapElements.length) {
        return;
    }

    var score = self.handler.getScore();
    for (var i = 0; i < self.heapElements.length; i++) {
        var el = self.heapElements[i];
        el.rescore(score);
    }
};

TChannelPeer.prototype.isConnected = function isConnected(direction, identified) {
    var self = this;

    if (identified === undefined) identified = true;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (direction && conn.direction !== direction) {
            continue;
        } else if (conn.closing) {
            continue;
        } else if (conn.remoteName !== null || !identified) {
            return true;
        }
    }

    return false;
};

TChannelPeer.prototype.close = function close(callback) {
    var self = this;

    if (self.reportTimer) {
        self.timers.clearTimeout(self.reportTimer);
        self.reportTimer = null;
    }

    var counter = self.connections.length;
    if (counter) {
        self.connections.forEach(function eachConn(conn) {
            conn.close(onClose);
        });
    } else {
        callback(null);
    }
    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more peer sockets than expected', {
                    counter: counter
                });
            }
            callback(null);
        }
    }
};

TChannelPeer.prototype.getInConnection = function getInConnection() {
    var self = this;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (!conn.closing) return conn;
    }
    return null;
};

TChannelPeer.prototype.getOutConnection = function getOutConnection(preferIdentified) {
    var self = this;
    var candidate = null;
    for (var i = self.connections.length - 1; i >= 0; i--) {
        var conn = self.connections[i];
        if (conn.closing) continue;
        if (!preferIdentified) return conn; // user doesn't care, take last outgoing
        if (conn.remoteName) return conn; // user wanted an identified channel, and we found one
        if (!candidate) candidate = conn; // we'll fallback to returning this if we can't find an identified one
    }
    return candidate;
};

TChannelPeer.prototype.getIdentifiedOutConnection = function getIdentifiedOutConnection() {
    var self = this;
    return self.getOutConnection(true);
};

TChannelPeer.prototype.countConnections = function countConnections(direction) {
    var self = this;
    if (!direction) {
        return self.connections.length;
    }

    var count = 0;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (conn.direction === direction) {
            count++;
        }
    }

    return count;
};

TChannelPeer.prototype.connect = function connect(outOnly) {
    var self = this;
    var conn = self.getIdentifiedOutConnection();
    if (!conn || (outOnly && conn.direction !== 'out')) {
        var socket = self.makeOutSocket();
        conn = self.makeOutConnection(socket);
        self.addConnection(conn);
    }
    return conn;
};

TChannelPeer.prototype.waitForIdentified =
function waitForIdentified(callback) {
    var self = this;

    var conn = self.connect();

    if (conn.closing) {
        callback(conn.closeError);
    } else if (conn.remoteName) {
        callback(null);
    } else {
        self._waitForIdentified(conn, callback);
    }
};

TChannelPeer.prototype._waitForIdentified =
function _waitForIdentified(conn, callback) {
    var self = this;

    self.pendingIdentified++;
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);
    conn.identifiedEvent.on(onIdentified);
    self.invalidateScore();

    function onConnectionError(err) {
        finish(err);
    }

    function onConnectionClose(err) {
        finish(err);
    }

    function onIdentified() {
        finish(null);
    }

    function finish(err) {
        self.pendingIdentified = 0;
        conn.errorEvent.removeListener(onConnectionError);
        conn.closeEvent.removeListener(onConnectionClose);
        conn.identifiedEvent.removeListener(onIdentified);
        self.invalidateScore();
        callback(err);
    }
};

TChannelPeer.prototype.request = function peerRequest(options) {
    var self = this;
    options.timeout = options.timeout || Request.defaultTimeout;
    return self.connect().request(options);
};

TChannelPeer.prototype.addConnection = function addConnection(conn) {
    var self = this;
    // TODO: first approx alert for self.connections.length > 2
    // TODO: second approx support pruning
    if (conn.direction === 'out') {
        self.connections.push(conn);
    } else {
        self.connections.unshift(conn);
    }
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);

    self._maybeInvalidateScore();
    if (!conn.remoteName) {
        // TODO: could optimize if handler had a way of saying "would a new
        // identified connection change your Tier?"
        conn.identifiedEvent.on(onIdentified);
    }

    return conn;

    function onIdentified() {
        conn.identifiedEvent.removeListener(onIdentified);
        self._maybeInvalidateScore();
    }

    function onConnectionError(err) {
        removeConnection(err);
    }

    function onConnectionClose() {
        removeConnection(null);
    }

    function removeConnection(err) {
        conn.closeEvent.removeListener(onConnectionClose);
        conn.errorEvent.removeListener(onConnectionError);
        conn.identifiedEvent.removeListener(onIdentified);
        if (err) {
            var loggerInfo = {
                error: err,
                direction: conn.direction,
                remoteName: conn.remoteName,
                socketRemoteAddr: conn.socketRemoteAddr
            };

            var codeName = errors.classify(err);
            if (codeName === 'Timeout') {
                self.logger.warn('Got a connection error', loggerInfo);
            } else {
                self.logger.error('Got an unexpected connection error', loggerInfo);
            }
        }

        self.removeConnection(conn);
    }
};

TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
    var self = this;

    var ret = null;

    var index = self.connections ? self.connections.indexOf(conn) : -1;
    if (index !== -1) {
        ret = self.connections.splice(index, 1)[0];
    }

    self._maybeInvalidateScore();

    return ret;
};

TChannelPeer.prototype.makeOutSocket = function makeOutSocket() {
    var self = this;
    var parts = self.hostPort.split(':');
    assert(parts.length === 2, 'invalid destination');
    var host = parts[0];
    var port = parts[1];
    assert(host !== '0.0.0.0', 'cannot connect to ephemeral peer');
    assert(port !== '0', 'cannot connect to dynamic port');
    var socket = net.createConnection({host: host, port: port});
    return socket;
};

TChannelPeer.prototype.makeOutConnection = function makeOutConnection(socket) {
    var self = this;
    var chan = self.channel.topChannel || self.channel;
    var conn = new TChannelConnection(chan, socket, 'out', self.hostPort);
    self.allocConnectionEvent.emit(self, conn);
    return conn;
};

TChannelPeer.prototype.outPendingWeightedRandom = function outPendingWeightedRandom() {
    // A weighted random variable:
    //   random() ** (1 / weight)
    // Such that the probability distribution is uniform for weights of 0, but
    // an increasing bias with increasing weight.
    // However, although weight should start at 0 and increase probability,
    // the number of pending requests starts at 0 and should decrease
    // probability as it increases.
    // For 0 pending requests, we produce a uniform probability distribution by
    // raising a uniform random variable to the power of 1 (pending + 1).
    // As the number of pending requests increase, the magnitude of the power
    // increases and the probability distribution develops a bias toward zero.
    // TODO review weighted reservoir sampling:
    // http://arxiv.org/pdf/1012.0256.pdf
    var self = this;
    var pending = self.pendingIdentified + self.countOutPending();
    var max = Math.pow(0.5, pending);
    var min = max / 2;
    var diff = max - min;
    return min + diff * self.random();
};

TChannelPeer.prototype.countOutPending = function countOutPending() {
    var self = this;
    var pending = 0;
    for (var index = 0; index < self.connections.length; index++) {
        var connPending = self.connections[index].ops.getPending();

        pending += connPending.out;
    }
    return pending;
};

// TODO: on connection #getScore impacting event
// - on identified

// Called on connection change event
TChannelPeer.prototype._maybeInvalidateScore = function _maybeInvalidateScore() {
    var self = this;

    if (self.handler.getTier() !== self.handler.lastTier) {
        self.invalidateScore();
    }
};

TChannelPeer.prototype.getScore = function getScore() {
    var self = this;
    return self.handler.getScore();
};

var TIER_UNCONNECTED = 0;
var TIER_ONLY_INCOMING = 1;
var TIER_FRESH_OUTGOING = 2;
var TIER_READY_OUTGOING = 3;

function PreferOutgoingHandler(peer) {
    var self = this;

    self.peer = peer;
    self.lastTier = self.getTier();
}

PreferOutgoingHandler.prototype.getTier = function getTier() {
    var self = this;

    var inconn = self.peer.getInConnection();
    var outconn = self.peer.getIdentifiedOutConnection();

    if (!inconn && !outconn) {
        return TIER_UNCONNECTED;
    } else if (!outconn || outconn.direction !== 'out') {
        return TIER_ONLY_INCOMING;
    } else if (outconn.remoteName === null) {
        return TIER_FRESH_OUTGOING;
    } else {
        return TIER_READY_OUTGOING;
    }
};

PreferOutgoingHandler.prototype.getScore = function getScore() {
    var self = this;

    // space:
    //   [0.1, 0.4)  peers with no identified outgoing connection
    //   [0.4, 1.0)  identified outgoing connections
    var random = self.peer.outPendingWeightedRandom();
    var qos = self.getTier();
    self.lastTier = qos;
    switch (qos) {
        case TIER_ONLY_INCOMING:
            if (!self.peer.channel.destroyed) {
                self.peer.connect();
            }
            /* falls through */
        case TIER_UNCONNECTED:
            /* falls through */
        case TIER_FRESH_OUTGOING:
            return 0.1 + random * 0.3;
        case TIER_READY_OUTGOING:
            return 0.4 + random * 0.6;
    }
};

module.exports = TChannelPeer;
<commit_msg>Document new peer score random variable<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var inherits = require('util').inherits;
var EventEmitter = require('./lib/event_emitter');
var net = require('net');

var TChannelConnection = require('./connection');
var errors = require('./errors');
var Request = require('./request');

var DEFAULT_REPORT_INTERVAL = 1000;

function TChannelPeer(channel, hostPort, options) {
    if (!(this instanceof TChannelPeer)) {
        return new TChannelPeer(channel, hostPort, options);
    }
    var self = this;
    EventEmitter.call(self);

    self.stateChangedEvent = self.defineEvent('stateChanged');
    self.allocConnectionEvent = self.defineEvent('allocConnection');

    assert(hostPort !== '0.0.0.0:0', 'Cannot create ephemeral peer');

    self.channel = channel;
    self.logger = self.channel.logger;
    self.timers = self.channel.timers;
    self.random = self.channel.random;
    self.options = options || {};
    self.hostPort = hostPort;
    self.connections = [];
    self.pendingIdentified = 0;
    self.heapElements = [];
    self.handler = new PreferOutgoingHandler(self);

    self.reportInterval = self.options.reportInterval || DEFAULT_REPORT_INTERVAL;
    if (self.reportInterval > 0 && self.channel.emitConnectionMetrics) {
        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }

    function onReport() {
        if (!self.hostPort) {
            return;
        }

        var count = self.countConnections('out');
        if (self.channel.emitConnectionMetrics) {
            self.channel.connectionsActiveStat.update(count, {
                'host-port': self.channel.hostPort,
                'peer-host-port': self.hostPort
            });
        }

        self.reportTimer = self.timers.setTimeout(
            onReport, self.reportInterval
        );
    }
}

inherits(TChannelPeer, EventEmitter);

TChannelPeer.prototype.invalidateScore = function invalidateScore() {
    var self = this;

    if (!self.heapElements.length) {
        return;
    }

    var score = self.handler.getScore();
    for (var i = 0; i < self.heapElements.length; i++) {
        var el = self.heapElements[i];
        el.rescore(score);
    }
};

TChannelPeer.prototype.isConnected = function isConnected(direction, identified) {
    var self = this;

    if (identified === undefined) identified = true;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (direction && conn.direction !== direction) {
            continue;
        } else if (conn.closing) {
            continue;
        } else if (conn.remoteName !== null || !identified) {
            return true;
        }
    }

    return false;
};

TChannelPeer.prototype.close = function close(callback) {
    var self = this;

    if (self.reportTimer) {
        self.timers.clearTimeout(self.reportTimer);
        self.reportTimer = null;
    }

    var counter = self.connections.length;
    if (counter) {
        self.connections.forEach(function eachConn(conn) {
            conn.close(onClose);
        });
    } else {
        callback(null);
    }
    function onClose() {
        if (--counter <= 0) {
            if (counter < 0) {
                self.logger.error('closed more peer sockets than expected', {
                    counter: counter
                });
            }
            callback(null);
        }
    }
};

TChannelPeer.prototype.getInConnection = function getInConnection() {
    var self = this;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (!conn.closing) return conn;
    }
    return null;
};

TChannelPeer.prototype.getOutConnection = function getOutConnection(preferIdentified) {
    var self = this;
    var candidate = null;
    for (var i = self.connections.length - 1; i >= 0; i--) {
        var conn = self.connections[i];
        if (conn.closing) continue;
        if (!preferIdentified) return conn; // user doesn't care, take last outgoing
        if (conn.remoteName) return conn; // user wanted an identified channel, and we found one
        if (!candidate) candidate = conn; // we'll fallback to returning this if we can't find an identified one
    }
    return candidate;
};

TChannelPeer.prototype.getIdentifiedOutConnection = function getIdentifiedOutConnection() {
    var self = this;
    return self.getOutConnection(true);
};

TChannelPeer.prototype.countConnections = function countConnections(direction) {
    var self = this;
    if (!direction) {
        return self.connections.length;
    }

    var count = 0;
    for (var i = 0; i < self.connections.length; i++) {
        var conn = self.connections[i];
        if (conn.direction === direction) {
            count++;
        }
    }

    return count;
};

TChannelPeer.prototype.connect = function connect(outOnly) {
    var self = this;
    var conn = self.getIdentifiedOutConnection();
    if (!conn || (outOnly && conn.direction !== 'out')) {
        var socket = self.makeOutSocket();
        conn = self.makeOutConnection(socket);
        self.addConnection(conn);
    }
    return conn;
};

TChannelPeer.prototype.waitForIdentified =
function waitForIdentified(callback) {
    var self = this;

    var conn = self.connect();

    if (conn.closing) {
        callback(conn.closeError);
    } else if (conn.remoteName) {
        callback(null);
    } else {
        self._waitForIdentified(conn, callback);
    }
};

TChannelPeer.prototype._waitForIdentified =
function _waitForIdentified(conn, callback) {
    var self = this;

    self.pendingIdentified++;
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);
    conn.identifiedEvent.on(onIdentified);
    self.invalidateScore();

    function onConnectionError(err) {
        finish(err);
    }

    function onConnectionClose(err) {
        finish(err);
    }

    function onIdentified() {
        finish(null);
    }

    function finish(err) {
        self.pendingIdentified = 0;
        conn.errorEvent.removeListener(onConnectionError);
        conn.closeEvent.removeListener(onConnectionClose);
        conn.identifiedEvent.removeListener(onIdentified);
        self.invalidateScore();
        callback(err);
    }
};

TChannelPeer.prototype.request = function peerRequest(options) {
    var self = this;
    options.timeout = options.timeout || Request.defaultTimeout;
    return self.connect().request(options);
};

TChannelPeer.prototype.addConnection = function addConnection(conn) {
    var self = this;
    // TODO: first approx alert for self.connections.length > 2
    // TODO: second approx support pruning
    if (conn.direction === 'out') {
        self.connections.push(conn);
    } else {
        self.connections.unshift(conn);
    }
    conn.errorEvent.on(onConnectionError);
    conn.closeEvent.on(onConnectionClose);

    self._maybeInvalidateScore();
    if (!conn.remoteName) {
        // TODO: could optimize if handler had a way of saying "would a new
        // identified connection change your Tier?"
        conn.identifiedEvent.on(onIdentified);
    }

    return conn;

    function onIdentified() {
        conn.identifiedEvent.removeListener(onIdentified);
        self._maybeInvalidateScore();
    }

    function onConnectionError(err) {
        removeConnection(err);
    }

    function onConnectionClose() {
        removeConnection(null);
    }

    function removeConnection(err) {
        conn.closeEvent.removeListener(onConnectionClose);
        conn.errorEvent.removeListener(onConnectionError);
        conn.identifiedEvent.removeListener(onIdentified);
        if (err) {
            var loggerInfo = {
                error: err,
                direction: conn.direction,
                remoteName: conn.remoteName,
                socketRemoteAddr: conn.socketRemoteAddr
            };

            var codeName = errors.classify(err);
            if (codeName === 'Timeout') {
                self.logger.warn('Got a connection error', loggerInfo);
            } else {
                self.logger.error('Got an unexpected connection error', loggerInfo);
            }
        }

        self.removeConnection(conn);
    }
};

TChannelPeer.prototype.removeConnection = function removeConnection(conn) {
    var self = this;

    var ret = null;

    var index = self.connections ? self.connections.indexOf(conn) : -1;
    if (index !== -1) {
        ret = self.connections.splice(index, 1)[0];
    }

    self._maybeInvalidateScore();

    return ret;
};

TChannelPeer.prototype.makeOutSocket = function makeOutSocket() {
    var self = this;
    var parts = self.hostPort.split(':');
    assert(parts.length === 2, 'invalid destination');
    var host = parts[0];
    var port = parts[1];
    assert(host !== '0.0.0.0', 'cannot connect to ephemeral peer');
    assert(port !== '0', 'cannot connect to dynamic port');
    var socket = net.createConnection({host: host, port: port});
    return socket;
};

TChannelPeer.prototype.makeOutConnection = function makeOutConnection(socket) {
    var self = this;
    var chan = self.channel.topChannel || self.channel;
    var conn = new TChannelConnection(chan, socket, 'out', self.hostPort);
    self.allocConnectionEvent.emit(self, conn);
    return conn;
};

TChannelPeer.prototype.outPendingWeightedRandom = function outPendingWeightedRandom() {
    // Returns a score in the range from 0 to 1, where it is preferable to use
    // a peer with a higher score over one with a lower score.
    // This range is divided among an infinite set of subranges corresponding
    // to peers with the same number of pending requests.
    // So, the range (1/2, 1) is reserved for peers with 0 pending connections.
    // The range (1/4, 1/2) is reserved for peers with 1 pending connections.
    // The range (1/8, 1/4) is reserved for peers with 2 pending connections.
    // Ad nauseam.
    // Within each equivalence class, each peer receives a uniform random
    // value.
    //
    // The previous score was a weighted random variable:
    //   random() ** (1 + pending)
    // This had the attribute that a less loaded peer was merely more likely to
    // be chosen over a more loaded peer.
    // We observed with the introduction of a heap, that a less favored peer
    // would have its score less frequently re-evaluated.
    // An emergent behavior was that scores would, over time, be squeezed
    // toward zero and the least favored peer would remain the least favored
    // for ever increasing durations.
    //
    // This remains true with this algorithm, within each equivalence class.
    var self = this;
    var pending = self.pendingIdentified + self.countOutPending();
    var max = Math.pow(0.5, pending);
    var min = max / 2;
    var diff = max - min;
    return min + diff * self.random();
};

TChannelPeer.prototype.countOutPending = function countOutPending() {
    var self = this;
    var pending = 0;
    for (var index = 0; index < self.connections.length; index++) {
        var connPending = self.connections[index].ops.getPending();

        pending += connPending.out;
    }
    return pending;
};

// TODO: on connection #getScore impacting event
// - on identified

// Called on connection change event
TChannelPeer.prototype._maybeInvalidateScore = function _maybeInvalidateScore() {
    var self = this;

    if (self.handler.getTier() !== self.handler.lastTier) {
        self.invalidateScore();
    }
};

TChannelPeer.prototype.getScore = function getScore() {
    var self = this;
    return self.handler.getScore();
};

var TIER_UNCONNECTED = 0;
var TIER_ONLY_INCOMING = 1;
var TIER_FRESH_OUTGOING = 2;
var TIER_READY_OUTGOING = 3;

function PreferOutgoingHandler(peer) {
    var self = this;

    self.peer = peer;
    self.lastTier = self.getTier();
}

PreferOutgoingHandler.prototype.getTier = function getTier() {
    var self = this;

    var inconn = self.peer.getInConnection();
    var outconn = self.peer.getIdentifiedOutConnection();

    if (!inconn && !outconn) {
        return TIER_UNCONNECTED;
    } else if (!outconn || outconn.direction !== 'out') {
        return TIER_ONLY_INCOMING;
    } else if (outconn.remoteName === null) {
        return TIER_FRESH_OUTGOING;
    } else {
        return TIER_READY_OUTGOING;
    }
};

PreferOutgoingHandler.prototype.getScore = function getScore() {
    var self = this;

    // space:
    //   [0.1, 0.4)  peers with no identified outgoing connection
    //   [0.4, 1.0)  identified outgoing connections
    var random = self.peer.outPendingWeightedRandom();
    var qos = self.getTier();
    self.lastTier = qos;
    switch (qos) {
        case TIER_ONLY_INCOMING:
            if (!self.peer.channel.destroyed) {
                self.peer.connect();
            }
            /* falls through */
        case TIER_UNCONNECTED:
            /* falls through */
        case TIER_FRESH_OUTGOING:
            return 0.1 + random * 0.3;
        case TIER_READY_OUTGOING:
            return 0.4 + random * 0.6;
    }
};

module.exports = TChannelPeer;
<|endoftext|>


<commit_before>import React from 'react';
import collapse from 'collapse-white-space';
import {isElement} from 'react-addons-test-utils';
import isPlainObject from 'is-plain-object';
import stringify from 'stringify-object';
import sortobject from 'sortobject';
import traverse from 'traverse';
import fill from 'lodash/array/fill';

export default function reactElementToJSXString(ReactElement, options = {}) {
  let getDisplayName = options.displayName || getDefaultDisplayName;

  return toJSXString({ReactElement});

  function toJSXString({ReactElement: Element = null, lvl = 0, inline = false}) {
    if (typeof Element === 'string' || typeof Element === 'number') {
      return Element;
    } else if (!isElement(Element)) {
      throw new Error('react-element-to-jsx-string: Expected a ReactElement, ' +
      'got `' + (typeof Element) + '`');
    }

    let tagName = getDisplayName(Element);

    let out = `<${tagName}`;
    let props = formatProps(Element.props);
    let attributes = [];
    let children = React.Children.toArray(Element.props.children)
    .filter(onlyMeaningfulChildren);

    if (Element.ref !== null) {
      attributes.push(getJSXAttribute('ref', Element.ref));
    }

    if (Element.key !== null &&
      // React automatically add key=".X" when there are some children
      !/^\./.test(Element.key)) {
      attributes.push(getJSXAttribute('key', Element.key));
    }

    attributes = attributes.concat(props);

    attributes.forEach(attribute => {
      if (attributes.length === 1 || inline) {
        out += ` `;
      } else {
        out += `\n${spacer(lvl + 1)}`;
      }

      if (attribute.value === '{true}') {
        out += `${attribute.name}`;
      } else {
        out += `${attribute.name}=${attribute.value}`;
      }
    });

    if (attributes.length > 1 && !inline) {
      out += `\n${spacer(lvl)}`;
    }

    if (children.length > 0) {
      out += `>`;
      lvl++;
      if (!inline) {
        out += `\n`;
        out += spacer(lvl);
      }

      if (typeof children === 'string') {
        out += children;
      } else {
        out += children
        .reduce(mergePlainStringChildren, [])
        .map(
          recurse({lvl, inline})
        ).join('\n' + spacer(lvl));
      }
      if (!inline) {
        out += `\n`;
        out += spacer(lvl - 1);
      }
      out += `</${tagName}>`;
    } else {
      if (attributes.length <= 1) {
        out += ` `;
      }

      out += '/>';
    }

    return out;
  }

  function formatProps(props) {
    return Object
      .keys(props)
      .filter(noChildren)
      .filter(prop => noFalse(props[prop]))
      .sort()
      .map(propName => {
        return getJSXAttribute(propName, props[propName]);
      });
  }

  function getJSXAttribute(name, value) {
    return {
      name,
      value: formatJSXAttribute(value)
        .replace(/'?<__reactElementToJSXString__Wrapper__>/g, '')
        .replace(/<\/__reactElementToJSXString__Wrapper__>'?/g, '')
    };
  }

  function formatJSXAttribute(propValue) {
    if (typeof propValue === 'string') {
      return `"${propValue}"`;
    }

    return `{${formatValue(propValue)}}`;
  }

  function formatValue(value) {
    if (typeof value === 'function') {
      return function noRefCheck() {};
    } else if (isElement(value)) {
      // we use this delimiter hack in cases where the react element is a property
      // of an object from a root prop
      // i.e.
      //   reactElementToJSXString(
      //   //
      // we then remove the whole wrapping
      // otherwise, the element would be surrounded by quotes:
      return '<__reactElementToJSXString__Wrapper__>' +
        toJSXString({ReactElement: value, inline: true}) +
        '</__reactElementToJSXString__Wrapper__>';
    } else if (isPlainObject(value) || Array.isArray(value)) {
      return '<__reactElementToJSXString__Wrapper__>' +
        stringifyObject(value) +
        '</__reactElementToJSXString__Wrapper__>';
    }

    return value;
  }

  function recurse({lvl, inline}) {
    return Element => {
      return toJSXString({ReactElement: Element, lvl, inline});
    };
  }

  function stringifyObject(obj) {
    if (Object.keys(obj).length > 0 || obj.length > 0) {
      obj = traverse(obj).map(function(value) {
        if (isElement(value) || this.isLeaf) {
          this.update(formatValue(value));
        }
      });

      obj = sortobject(obj);
    }

    return collapse(stringify(obj))
      .replace(/{ /g, '{')
      .replace(/ }/g, '}')
      .replace(/\[ /g, '[')
      .replace(/ \]/g, ']');
  }
}

function getDefaultDisplayName(ReactElement) {
  return ReactElement.type.name || // function name
    ReactElement.type.displayName ||
    (typeof ReactElement.type === 'function' ? // function without a name, you should provide one
      'No Display Name' :
      ReactElement.type);
}

function mergePlainStringChildren(prev, cur) {
  var lastItem = prev[prev.length - 1];

  if (typeof cur === 'number') {
    cur = '' + cur;
  }

  if (typeof lastItem === 'string' && typeof cur === 'string') {
    prev[prev.length - 1] += cur;
  } else {
    prev.push(cur);
  }

  return prev;
}

function spacer(times) {
  return fill(new Array(times), `  `).join(``);
}

function noChildren(propName) {
  return propName !== 'children';
}

function noFalse(propValue) {
  return typeof propValue !== 'boolean' || propValue;
}

function onlyMeaningfulChildren(children) {
  return children !== true && children !== false && children !== null && children !== '';
}
<commit_msg>Remove default props<commit_after>import React from 'react';
import collapse from 'collapse-white-space';
import {isElement} from 'react-addons-test-utils';
import isPlainObject from 'is-plain-object';
import stringify from 'stringify-object';
import sortobject from 'sortobject';
import traverse from 'traverse';
import fill from 'lodash/array/fill';

export default function reactElementToJSXString(ReactElement, options = {}) {
  let getDisplayName = options.displayName || getDefaultDisplayName;

  return toJSXString({ReactElement});

  function toJSXString({ReactElement: Element = null, lvl = 0, inline = false}) {
    if (typeof Element === 'string' || typeof Element === 'number') {
      return Element;
    } else if (!isElement(Element)) {
      throw new Error('react-element-to-jsx-string: Expected a ReactElement, ' +
      'got `' + (typeof Element) + '`');
    }

    let tagName = getDisplayName(Element);

    let out = `<${tagName}`;
    let props = formatProps(Element.props, getDefaultProps(Element));
    let attributes = [];
    let children = React.Children.toArray(Element.props.children)
    .filter(onlyMeaningfulChildren);

    if (Element.ref !== null) {
      attributes.push(getJSXAttribute('ref', Element.ref));
    }

    if (Element.key !== null &&
      // React automatically add key=".X" when there are some children
      !/^\./.test(Element.key)) {
      attributes.push(getJSXAttribute('key', Element.key));
    }

    attributes = attributes.concat(props);

    attributes.forEach(attribute => {
      if (attributes.length === 1 || inline) {
        out += ` `;
      } else {
        out += `\n${spacer(lvl + 1)}`;
      }

      if (attribute.value === '{true}') {
        out += `${attribute.name}`;
      } else {
        out += `${attribute.name}=${attribute.value}`;
      }
    });

    if (attributes.length > 1 && !inline) {
      out += `\n${spacer(lvl)}`;
    }

    if (children.length > 0) {
      out += `>`;
      lvl++;
      if (!inline) {
        out += `\n`;
        out += spacer(lvl);
      }

      if (typeof children === 'string') {
        out += children;
      } else {
        out += children
        .reduce(mergePlainStringChildren, [])
        .map(
          recurse({lvl, inline})
        ).join('\n' + spacer(lvl));
      }
      if (!inline) {
        out += `\n`;
        out += spacer(lvl - 1);
      }
      out += `</${tagName}>`;
    } else {
      if (attributes.length <= 1) {
        out += ` `;
      }

      out += '/>';
    }

    return out;
  }

  function formatProps(props, defaultProps) {
    return Object
      .keys(props)
      .filter(noChildren)
      .filter(key => noFalse(props[key]))
      .filter((key, value) => {
        return defaultProps[key] === value
      })
      .sort()
      .map(propName => {
        return getJSXAttribute(propName, props[propName]);
      });
  }

  function getJSXAttribute(name, value) {
    return {
      name,
      value: formatJSXAttribute(value)
        .replace(/'?<__reactElementToJSXString__Wrapper__>/g, '')
        .replace(/<\/__reactElementToJSXString__Wrapper__>'?/g, '')
    };
  }

  function formatJSXAttribute(propValue) {
    if (typeof propValue === 'string') {
      return `"${propValue}"`;
    }

    return `{${formatValue(propValue)}}`;
  }

  function formatValue(value) {
    if (typeof value === 'function') {
      return function noRefCheck() {};
    } else if (isElement(value)) {
      // we use this delimiter hack in cases where the react element is a property
      // of an object from a root prop
      // i.e.
      //   reactElementToJSXString(
      //   //
      // we then remove the whole wrapping
      // otherwise, the element would be surrounded by quotes:
      return '<__reactElementToJSXString__Wrapper__>' +
        toJSXString({ReactElement: value, inline: true}) +
        '</__reactElementToJSXString__Wrapper__>';
    } else if (isPlainObject(value) || Array.isArray(value)) {
      return '<__reactElementToJSXString__Wrapper__>' +
        stringifyObject(value) +
        '</__reactElementToJSXString__Wrapper__>';
    }

    return value;
  }

  function recurse({lvl, inline}) {
    return Element => {
      return toJSXString({ReactElement: Element, lvl, inline});
    };
  }

  function stringifyObject(obj) {
    if (Object.keys(obj).length > 0 || obj.length > 0) {
      obj = traverse(obj).map(function(value) {
        if (isElement(value) || this.isLeaf) {
          this.update(formatValue(value));
        }
      });

      obj = sortobject(obj);
    }

    return collapse(stringify(obj))
      .replace(/{ /g, '{')
      .replace(/ }/g, '}')
      .replace(/\[ /g, '[')
      .replace(/ \]/g, ']');
  }
}

function getDefaultDisplayName(ReactElement) {
  return ReactElement.type.name || // function name
    ReactElement.type.displayName ||
    (typeof ReactElement.type === 'function' ? // function without a name, you should provide one
      'No Display Name' :
      ReactElement.type);
}

function getDefaultProps(ReactElement) {
  return ReactElement.type.defaultProps
}

function mergePlainStringChildren(prev, cur) {
  var lastItem = prev[prev.length - 1];

  if (typeof cur === 'number') {
    cur = '' + cur;
  }

  if (typeof lastItem === 'string' && typeof cur === 'string') {
    prev[prev.length - 1] += cur;
  } else {
    prev.push(cur);
  }

  return prev;
}

function spacer(times) {
  return fill(new Array(times), `  `).join(``);
}

function noChildren(propName) {
  return propName !== 'children';
}

function noFalse(propValue) {
  return typeof propValue !== 'boolean' || propValue;
}

function onlyMeaningfulChildren(children) {
  return children !== true && children !== false && children !== null && children !== '';
}
<|endoftext|>


<commit_before>'use strict';

const BABYLON = require('babylonjs');

const engine = new BABYLON.Engine(document.getElementById('render-canvas'), true);
const scene = new BABYLON.Scene(engine);

scene.enablePhysics(
  new BABYLON.Vector3(0, -9.81, 0),
  new BABYLON.OimoJSPlugin());
require('../models/simple.blend').Append(BABYLON.SceneLoader, scene, loadedScene => {
  /*onsuccess*/
  new BABYLON.PhysicsImpostor(loadedScene.meshes[0], BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1, }, scene);
}, x => {/*onprogress*/}, ex => {/*onerror*/});

engine.runRenderLoop(() => scene.render());
<commit_msg>Add window resize handler.<commit_after>'use strict';

const BABYLON = require('babylonjs');

const engine = new BABYLON.Engine(document.getElementById('render-canvas'), true);
window.addEventListener('resize', () => engine.resize());
const scene = new BABYLON.Scene(engine);

scene.enablePhysics(
  new BABYLON.Vector3(0, -9.81, 0),
  new BABYLON.OimoJSPlugin());
require('../models/simple.blend').Append(BABYLON.SceneLoader, scene, loadedScene => {
  /*onsuccess*/
  new BABYLON.PhysicsImpostor(loadedScene.meshes[0], BABYLON.PhysicsImpostor.BoxImpostor, { mass: 1, }, scene);
}, x => {/*onprogress*/}, ex => {/*onerror*/});

engine.runRenderLoop(() => scene.render());
<|endoftext|>


<commit_before>
(function () {

  const NS_SVG = 'http://www.w3.org/2000/svg';

  function zdom(target) {
    if(typeof target === 'string') {
      return zdom.select(target);
    } else {
      return target;
    }
  }

  /**
   * Query
   */
  zdom.select = function () {
    var selector;
    if(arguments.length === 1) {
      selector = arguments[0];
      console.assert(typeof selector === 'string');
      return document.querySelector(selector);
    } else if(arguments.length >= 2) {
      var el = arguments[0];
      selector = arguments[1];
      console.assert(typeof el === 'object');
      console.assert(typeof selector === 'string');
      return el.querySelector(selector);
    }
  };

  zdom.selectAll = function () {
    var selector;
    if(arguments.length === 1) {
      selector = arguments[0];
      console.assert(typeof selector === 'string');
      return document.querySelectorAll(selector);
    } else if(arguments.length >= 2) {
      var el = arguments[0];
      selector = arguments[1];
      console.assert(typeof el === 'object');
      console.assert(typeof selector === 'string');
      return el.querySelectorAll(selector);
    }
  };

  /**
   * Creation of standard SVG elements
   */
  zdom.createSVGElement = function(tag) {
    return document.createElementNS(NS_SVG, tag);
  };

  zdom.createLine = function (x1, y1, x2, y2, style) {
    var el = document.createElementNS(NS_SVG, 'line');
    zdom.set(el, 'x1', x1);
    zdom.set(el, 'y1', y1);
    zdom.set(el, 'x2', x2);
    zdom.set(el, 'y2', y2);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createCircle = function (cx, cy, r, style) {
    var el = document.createElementNS(NS_SVG, 'circle');
    console.assert(!isNaN(cx));
    console.assert(!isNaN(cy));
    console.assert(!isNaN(r));
    zdom.set(el, 'cx', cx);
    zdom.set(el, 'cy', cy);
    zdom.set(el, 'r', r);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createEllipse = function (cx, cy, rx, ry, style) {
    var el = document.createElementNS(NS_SVG, 'ellipse');
    console.assert(!isNaN(cx));
    console.assert(!isNaN(cy));
    console.assert(!isNaN(rx));
    console.assert(!isNaN(ry));
    zdom.set(el, 'cx', cx);
    zdom.set(el, 'cy', cy);
    zdom.set(el, 'rx', rx);
    zdom.set(el, 'ry', ry);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createRect = function (x, y, width, height, rx, ry, style) {
    var el = document.createElementNS(NS_SVG, 'rect');
    zdom.set(el,'x',x);
    zdom.set(el,'y',y);
    zdom.set(el,'width',width);
    zdom.set(el,'height',height);
    zdom.set(el,'rx',rx);
    zdom.set(el,'ry',ry);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createPolygon = function (points, style) {
    var el = document.createElementNS(NS_SVG, 'polygon');
    zdom.set(el, 'points', points.map(function (x,y) { return x+','+y; }).join(' '));
    zdom.style(el, style||'');
    return el;
  };

  zdom.createPolyline = function (points, style) {
    var el = document.createElementNS(NS_SVG, 'polyline');
    zdom.set(el, 'points', points.map(function (x,y) { return x+','+y; }).join(' '));
    zdom.style(el, style||'');
    return el;
  };

  zdom.createPath = function (d, style) {
    var el = document.createElementNS(NS_SVG, 'path');
    zdom.set(el, 'd', d);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createG = function () {
    return document.createElementNS(NS_SVG, 'g');
  };

  /**
   * Creation of standard HTML elements
   */
  zdom.createDiv = function () {
    return document.createElement('div');
  };

  zdom.createSpan = function () {
    return document.createElement('span');
  };

  zdom.createInput = function (options) {
    var el = document.createElement('input');
    for(var key in options) {
      zdom.set(el, key, options[key]);
    }
    return el;
  };

  zdom.createCanvas = function () {
    return document.createElement('canvas');
  };

  zdom.createImage = function () {
    return document.createElement('img');
  };

  zdom.create = zdom.parse = function(markup) {
    var doc = new window.DOMParser().parseFromString(markup, 'text/html');
    return doc.body.children[0];
  };

  zdom.createElementNS = function (namespace, tag) {
    return document.createElementNS(namespace,tag);
  };

  /**
   * Element manipulation
   */
  zdom.set = function (el, key, value) {
    el.setAttribute(key, value);
  };

  zdom.get = function (el, key) {
    return el.getAttribute(key);
  };

  zdom.has = function (el, key) {
    return el.hasAttribute(key);
  };

  zdom.id = function (el, idval) {
    if(idval) {
      // Setter
      zdom.set(el, 'id', idval);
    } else {
      // Getter
      return zdom.get(el, 'id');
    }
  };

  zdom.style = function (el, styleStr) {
    console.assert(typeof styleStr === 'string');
    zdom.set(el, 'style', styleStr);
  };

  zdom.parent = function (el) {
    return el.parentElement;
  };

  zdom.add = function (parent, child) {
    parent.appendChild(child);
  };

  zdom.prepend = function(parent, child) {
    parent.insertBefore(child, parent.firstChild);
  };

  zdom.remove = function (parent, child) {
    parent.removeChild(child);
  };

  zdom.empty = function (el) {
    for(var i=0; i<el.children.length; i++) {
      el.children[i].remove();
    }
  };

  zdom.addClass = function (el, cname) {
    if(el.className.indexOf(cname) < 0) {
      el.className = el.className + ' ' + cname;
    }
  };

  zdom.removeClass = function (el, cname) {
    var classes = el.className.split(' ')
      .filter(function(seg) {return seg.length > 0});
    el.className = classes
      .filter(function (cls) { return cls !== cname; })
      .join(' ');
  };

  zdom.hasClass = function (el, cname) {
    return el.className.indexOf(cname) >= 0;
  };

  zdom.show = function (el, customDisplayVal) {
    el.style.display = customDisplayVal || 'inherit';
  };

  zdom.hide = function (el) {
    el.style.display = 'none';
  };

  zdom.setStyle = function (el, styleKey, styleValue) {
    el.style[styleKey] = styleValue;
  };

  zdom.getStyle = function (el, styleKey) {
    return el.style[styleKey];
  };

  zdom.getStylePx = function (el, styleKey) {
    var value = zdom.getStyle(el, styleKey);
    if(/^\d+$/.test(value)) {
      return parseInt(value);
    } else if(/^\d+px$/.test(value)) {
      return parseInt(/^(\d+)px$/.exec(value)[1]);
    } else {
      console.assert(false);
    }
  };

  zdom.text = function(el, text) {
    el.textContent = text;
  };

  if(typeof module !== 'undefined') {
    module.exports = zdom;
  }

})();
<commit_msg>Add createSVG<commit_after>
(function () {

  const NS_SVG = 'http://www.w3.org/2000/svg';

  function zdom(target) {
    if(typeof target === 'string') {
      return zdom.select(target);
    } else {
      return target;
    }
  }

  /**
   * Query
   */
  zdom.select = function () {
    var selector;
    if(arguments.length === 1) {
      selector = arguments[0];
      console.assert(typeof selector === 'string');
      return document.querySelector(selector);
    } else if(arguments.length >= 2) {
      var el = arguments[0];
      selector = arguments[1];
      console.assert(typeof el === 'object');
      console.assert(typeof selector === 'string');
      return el.querySelector(selector);
    }
  };

  zdom.selectAll = function () {
    var selector;
    if(arguments.length === 1) {
      selector = arguments[0];
      console.assert(typeof selector === 'string');
      return document.querySelectorAll(selector);
    } else if(arguments.length >= 2) {
      var el = arguments[0];
      selector = arguments[1];
      console.assert(typeof el === 'object');
      console.assert(typeof selector === 'string');
      return el.querySelectorAll(selector);
    }
  };

  /**
   * Creation of standard SVG elements
   */

  zdom.createSVG = function (width, height) {
    var el = zdom.createSVGElement('svg');
    zdom.set(el, 'width', ''+width);
    zdom.set(el, 'height', ''+height);
    return el;
  };

  zdom.createSVGElement = function (tag) {
    return document.createElementNS(NS_SVG, tag);
  };

  zdom.createLine = function (x1, y1, x2, y2, style) {
    var el = document.createElementNS(NS_SVG, 'line');
    zdom.set(el, 'x1', x1);
    zdom.set(el, 'y1', y1);
    zdom.set(el, 'x2', x2);
    zdom.set(el, 'y2', y2);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createCircle = function (cx, cy, r, style) {
    var el = document.createElementNS(NS_SVG, 'circle');
    console.assert(!isNaN(cx));
    console.assert(!isNaN(cy));
    console.assert(!isNaN(r));
    zdom.set(el, 'cx', cx);
    zdom.set(el, 'cy', cy);
    zdom.set(el, 'r', r);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createEllipse = function (cx, cy, rx, ry, style) {
    var el = document.createElementNS(NS_SVG, 'ellipse');
    console.assert(!isNaN(cx));
    console.assert(!isNaN(cy));
    console.assert(!isNaN(rx));
    console.assert(!isNaN(ry));
    zdom.set(el, 'cx', cx);
    zdom.set(el, 'cy', cy);
    zdom.set(el, 'rx', rx);
    zdom.set(el, 'ry', ry);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createRect = function (x, y, width, height, rx, ry, style) {
    var el = document.createElementNS(NS_SVG, 'rect');
    zdom.set(el,'x',x);
    zdom.set(el,'y',y);
    zdom.set(el,'width',width);
    zdom.set(el,'height',height);
    zdom.set(el,'rx',rx);
    zdom.set(el,'ry',ry);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createPolygon = function (points, style) {
    var el = document.createElementNS(NS_SVG, 'polygon');
    zdom.set(el, 'points', points.map(function (x,y) { return x+','+y; }).join(' '));
    zdom.style(el, style||'');
    return el;
  };

  zdom.createPolyline = function (points, style) {
    var el = document.createElementNS(NS_SVG, 'polyline');
    zdom.set(el, 'points', points.map(function (x,y) { return x+','+y; }).join(' '));
    zdom.style(el, style||'');
    return el;
  };

  zdom.createPath = function (d, style) {
    var el = document.createElementNS(NS_SVG, 'path');
    zdom.set(el, 'd', d);
    zdom.style(el, style||'');
    return el;
  };

  zdom.createG = function () {
    return document.createElementNS(NS_SVG, 'g');
  };

  /**
   * Creation of standard HTML elements
   */
  zdom.createDiv = function () {
    return document.createElement('div');
  };

  zdom.createSpan = function () {
    return document.createElement('span');
  };

  zdom.createInput = function (options) {
    var el = document.createElement('input');
    for(var key in options) {
      zdom.set(el, key, options[key]);
    }
    return el;
  };

  zdom.createCanvas = function () {
    return document.createElement('canvas');
  };

  zdom.createImage = function () {
    return document.createElement('img');
  };

  zdom.create = zdom.parse = function(markup) {
    var doc = new window.DOMParser().parseFromString(markup, 'text/html');
    return doc.body.children[0];
  };

  zdom.createElementNS = function (namespace, tag) {
    return document.createElementNS(namespace,tag);
  };

  /**
   * Element manipulation
   */
  zdom.set = function (el, key, value) {
    el.setAttribute(key, value);
  };

  zdom.get = function (el, key) {
    return el.getAttribute(key);
  };

  zdom.has = function (el, key) {
    return el.hasAttribute(key);
  };

  zdom.id = function (el, idval) {
    if(idval) {
      // Setter
      zdom.set(el, 'id', idval);
    } else {
      // Getter
      return zdom.get(el, 'id');
    }
  };

  zdom.style = function (el, styleStr) {
    console.assert(typeof styleStr === 'string');
    zdom.set(el, 'style', styleStr);
  };

  zdom.parent = function (el) {
    return el.parentElement;
  };

  zdom.add = function (parent, child) {
    parent.appendChild(child);
  };

  zdom.prepend = function(parent, child) {
    parent.insertBefore(child, parent.firstChild);
  };

  zdom.remove = function (parent, child) {
    parent.removeChild(child);
  };

  zdom.empty = function (el) {
    for(var i=0; i<el.children.length; i++) {
      el.children[i].remove();
    }
  };

  zdom.addClass = function (el, cname) {
    if(el.className.indexOf(cname) < 0) {
      el.className = el.className + ' ' + cname;
    }
  };

  zdom.removeClass = function (el, cname) {
    var classes = el.className.split(' ')
      .filter(function(seg) {return seg.length > 0});
    el.className = classes
      .filter(function (cls) { return cls !== cname; })
      .join(' ');
  };

  zdom.hasClass = function (el, cname) {
    return el.className.indexOf(cname) >= 0;
  };

  zdom.show = function (el, customDisplayVal) {
    el.style.display = customDisplayVal || 'inherit';
  };

  zdom.hide = function (el) {
    el.style.display = 'none';
  };

  zdom.setStyle = function (el, styleKey, styleValue) {
    el.style[styleKey] = styleValue;
  };

  zdom.getStyle = function (el, styleKey) {
    return el.style[styleKey];
  };

  zdom.getStylePx = function (el, styleKey) {
    var value = zdom.getStyle(el, styleKey);
    if(/^\d+$/.test(value)) {
      return parseInt(value);
    } else if(/^\d+px$/.test(value)) {
      return parseInt(/^(\d+)px$/.exec(value)[1]);
    } else {
      console.assert(false);
    }
  };

  zdom.text = function(el, text) {
    el.textContent = text;
  };

  if(typeof module !== 'undefined') {
    module.exports = zdom;
  }

})();
<|endoftext|>