From 3a1abc5d60e089cebc35cec054898d86bc281072 Mon Sep 17 00:00:00 2001 From: Eric Wooley Date: Thu, 24 Sep 2015 19:46:55 -0700 Subject: [PATCH] Documented, renamed, and refactored long methods --- index.js | 2 +- lib/torrent.js | 250 +++++++++++++++++++++++++++++++++++-------------- 2 files changed, 180 insertions(+), 72 deletions(-) diff --git a/index.js b/index.js index 4f4577db..b9d1a00d 100644 --- a/index.js +++ b/index.js @@ -230,7 +230,7 @@ WebTorrent.prototype.seed = function (input, opts, onseed) { _onseed() return } else { - torrent._onTorrentId(torrentBuf) + torrent._createParsedTorrent(torrentBuf) } }) }) diff --git a/lib/torrent.js b/lib/torrent.js index 6b58f776..0ad37637 100644 --- a/lib/torrent.js +++ b/lib/torrent.js @@ -47,8 +47,24 @@ var TMP = path.join(pathExists.sync('/tmp') ? '/tmp' : os.tmpDir(), 'webtorrent' inherits(Torrent, EventEmitter) /** - * @param {string|Buffer|Object} torrentId + * @param {string|Buffer|Object} torrentId path to the file or folder on filesystem (string) + * W3C File object (from an or drag and drop) + * W3C FileList object (basically an array of File objects) + * Node Buffer object (works in the browser) + * * @param {Object} opts + * @param {WebTorrent} opts.client The torrent client + * @param {Array} opts.announce Torrent trackers to use (added to list in .torrent or magnet uri) + * @param {String} opts.path Folder to download files to (default=`/tmp/webtorrent/`) + * @param {Function} opts.store Custom chunk store (must follow + * [abstract-chunk-store](https://www.npmjs.com/package/abstract-chunk-store) API) + * @param {Array} opts.urlList Allow specifying web seeds via `opts` parameter + * @param {abstract-chunk-store} opts.store https://github.com/mafintosh/abstract-chunk-store + * abstract-chunk-store compliant buffer storage Object + * @param {String} opts.strategy Default strategy for downloading torrent chucnks, valid options are: + * 'sequential' Download from left to right + * 'rarest' Download the least seeded first? (varification needed) + * @param {Boolean|Number} opts.uploads Number of upload slots? (verification needed) */ function Torrent (torrentId, opts) { var self = this @@ -88,10 +104,12 @@ function Torrent (torrentId, opts) { // for cleanup self._servers = [] - if (torrentId) self._onTorrentId(torrentId) + if (torrentId) self._createParsedTorrent(torrentId) } -// Time remaining (in milliseconds) +/** + * Returns Time remaining (in milliseconds) + */ Object.defineProperty(Torrent.prototype, 'timeRemaining', { get: function () { if (this.swarm.downloadSpeed() === 0) return Infinity @@ -99,7 +117,9 @@ Object.defineProperty(Torrent.prototype, 'timeRemaining', { } }) -// Bytes completed (excluding invalid data) +/** + * Bytes completed (excluding invalid data) + */ Object.defineProperty(Torrent.prototype, 'downloaded', { get: function () { var downloaded = 0 @@ -115,12 +135,16 @@ Object.defineProperty(Torrent.prototype, 'downloaded', { } }) -// Bytes received from peers (including invalid data) +/** + * Bytes received from peers (including invalid data) + */ Object.defineProperty(Torrent.prototype, 'received', { get: function () { return this.swarm ? this.swarm.downloaded : 0 } }) -// Bytes uploaded +/** + * Returnes number of bytes uploaded. + */ Object.defineProperty(Torrent.prototype, 'uploaded', { get: function () { return this.swarm ? this.swarm.uploaded : 0 } }) @@ -139,21 +163,30 @@ Object.defineProperty(Torrent.prototype, 'uploaded', { // } // }) -// Percentage complete, represented as a number between 0 and 1 +/** + * Returns percentage complete, represented as a number between 0 and 1 + */ Object.defineProperty(Torrent.prototype, 'progress', { get: function () { return this.length ? this.downloaded / this.length : 0 } }) -// Seed ratio +/** + * Returns the seed ratio (Uploaded Bytes / torrent size) + */ Object.defineProperty(Torrent.prototype, 'ratio', { get: function () { return this.uploaded / (this.downloaded || 1) } }) -// Number of peers +/** + * Number of peers + */ Object.defineProperty(Torrent.prototype, 'numPeers', { get: function () { return this.swarm ? this.swarm.numPeers : 0 } }) +/** + * Create a Blob url for the browser to link too. (Browser only) + */ Object.defineProperty(Torrent.prototype, 'torrentFileURL', { get: function () { if (typeof window === 'undefined') throw new Error('browser-only property') @@ -164,25 +197,39 @@ Object.defineProperty(Torrent.prototype, 'torrentFileURL', { } }) +/** + * @return The current download speed. + */ Torrent.prototype.downloadSpeed = function () { return this.swarm ? this.swarm.downloadSpeed() : 0 } +/** + * @return The current upload speed. + */ Torrent.prototype.uploadSpeed = function () { return this.swarm ? this.swarm.uploadSpeed() : 0 } -Torrent.prototype._onTorrentId = function (torrentId) { +/** + * Parse a torrentId into a parsed torrent. + * @param {String|Buffer|object} torrentId See top documentation + */ +Torrent.prototype._createParsedTorrent = function (torrentId) { var self = this if (self.destroyed) return parseTorrent.remote(torrentId, function (err, parsedTorrent) { if (self.destroyed) return if (err) return self._onError(err) - self._onParsedTorrent(parsedTorrent) + self._initializeParsedTorrent(parsedTorrent) }) } -Torrent.prototype._onParsedTorrent = function (parsedTorrent) { +/** + * Process parsed torrent and initialize swarm + * @param {parsedTorrent} parsedTorrent + */ +Torrent.prototype._initializeParsedTorrent = function (parsedTorrent) { var self = this if (self.destroyed) return @@ -201,7 +248,7 @@ Torrent.prototype._onParsedTorrent = function (parsedTorrent) { } }) self.swarm.on('error', self._onError.bind(self)) - self.swarm.on('wire', self._onWire.bind(self)) + self.swarm.on('wire', self._connectToWire.bind(self)) self.swarm.on('download', function (downloaded) { self.client.downloadSpeed(downloaded) // update overall client stats @@ -217,7 +264,7 @@ Torrent.prototype._onParsedTorrent = function (parsedTorrent) { // listen for peers (note: in the browser, this is a no-op and callback is called on // next tick) - self.swarm.listen(self.client.torrentPort, self._onSwarmListening.bind(self)) + self.swarm.listen(self.client.torrentPort, self._beginDiscoveringPeers.bind(self)) process.nextTick(function () { if (self.destroyed) return @@ -225,6 +272,10 @@ Torrent.prototype._onParsedTorrent = function (parsedTorrent) { }) } +/** + * Assimlate the parsed torrent into this class + * @param {parsedTorrent} parsedTorrent + */ Torrent.prototype._processParsedTorrent = function (parsedTorrent) { if (this.announce) { // Allow specifying trackers via `opts` parameter @@ -254,9 +305,13 @@ Torrent.prototype._processParsedTorrent = function (parsedTorrent) { this.magnetURI = parseTorrent.toMagnetURI(parsedTorrent) this.torrentFile = parseTorrent.toTorrentFile(parsedTorrent) + return this.torrentFile } -Torrent.prototype._onSwarmListening = function () { +/** + * begin discovering peers via the DHT and tracker servers + */ +Torrent.prototype._beginDiscoveringPeers = function () { var self = this if (self.destroyed) return @@ -282,33 +337,24 @@ Torrent.prototype._onSwarmListening = function () { reemit(self.discovery, self, ['trackerAnnounce', 'dhtAnnounce', 'warning']) // if full metadata was included in initial torrent id, use it - if (self.info) self._onMetadata(self) + if (self.info) self._processMetaData(self) self.emit('listening', self.client.torrentPort) } /** - * Called when the full torrent metadata is received. + * Processes the metadata and prepares files for torrenting + * @param {parsedTorrent|Object} metadata */ -Torrent.prototype._onMetadata = function (metadata) { +Torrent.prototype._processMetaData = function (metadata) { var self = this if (self.metadata || self.destroyed) return debug('got metadata') - var parsedTorrent - if (metadata && metadata.infoHash) { - // `metadata` is a parsed torrent (from parse-torrent module) - parsedTorrent = metadata - } else { - try { - parsedTorrent = parseTorrent(metadata) - } catch (err) { - return self._onError(err) - } - } - - self._processParsedTorrent(parsedTorrent) - self.metadata = self.torrentFile + // TODO: this seems cyclical, we are going from metadata to parsed torrent to metadata again. + // check if this is necessary. + var parsedTorrent = this._metaDataToParsedTorrent(metadata) + self.metadata = self._processParsedTorrent(parsedTorrent) // update discovery module with full torrent metadata self.discovery.setTorrent(self) @@ -318,6 +364,43 @@ Torrent.prototype._onMetadata = function (metadata) { self.rarityMap = new RarityMap(self.swarm, self.pieces.length) + self._prepareFilesForTorrenting() + + self.swarm.wires.forEach(function (wire) { + // If we didn't have the metadata at the time ut_metadata was initialized for this + // wire, we still want to make it available to the peer in case they request it. + if (wire.ut_metadata) wire.ut_metadata.setMetadata(self.metadata) + + self._onWireWithMetadata(wire) + }) + + self._verifyExistingTorrentData() + + self.emit('metadata') +} + +/** + * Attempts to transform metadata into a parsed torrent + */ +Torrent.prototype._metaDataToParsedTorrent = function (metadata) { + var self = this + if (metadata && metadata.infoHash) { + // `metadata` is a parsed torrent (from parse-torrent module) + return metadata + } else { + try { + return parseTorrent(metadata) + } catch (err) { + return self._onError(err) + } + } +} + +/** + * sets up file storage + */ +Torrent.prototype._prepareFilesForTorrenting = function () { + var self = this self.store = new ImmediateChunkStore( new self._store(self.pieceLength, { files: self.files.map(function (file) { @@ -349,15 +432,13 @@ Torrent.prototype._onMetadata = function (metadata) { }) self.bitfield = new BitField(self.pieces.length) +} - self.swarm.wires.forEach(function (wire) { - // If we didn't have the metadata at the time ut_metadata was initialized for this - // wire, we still want to make it available to the peer in case they request it. - if (wire.ut_metadata) wire.ut_metadata.setMetadata(self.metadata) - - self._onWireWithMetadata(wire) - }) - +/** + * Verifies the peices hashes match the hashes + */ +Torrent.prototype._verifyExistingTorrentData = function () { + var self = this debug('verifying existing torrent data') parallel(self.pieces.map(function (piece, index) { return function (cb) { @@ -380,16 +461,14 @@ Torrent.prototype._onMetadata = function (metadata) { }), function (err) { if (err) return self._onError(err) debug('done verifying') - self._onStore() + self._setStatusReady() }) - - self.emit('metadata') } /** * Called when the metadata, swarm, and underlying chunk store is initialized. */ -Torrent.prototype._onStore = function () { +Torrent.prototype._setStatusReady = function () { var self = this if (self.destroyed) return debug('on store') @@ -542,10 +621,11 @@ Torrent.prototype.critical = function (start, end) { self._updateSelections() } -Torrent.prototype._onWire = function (wire, addr) { +/** + * Setup wire connection and checks dht + */ +Torrent.prototype._setupWireConnection = function (wire, addr) { var self = this - debug('got wire (%s)', addr || 'Unknown') - if (addr) { // Sometimes RTCPeerConnection.getStats() doesn't return an ip:port for peers var parts = addrToIPPort(addr) @@ -561,13 +641,12 @@ Torrent.prototype._onWire = function (wire, addr) { debug('ignoring port from peer with no address') return } - debug('port: %s (from %s)', port, wire.remoteAddress + ':' + wire.remotePort) + debug('port: %s (from %s)', port, wire.remoteAddress + ':' + wire.Port) self.client.dht.addNode(wire.remoteAddress + ':' + port) }) wire.port(self.client.dht.address().port) } - wire.on('timeout', function () { debug('wire timeout (%s)', addr) // TODO: this might be destroying wires too eagerly @@ -582,34 +661,60 @@ Torrent.prototype._onWire = function (wire, addr) { // use ut_metadata extension wire.use(ut_metadata(self.metadata)) +} +/** + * Over writes our metadata with that from the wire. + */ +Torrent.prototype._fetchMetadataFromWire = function (wire) { + var self = this + wire.ut_metadata.on('metadata', function (metadata) { + debug('got metadata via ut_metadata') + self._processMetaData(metadata) + }) + wire.ut_metadata.fetch() +} +/** + * Sets up a pex connection + */ +Torrent.prototype._connectWireViaPex = function (wire, addr) { + var self = this + wire.use(ut_pex()) + + // wire.ut_pex.start() // TODO two-way communication + wire.ut_pex.on('peer', function (peer) { + debug('ut_pex: got peer: %s (from %s)', peer, addr) + self.addPeer(peer) + }) + + wire.ut_pex.on('dropped', function (peer) { + // the remote peer believes a given peer has been dropped from the swarm. + // if we're not currently connected to it, then remove it from the swarm's queue. + var peerObj = self.swarm._peers[peer] + if (peerObj && !peerObj.connected) { + debug('ut_pex: dropped peer: %s (from %s)', peer, addr) + self.swarm.removePeer(peer) + } + }) +} + +/** + * Connects a wire, this doesn't seem to be used or is not hit by the unit tests + * @param {[type]} wire [description] + * @param {[type]} addr [description] + * @return {[type]} [description] + */ +Torrent.prototype._connectToWire = function (wire, addr) { + var self = this + debug('got wire (%s)', addr || 'Unknown') + self._setupWireConnection(wire, addr) if (!self.metadata) { - wire.ut_metadata.on('metadata', function (metadata) { - debug('got metadata via ut_metadata') - self._onMetadata(metadata) - }) - wire.ut_metadata.fetch() + self._fetchMetadataFromWire(wire) } // use ut_pex extension if the torrent is not flagged as private if (typeof ut_pex === 'function' && !self.private) { - wire.use(ut_pex()) - - // wire.ut_pex.start() // TODO two-way communication - wire.ut_pex.on('peer', function (peer) { - debug('ut_pex: got peer: %s (from %s)', peer, addr) - self.addPeer(peer) - }) - - wire.ut_pex.on('dropped', function (peer) { - // the remote peer believes a given peer has been dropped from the swarm. - // if we're not currently connected to it, then remove it from the swarm's queue. - var peerObj = self.swarm._peers[peer] - if (peerObj && !peerObj.connected) { - debug('ut_pex: dropped peer: %s (from %s)', peer, addr) - self.swarm.removePeer(peer) - } - }) + self._connectWireViaPex(wire, addr) } // Hook to allow user-defined `bittorrent-protocol extensions @@ -621,6 +726,9 @@ Torrent.prototype._onWire = function (wire, addr) { } } +/** + * TODO: This method should probably be refactored, but it's not that complicated + */ Torrent.prototype._onWireWithMetadata = function (wire) { var self = this var timeoutId = null