diff options
author | Feross Aboukhadijeh <feross@feross.org> | 2014-02-03 05:25:51 +0400 |
---|---|---|
committer | Feross Aboukhadijeh <feross@feross.org> | 2014-02-03 05:25:51 +0400 |
commit | 2104b76c2cc0f2e8ffb9cab1eb3fd451747e7503 (patch) | |
tree | e7a754456162b4d5cdfeacf6c5b8192c3330a6db | |
parent | e257965a02c7bdb6e94d4294d396859f9057113c (diff) |
refactor
-rw-r--r-- | index.js | 207 | ||||
-rw-r--r-- | lib/Torrent.js | 179 | ||||
-rw-r--r-- | lib/TorrentManager.js | 63 | ||||
-rw-r--r-- | package.json | 5 |
4 files changed, 251 insertions, 203 deletions
@@ -22,212 +22,19 @@ // _error.apply(null, args) // } -var $ = require('jquery') -var async = require('async') -var bncode = require('bncode') -var DHT = require('bittorrent-dht') -var hat = require('hat') -var magnet = require('magnet-uri') -var portfinder = require('chrome-portfinder') -var Swarm = require('bittorrent-swarm') - -var MAX_PEERS = 200 -var WIRE_TIMEOUT = 10000 -var METADATA_BLOCK_SIZE = 16 * 1024 +var TorrentManager = require('./lib/TorrentManager') var isChromeApp = !!(typeof window !== 'undefined' && window.chrome && window.chrome.app && window.chrome.app.runtime) if (isChromeApp) console.log('This is a chrome app.') -var peerId = '-WW0001-' + hat(48) - -function magnetToInfoHash (uri) { - try { - return magnet(uri).xt.split('urn:btih:')[1] - } catch (e) { - return null - } -} - -function magnetToDisplayName (uri) { - try { - return magnet(uri).dn - } catch (e) { - return null - } -} - -var magnetUri = 'magnet:?xt=urn:btih:d2474e86c95b19b8bcfdb92bc12c9d44667cfa36&dn=Leaves+of+Grass+by+Walt+Whitman.epub' -var infoHash = magnetToInfoHash(magnetUri) -var displayName = magnetToDisplayName(magnetUri) - -$('.infoHash span').text(infoHash) -$('.displayName span').text(displayName) - -var dht -var swarm - -async.auto({ - dhtPort: function (cb) { - portfinder.getPort(cb) - }, - swarmPort: function (cb) { - portfinder.getPort(cb) - }, - dht: ['dhtPort', function (cb, r) { - dht = new DHT(infoHash) - - dht.on('node', function (node, infoHash) { - var num = Number($('.dhtNodes span').text()) - $('.dhtNodes span').text(num + 1) - }) - - dht.on('peer', function (peer, infoHash) { - var num = Number($('.dhtPeers span').text()) - $('.dhtPeers span').text(num + 1) - // console.log('peer: ' + peer) - - swarm.add(peer) - }) - - dht.findPeers(MAX_PEERS) // TODO: should the DHT be concerned with max peers? - - // TODO: DHT should listen - // dht.listen(r.dhtPort) - }], - swarm: ['swarmPort', function (cb, r) { - swarm = new Swarm(infoHash, peerId, { dht: true }) - - // TODO: add swarm listen and add ourselves to the DHT - - swarm.on('wire', function (wire) { - $('.connectedPeers span').text(swarm.wires.length) - - // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire - wire.setKeepAlive(true) - - // If peer supports DHT, send PORT message to report what port our DHT node - // is listening on - if (wire.peerExtensions.dht) { - // TODO: DHT doesn't support listening yet - // wire.port(dht.port) - } - - // When peer sends PORT, add them to the routing table - wire.on('port', function (port) { - console.log('PORT', port) - // TODO: DHT doesn't have a routing table - // dht.add(wire.remoteAddress, port) - }) - - // Time to wait before considering requests timed out - wire.setTimeout(WIRE_TIMEOUT) - - // Support extended messages: - // - ut_metadata (metadata fetching, trackerless torrents) - if (wire.peerExtensions) { - console.log('Wire ' + wire.remoteAddress + ' supports extended messages', wire.peerExtensions) - wire.extended(0, { - m: { - ut_metadata: 1 - } - // TODO - this should be set once we have metadata - // metadata_size: xx - }) - } - - wire.on('extended', function (ext, buf) { - var dict - console.log('Received extended message ' + ext + ' from ' + wire.remoteAddress) - - if (ext === 0) { // handshake - - try { - console.log('decoding ' + buf.toString()) - dict = bncode.decode(buf.toString()) - console.log('got extended handshake: ' + JSON.stringify(dict)) - } catch (e) { - console.error('Error decoding extended message: ' + e.message) - } - - if (dict.m.ut_metadata && dict.metadata_size) { - var metadataSize = dict.metadata_size - var numPieces = Math.ceil(metadataSize / METADATA_BLOCK_SIZE) - console.log('metadata size: ' + metadataSize) - console.log(numPieces + ' pieces') - - wire.metadata = new Buffer(metadataSize) - - // request all pieces - for (var piece = 0; piece < numPieces; piece++) { - wire.extended(dict.m.ut_metadata, { - msg_type: 0, - piece: piece - }) - } - } - - } else if (ext === 1) { // ut_metadata - - // 0 - request - // 1 - data - // 2 - reject - - var str - var dataIndex - var data - try { - str = buf.toString() - console.log('decoding ' + str) - dataIndex = str.indexOf('ee') + 2 - var msg = str.substring(0, dataIndex) - console.log('using ' + msg) - dict = bncode.decode(msg) - data = buf.slice(dataIndex) - console.log('got metadata: ' + JSON.stringify(dict)) - console.log('got metadata data: ' + data.length + ' bytes') - } catch (e) { - console.error('Error decoding extended message: ' + e.message) - } - - // {'msg_type': 1, 'piece': 0, 'total_size': 3425} - if (dict.msg_type === 1) { // data - console.log('total_size: ' + dict.total_size) - data.copy(wire.metadata, dict.piece * METADATA_BLOCK_SIZE) - - var errorHandler = function (err) { - console.error('error' + err.toString()) - } - - chrome.fileSystem.chooseEntry({ - type: 'saveFile', - suggestedName: displayName - }, function (writableFileEntry) { - writableFileEntry.createWriter(function (writer) { - writer.onerror = errorHandler - writer.onwriteend = function (e) { - console.log('write complete') - } - writer.write(new Blob([wire.metadata]), { type: 'text/plain' }) - }, errorHandler) - }) - } - } - }) - - }) - swarm.on('error', function (err) { - console.error(err.message) - }) +var manager = new TorrentManager() - // swarm.listen(r.dhtPort, function () { - // console.log('Swarm listening on port ' + r.dhtPort) - // }) - }] -}, function (err) { - if (err) console.error(err.message) - else console.log('Setup complete') -}) +manager.add('magnet:?xt=urn:btih:d2474e86c95b19b8bcfdb92bc12c9d44667cfa36&dn=Leaves+of+Grass+by+Walt+Whitman.epub') +manager.on('error', function (err) { + console.error(err) + // TODO: Show error in UI somehow +})
\ No newline at end of file diff --git a/lib/Torrent.js b/lib/Torrent.js new file mode 100644 index 0000000..cb1a5f9 --- /dev/null +++ b/lib/Torrent.js @@ -0,0 +1,179 @@ +module.exports = Torrent + +var bncode = require('bncode') +var EventEmitter = require('events').EventEmitter +var inherits = require('inherits') +var magnet = require('magnet-uri') +var Swarm = require('bittorrent-swarm') + +var METADATA_BLOCK_SIZE = 16 * 1024 +var WIRE_TIMEOUT = 10000 + +inherits(Torrent, EventEmitter) + +function Torrent (uri, opts) { + if (!(this instanceof Torrent)) return new Torrent(uri, opts) + EventEmitter.call(this) + + var info = this._parseMagnetUri(uri) + if (!info.infoHash) + throw new Error('invalid torrent uri') + + this.infoHash = info.infoHash + this.displayName = info.displayName + + this.swarm = new Swarm(this.infoHash, opts.peerId, { dht: true }) + + // TODO: swarm pooling should be smart about picking port + // this.swarm.listen(function (port) { + // console.log('Swarm listening on port ' + port) + // this.emit('listening', port) + // }.bind(this)) + + this.swarm.on('wire', function (wire) { + $('.connectedPeers span').text(this.swarm.wires.length) + + // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire + wire.setKeepAlive(true) + + // If peer supports DHT, send PORT message to report what port our DHT node + // is listening on + if (wire.peerExtensions.dht) { + console.log('peer supports DHT') + // TODO: DHT doesn't support listening yet + // wire.port(dht.port) + } + + // When peer sends PORT, add them to the routing table + wire.on('port', function (port) { + console.log('received PORT: ', port) + // TODO: DHT doesn't have a routing table yet + // dht.add(wire.remoteAddress, port) + }) + + // Timeout for wire requests to this peer + wire.setTimeout(WIRE_TIMEOUT) + + // Support extended messages: + // - ut_metadata (metadata fetching, trackerless torrents) + if (wire.peerExtensions) { + console.log('Wire ' + wire.remoteAddress + ' supports extended messages', wire.peerExtensions) + wire.extended(0, { + m: { + ut_metadata: 1 + } + // TODO - this should be set once we have metadata + // metadata_size: xx + }) + } + + wire.on('extended', function (ext, buf) { + var dict + console.log('Received extended message ' + ext + ' from ' + wire.remoteAddress) + + if (ext === 0) { // handshake + + try { + console.log('decoding ' + buf.toString()) + dict = bncode.decode(buf.toString()) + console.log('got extended handshake: ' + JSON.stringify(dict)) + } catch (e) { + console.error('Error decoding extended message: ' + e.message) + } + + if (dict.m.ut_metadata && dict.metadata_size) { + var metadataSize = dict.metadata_size + var numPieces = Math.ceil(metadataSize / METADATA_BLOCK_SIZE) + console.log('metadata size: ' + metadataSize) + console.log(numPieces + ' pieces') + + wire.metadata = new Buffer(metadataSize) + + // request all pieces + for (var piece = 0; piece < numPieces; piece++) { + wire.extended(dict.m.ut_metadata, { + msg_type: 0, + piece: piece + }) + } + } + + } else if (ext === 1) { // ut_metadata + + // 0 - request + // 1 - data + // 2 - reject + + var str + var dataIndex + var data + try { + str = buf.toString() + console.log('decoding ' + str) + dataIndex = str.indexOf('ee') + 2 + var msg = str.substring(0, dataIndex) + console.log('using ' + msg) + dict = bncode.decode(msg) + data = buf.slice(dataIndex) + console.log('got metadata: ' + JSON.stringify(dict)) + console.log('got metadata data: ' + data.length + ' bytes') + } catch (e) { + console.error('Error decoding extended message: ' + e.message) + } + + // {'msg_type': 1, 'piece': 0, 'total_size': 3425} + if (dict.msg_type === 1) { // data + console.log('total_size: ' + dict.total_size) + data.copy(wire.metadata, dict.piece * METADATA_BLOCK_SIZE) + + var errorHandler = function (err) { + console.error('error' + err.toString()) + } + + chrome.fileSystem.chooseEntry({ + type: 'saveFile', + suggestedName: this.displayName + '.torrent' + }, function (writableFileEntry) { + writableFileEntry.createWriter(function (writer) { + writer.onerror = errorHandler + writer.onwriteend = function (e) { + console.log('write complete') + } + writer.write(new Blob([wire.metadata]), { type: 'text/plain' }) + }, errorHandler) + }) + } + } + }.bind(this)) + + }.bind(this)) + + this.swarm.on('error', function (err) { + console.error(err.message) + }) +} + +/** + * Add a peer to the swarm + * @param {string} addr + */ +Torrent.prototype.addPeer = function (addr) { + this.swarm.add(addr) +} + +// +// HELPER METHODS +// + +/** + * Given a magnet URI, return infoHash and displayName + * @param {string} uri + * @return {Object} + */ +Torrent.prototype._parseMagnetUri = function (uri) { + var parsed = magnet(uri) + return { + displayName: parsed.dn, + infoHash: parsed.xt && parsed.xt.split('urn:btih:')[1] + } +} diff --git a/lib/TorrentManager.js b/lib/TorrentManager.js new file mode 100644 index 0000000..bc9c4eb --- /dev/null +++ b/lib/TorrentManager.js @@ -0,0 +1,63 @@ +module.exports = TorrentManager + +var $ = require('jquery') +var DHT = require('bittorrent-dht') +var EventEmitter = require('events').EventEmitter +var hat = require('hat') +var inherits = require('inherits') +var Torrent = require('./Torrent') + +var MAX_PEERS = 200 + +inherits(TorrentManager, EventEmitter) + +function TorrentManager () { + if (!(this instanceof TorrentManager)) return new TorrentManager() + EventEmitter.call(this) + + // TODO: should these ids be consistent between restarts? + this.peerId = new Buffer('-WW0001-' + hat(48), 'utf8') + this.nodeId = new Buffer(hat(160), 'hex') + + this.torrents = {} + + this.dht = new DHT({ nodeId: this.nodeId }) + + this.dht.on('node', this.updateUI.bind(this)) + this.dht.on('peer', this.updateUI.bind(this)) + + this.dht.on('peer', function (addr, infoHash) { + var torrent = this.torrents[infoHash] + torrent.addPeer(addr) + }.bind(this)) + + // this.dht.listen() + +} + +TorrentManager.prototype.add = function (uri) { + var torrent = new Torrent(uri, { peerId: this.peerId }) + this.torrents[torrent.infoHash] = torrent + + torrent.on('listening', function (port) { + // TODO: Add the torrent to the public DHT so peers know to find up + }) + + // TODO: DHT should support multiple infoHashes + this.dht.setInfoHash(torrent.infoHash) + this.dht.findPeers(MAX_PEERS) // TODO: should the DHT be concerned with max peers? + + this.updateUI() +} + +// TODO: show multiple torrents +TorrentManager.prototype.updateUI = function () { + // console.log('Peer ID: ' + this.peerId.toString('utf8')) + // console.log('Node ID: ' + this.nodeId.toString('hex')) + $('.infoHash span').text(this.torrents['d2474e86c95b19b8bcfdb92bc12c9d44667cfa36'].infoHash) + $('.displayName span').text(this.torrents['d2474e86c95b19b8bcfdb92bc12c9d44667cfa36'].displayName) + + $('.dhtNodes span').text(Object.keys(this.dht.nodes).length) + $('.dhtPeers span').text(Object.keys(this.dht.peers).length) +} + diff --git a/package.json b/package.json index 0fe2472..88770e8 100644 --- a/package.json +++ b/package.json @@ -22,17 +22,16 @@ "url": "https://github.com/feross/webtorrent/issues" }, "dependencies": { - "async": "~0.2.10", "bittorrent-dht": "0.x", "bittorrent-protocol": "0.x", "bittorrent-swarm": "0.x", "bncode": "~0.5.2", "browserify": "3.x", "browserify-shim": "~3.2.2", - "chrome-portfinder": "~0.2.2", "hat": "0.0.3", + "inherits": "~2.0.1", "jquery": "~2.1.0", - "magnet-uri": "0.x", + "magnet-uri": "1.x", "portfinder": "~0.2.1", "read-torrent": "~0.2.0", "speedometer": "~0.1.2" |