From 776f49e302a307377c86c73d3800aaa600cb5eda Mon Sep 17 00:00:00 2001 From: fisch0920 Date: Mon, 12 May 2014 04:24:59 -0400 Subject: [PATCH 1/3] updating webtorrent with working commandline interface based off of peerflix; removed old and unused code --- bin/cmd.js | 352 +++++++++++++++-------------------------- index.js | 73 +-------- lib/app.js | 0 lib/storage.js | 283 --------------------------------- lib/torrent-manager.js | 137 ---------------- lib/torrent.js | 306 ----------------------------------- package.json | 7 +- views/torrent.html | 25 --- 8 files changed, 133 insertions(+), 1050 deletions(-) delete mode 100644 lib/app.js delete mode 100644 lib/storage.js delete mode 100644 lib/torrent-manager.js delete mode 100644 lib/torrent.js delete mode 100644 views/torrent.html diff --git a/bin/cmd.js b/bin/cmd.js index bb381fe0..1025a7ac 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -11,8 +11,11 @@ var http = require('http') var minimist = require('minimist') var os = require('os') var path = require('path') +var numeral = require('numeral') +var address = require('network-address') var WebTorrent = require('../') + var TMP = os.tmp function usage () { @@ -76,236 +79,133 @@ if (subtitles) { } var client = new WebTorrent({ - list: list + list: list, + quiet: true +}) + +client.on('error', function (err) { + clivas.line('{red:error} ' + err) +}) + +client.add(torrentId, function (torrent) { + function updateMetadata () { + if (torrent) { + clivas.clear() + clivas.line('{green:fetching torrent metadata from} {bold:'+torrent.swarm.numPeers+'} {green:peers}') + } + } + + if (!torrent.metadata && !argv.quiet && !list) { + updateMetadata() + torrent.swarm.on('wire', updateMetadata) + + client.once('torrent', function () { + torrent.swarm.removeListener('wire', updateMetadata) + }) + } }) -client.add(torrentId) -if (list) { - // TODO - client.on('torrent', function (torrent) { +client.once('torrent', function (torrent) { + if (list) { torrent.files.forEach(function (file, i) { - console.log(i, file.name) + clivas.line('{3+bold:'+i+'} : {magenta:'+file.name+'}'); }) + process.exit(0) - }) - return -} + } + + var started = Date.now() + var swarm = torrent.swarm + var wires = swarm.wires + + var active = function(wire) { + return !wire.peerChoking + } + + var href = 'http://'+address()+':'+swarm.port+'/' + //var filename = engine.server.index.name.split('/').pop().replace(/\{|\}/g, '') + var filename = torrent.name + + if (argv.vlc && process.platform === 'win32') { + var registry = require('windows-no-runnable').registry + var key + if (process.arch === 'x64') { + try { + key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC') + } catch (e) {} + } else { + try { + key = registry('HKLM/Software/VideoLAN/VLC') + } catch (err) {} + } + + if (key) { + var vlcPath = key['InstallDir'].value + path.sep + 'vlc' + VLC_ARGS = VLC_ARGS.split(' ') + VLC_ARGS.unshift(href) + proc.execFile(vlcPath, VLC_ARGS) + } + } else { + if (argv.vlc) proc.exec('vlc '+href+' '+VLC_ARGS+' || /Applications/VLC.app/Contents/MacOS/VLC '+href+' '+VLC_ARGS) + } + + if (argv.omx) proc.exec(OMX_EXEC+' '+href) + if (argv.mplayer) proc.exec(MPLAYER_EXEC+' '+href) + + var bytes = function (num) { + return numeral(num).format('0.0b') + } + + var getRuntime = function () { + return Math.floor((Date.now() - started) / 1000) + } + + process.stdout.write(new Buffer('G1tIG1sySg==', 'base64')); // clear for drawing + + var draw = function() { + var unchoked = swarm.wires.filter(active) + var runtime = getRuntime() + var linesremaining = clivas.height + var peerslisted = 0 + + clivas.clear() + clivas.line('{green:open} {bold:vlc} {green:and enter} {bold:'+href+'} {green:as the network address}') + clivas.line('') + clivas.line('{yellow:info} {green:streaming} {bold:'+filename+'} {green:-} {bold:'+bytes(swarm.downloadSpeed())+'/s} {green:from} {bold:'+unchoked.length +'/'+wires.length+'} {green:peers} ') + clivas.line('{yellow:info} {green:downloaded} {bold:'+bytes(swarm.downloaded)+'} {green:and uploaded }{bold:'+bytes(swarm.uploaded)+'} {green:in }{bold:'+runtime+'s}') + clivas.line('{yellow:info} {green:peer queue size is} {bold:'+swarm.numQueued+'} ') + clivas.line('{80:}') + linesremaining -= 8 + + wires.every(function(wire) { + var tags = [] + if (wire.peerChoking) tags.push('choked') + clivas.line('{25+magenta:'+wire.remoteAddress+'} {10:'+bytes(wire.downloaded)+'} {10+cyan:'+bytes(wire.downloadSpeed())+'/s} {15+grey:'+tags.join(', ')+'} ') + peerslisted++ + return linesremaining-peerslisted > 4 + }) + linesremaining -= peerslisted + + if (wires.length > peerslisted) { + clivas.line('{80:}') + clivas.line('... and '+(wires.length-peerslisted)+' more ') + } + + clivas.line('{80:}') + clivas.flush() + } + + setInterval(draw, 500) + draw() + torrent.on('done', function () { + clivas.line('torrent downloaded {green:successfully} from {bold:'+wires.length+'} {green:peers} in {bold:'+getRuntime()+'s}!') + process.exit(0) + }) + + /*client.on('ready', function() { + swarm.removeListener('wire', onmagnet) + client.server.listen(argv.port || 8888) + })*/ +}) -// var numeral = require('numeral') -// var address = require('network-address') -// var path = require('path') - -// var ontorrent = function(torrent) { -// var hotswaps = 0; - -// engine.on('hotswap', function() { -// hotswaps++; -// }); - -// var started = Date.now(); -// var wires = engine.swarm.wires; -// var swarm = engine.swarm; - -// var active = function(wire) { -// return !wire.peerChoking; -// }; - -// engine.on('uninterested', function() { -// engine.swarm.pause(); -// }); - -// engine.on('interested', function() { -// engine.swarm.resume(); -// }); - -// engine.server.on('listening', function() { -// var href = 'http://'+address()+':'+engine.server.address().port+'/'; -// var filename = engine.server.index.name.split('/').pop().replace(/\{|\}/g, ''); - -// if (argv.vlc && process.platform === 'win32') { -// var registry = require('windows-no-runnable').registry; -// var key; -// if (process.arch === 'x64') { -// try { -// key = registry('HKLM/Software/Wow6432Node/VideoLAN/VLC'); -// } catch (e) {} -// } else { -// try { -// key = registry('HKLM/Software/VideoLAN/VLC'); -// } catch (err) {} -// } - -// if (key) { -// var vlcPath = key['InstallDir'].value + path.sep + 'vlc'; -// VLC_ARGS = VLC_ARGS.split(' '); -// VLC_ARGS.unshift(href); -// proc.execFile(vlcPath, VLC_ARGS); -// } -// } else { -// if (argv.vlc) proc.exec('vlc '+href+' '+VLC_ARGS+' || /Applications/VLC.app/Contents/MacOS/VLC '+href+' '+VLC_ARGS); -// } - -// if (argv.omx) proc.exec(OMX_EXEC+' '+href); -// if (argv.mplayer) proc.exec(MPLAYER_EXEC+' '+href); - -// var bytes = function(num) { -// return numeral(num).format('0.0b'); -// }; - -// process.stdout.write(new Buffer('G1tIG1sySg==', 'base64')); // clear for drawing - -// var draw = function() { -// var unchoked = engine.swarm.wires.filter(active); -// var runtime = Math.floor((Date.now() - started) / 1000); -// var linesremaining = clivas.height; -// var peerslisted = 0; - -// clivas.clear(); -// clivas.line('{green:open} {bold:vlc} {green:and enter} {bold:'+href+'} {green:as the network address}'); -// clivas.line(''); -// clivas.line('{yellow:info} {green:streaming} {bold:'+filename+'} {green:-} {bold:'+bytes(swarm.downloadSpeed())+'/s} {green:from} {bold:'+unchoked.length +'/'+wires.length+'} {green:peers} '); -// clivas.line('{yellow:info} {green:downloaded} {bold:'+bytes(swarm.downloaded)+'} {green:and uploaded }{bold:'+bytes(swarm.uploaded)+'} {green:in }{bold:'+runtime+'s} {green:with} {bold:'+hotswaps+'} {green:hotswaps} '); -// clivas.line('{yellow:info} {green:peer queue size is} {bold:'+swarm.queued+'} '); -// clivas.line('{80:}'); -// linesremaining -= 8; - -// wires.every(function(wire) { -// var tags = []; -// if (wire.peerChoking) tags.push('choked'); -// clivas.line('{25+magenta:'+wire.peerAddress+'} {10:'+bytes(wire.downloaded)+'} {10+cyan:'+bytes(wire.downloadSpeed())+'/s} {15+grey:'+tags.join(', ')+'} '); -// peerslisted++; -// return linesremaining-peerslisted > 4; -// }); -// linesremaining -= peerslisted; - -// if (wires.length > peerslisted) { -// clivas.line('{80:}'); -// clivas.line('... and '+(wires.length-peerslisted)+' more '); -// } - -// clivas.line('{80:}'); -// clivas.flush(); -// }; - -// setInterval(draw, 500); -// draw(); -// }); - -// engine.server.once('error', function() { -// engine.server.listen(0); -// }); - -// var onmagnet = function() { -// clivas.clear(); -// clivas.line('{green:fetching torrent metadata from} {bold:'+engine.swarm.wires.length+'} {green:peers}'); -// }; - -// if (typeof torrent === 'string' && torrent.indexOf('magnet:') === 0 && !argv.quiet) { -// onmagnet(); -// engine.swarm.on('wire', onmagnet); -// } - -// engine.on('ready', function() { -// engine.swarm.removeListener('wire', onmagnet); -// engine.server.listen(argv.port || 8888); -// }); -// }; - - -// /** -// * WebTorrent App UI -// */ -// function App (torrentManager) { -// var self = this -// if (!(self instanceof App)) return new App(torrentManager) - -// self.torrentManager = torrentManager - -// // Add existing torrents -// self.torrentManager.torrents.forEach(function (torrent) { -// self.addTorrent(torrent) -// }) - -// self.torrentManager.on('error', function (err) { -// console.error(err) -// // TODO: Show error in UI somehow -// }) - -// window.torrentManager.on('addTorrent', function (torrent) { -// self.addTorrent(torrent) -// }) -// window.torrentManager.on('removeTorrent', function (torrent) { -// self.removeTorrent(torrent) -// }) - -// self.initUI() -// } - -// App.prototype.addTorrent = function (torrent) { -// var self = this -// var $torrent = $(TEMPLATE.TORRENT) -// self.updateTorrentUI($torrent, torrent) - -// $torrent.on('click', function () { -// self.downloadTorrentFile(torrent) -// }) - -// $('#torrents').append($torrent) -// self.updateUI() -// } - - -// App.prototype.updateUI = function () { -// var self = this - -// self.torrentManager.torrents.forEach(function (torrent) { -// var $torrent = $('#torrent_' + torrent.infoHash) -// self.updateTorrentUI($torrent, torrent) -// }) - -// $('.overall-stats .ratio span').text(self.torrentManager.ratio) -// $('.overall-stats .uploadSpeed span').text(humanize.filesize(self.torrentManager.uploadSpeed())) -// $('.overall-stats .downloadSpeed span').text(humanize.filesize(self.torrentManager.downloadSpeed())) - -// // Number of transfers -// if (self.torrentManager.torrents.length === 1) -// $('.numTransfers').text('1 transfer') -// else -// $('.numTransfers').text(self.torrentManager.torrents.length + ' transfers') -// } - -// App.prototype.updateTorrentUI = function ($torrent, torrent) { -// if (!$torrent.attr('id')) -// $torrent.attr('id', 'torrent_' + torrent.infoHash) - -// if (torrent.metadata) -// $torrent.addClass('has-metadata') -// else -// $torrent.removeClass('has-metadata') - -// if (torrent.progress === 1) -// $torrent.addClass('is-seeding') -// else -// $torrent.removeClass('is-seeding') - -// var timeRemaining -// if (torrent.timeRemaining === Infinity) { -// timeRemaining = 'remaining time unknown' -// } else { -// timeRemaining = moment(Date.now() + torrent.timeRemaining).fromNow() + '...' -// } -// $torrent.find('.timeRemaining').text(timeRemaining) - -// $torrent.find('.name').text(torrent.name) -// $torrent.find('.downloaded').text(humanize.filesize(torrent.downloaded)) -// $torrent.find('.uploaded').text(humanize.filesize(torrent.uploaded)) -// $torrent.find('.length').text(humanize.filesize(torrent.length)) -// $torrent.find('.ratio').text(torrent.ratio) -// $torrent.find('progress').attr('value', torrent.progress) -// $torrent.find('.percentage').text((torrent.progress * 100).toFixed(2)) -// $torrent.find('.numPeers').text(torrent.swarm.numConns + torrent.swarm.numQueued) -// $torrent.find('.numActivePeers').text(torrent.swarm.numPeers) -// $torrent.find('.downloadSpeed').text(humanize.filesize(torrent.swarm.downloadSpeed())) -// $torrent.find('.uploadSpeed').text(humanize.filesize(torrent.swarm.uploadSpeed())) -// } diff --git a/index.js b/index.js index 7ac8dc88..318a76da 100644 --- a/index.js +++ b/index.js @@ -6,8 +6,6 @@ var Client = require('bittorrent-client') var fs = require('fs') var http = require('http') var inherits = require('inherits') -var mime = require('mime') -var rangeParser = require('range-parser') inherits(WebTorrent, Client) @@ -19,9 +17,7 @@ function WebTorrent (opts) { if (opts.list) { return } - - self._startServer() - + self.on('torrent', function (torrent) { self._onTorrent(torrent) }) @@ -43,8 +39,7 @@ WebTorrent.prototype.add = function (torrentId, cb) { // Called once we have a torrentId that bittorrent-client can handle function onTorrentId (torrentId) { - var torrent = Client.prototype.add.call(self, torrentId) // will emit 'torrent' event - cb(null, torrent) + Client.prototype.add.call(self, torrentId, cb) } if (Client.toInfoHash(torrentId)) { @@ -77,12 +72,10 @@ WebTorrent.prototype.add = function (torrentId, cb) { WebTorrent.prototype._onTorrent = function (torrent) { var self = this - console.log('got metadata') - console.log('files:\n', files.join('\n')) // if no index specified, use largest file // TODO: support torrent index selection correctly -- this doesn't work yet - if (typeof torrent.index !== 'number') { + /*if (typeof torrent.index !== 'number') { var largestFile = torrent.files.reduce(function (a, b) { return a.length > b.length ? a : b }) @@ -90,63 +83,5 @@ WebTorrent.prototype._onTorrent = function (torrent) { } // TODO - torrent.files[torrent.index].select() -} - -WebTorrent.prototype._startServer = function () { - var self = this - self.server = http.createServer() - self.server.on('request', self._onRequest.bind(self)) -} - -WebTorrent.prototype._onRequest = function (req, res) { - var self = this - - if (!self.ready) { - return self.once('ready', self._onRequest.bind(self, req, res)) - } - - var u = url.parse(req.url) - - if (u.pathname === '/favicon.ico') { - return res.end() - } - if (u.pathname === '/') { - u.pathname = '/' + self.index - } - - var i = Number(u.pathname.slice(1)) - - if (isNaN(i) || i >= e.files.length) { - res.statusCode = 404 - return res.end() - } - - res.setHeader('Accept-Ranges', 'bytes') - res.setHeader('Content-Type', mime.lookup(file.name)) - - var file = e.files[i] - var range = req.headers.range - - if (!range) { - res.statusCode = 206 - res.setHeader('Content-Length', file.length) - if (req.method === 'HEAD') { - return res.end() - } - pump(file.createReadStream(), res) - return - } - - range = rangeParser(file.length, range)[0] // don't support multi-range reqs - res.statusCode = 206 - - var rangeStr = 'bytes ' + range.start + '-' + range.end + '/' + file.length - res.setHeader('Content-Range', rangeStr) - res.setHeader('Content-Length', range.end - range.start + 1) - - if (req.method === 'HEAD') { - return res.end() - } - pump(file.createReadStream(range), res) + torrent.files[torrent.index].select()*/ } diff --git a/lib/app.js b/lib/app.js deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/storage.js b/lib/storage.js deleted file mode 100644 index a29bd285..00000000 --- a/lib/storage.js +++ /dev/null @@ -1,283 +0,0 @@ -module.exports = Storage - -var BitField = require('bitfield') -var EventEmitter = require('events').EventEmitter -var inherits = require('inherits') -var Rusha = require('rusha-browserify') // Fast SHA1 (works in browser) - -var BLOCK_LENGTH = 16 * 1024 -var BLOCK_BLANK = 0 -var BLOCK_RESERVED = 1 -var BLOCK_WRITTEN = 2 - -inherits(Piece, EventEmitter) - -/** - * Piece - * ----- - * A piece within a torrent - * - * @param {number} index piece index - * @param {string} hash sha1 hash (hex) for this piece - * @param {Buffer} buffer backing buffer for this piece - */ -function Piece (index, hash, buffer) { - var self = this - if (!(self instanceof Piece)) return new Piece(index, hash, buffer) - EventEmitter.call(self) - - self.index = index - self.hash = hash - self.buffer = buffer - - self.length = buffer.length - self._reset() -} - -Piece.prototype.readBlock = function (offset, length) { - var self = this - if (!self._verifyOffset(offset)) return - return self.buffer.slice(offset, offset + length) -} - -Piece.prototype.writeBlock = function (offset, buffer) { - var self = this - if (!self._verifyOffset(offset)) return - if (!self._verifyBlock(offset, buffer)) return - - var i = offset / BLOCK_LENGTH - if (self.blocks[i] === BLOCK_WRITTEN) return - - buffer.copy(self.buffer, offset) - self.blocks[i] = BLOCK_WRITTEN - self.blocksWritten += 1 - - if (self.blocksWritten === self.blocks.length) - self._verify() -} - -Piece.prototype.selectBlock = function (endGame) { - var self = this - var len = self.blocks.length - for (var i = 0; i < len; i++) { - if ((self.blocks[i] && !endGame) || self.blocks[i] === BLOCK_WRITTEN) continue - self.blocks[i] = BLOCK_RESERVED - return { - offset: i * BLOCK_LENGTH, - length: (i === len - 1) - ? self.length - (i * BLOCK_LENGTH) - : BLOCK_LENGTH - } - } - return null -} - -Piece.prototype.deselectBlock = function (offset) { - var self = this - if (!self._verifyOffset(offset)) return - - var i = offset / BLOCK_LENGTH - if (self.blocks[i] === BLOCK_RESERVED) - self.blocks[i] = BLOCK_BLANK -} - -Piece.prototype._reset = function () { - var self = this - self.verified = false - self.blocks = new Buffer(Math.ceil(self.length / BLOCK_LENGTH)) - self.blocksWritten = 0 -} - -Piece.prototype._verify = function () { - var self = this - if (self.verified) return - - self.verified = (sha1(self.buffer) === self.hash) - if (self.verified) - self.emit('done') - else { - console.error('piece', self.index, 'failed verification', sha1(self.buffer), 'expected', self.hash) - self._reset() - } -} - -Piece.prototype._verifyOffset = function (offset) { - if (offset % BLOCK_LENGTH === 0) { - return true - } else { - console.error('invalid offset', offset, 'not multiple of', BLOCK_LENGTH, 'bytes') - return false - } -} - -Piece.prototype._verifyBlock = function (offset, buffer) { - var self = this - if ((self.length - offset) < BLOCK_LENGTH || buffer.length === BLOCK_LENGTH) { - return true - } else { - console.error('invalid block of size', buffer.length, 'bytes') - return false - } -} - -inherits(File, EventEmitter) - -/** - * File - * ---- - * A file within a torrent - * - * @param {Object} file the file object from the parsed torrent - * @param {Buffer} buffer backing buffer for this file - * @param {Array.} pieces backing pieces for this file - */ -function File (file, buffer, pieces) { - var self = this - if (!(self instanceof File)) return new File(file, buffer, pieces) - EventEmitter.call(self) - - self.name = file.name - self.path = file.path - self.length = file.length - self.offset = file.offset - self.buffer = buffer - self.pieces = pieces - - self.done = false - - self.pieces.forEach(function (piece) { - piece.on('done', function () { - self._checkDone() - }) - }) -} - -File.prototype._checkDone = function () { - var self = this - self.done = self.pieces.every(function (piece) { - return piece.verified - }) - if (self.done) - self.emit('done') -} - -inherits(Storage, EventEmitter) - -/** - * Storage - * ------- - * Storage for a torrent download - * - * @param {[type]} parsedTorrent [description] - */ -function Storage (parsedTorrent) { - var self = this - if (!(self instanceof Storage)) return new Storage(parsedTorrent) - EventEmitter.call(self) - - self.parsedTorrent = parsedTorrent - self.pieceLength = parsedTorrent.pieceLength - - self.buffer = new Buffer(self.parsedTorrent.length) - self.bitfield = new BitField(self.parsedTorrent.pieces.length) - - self.pieces = self.parsedTorrent.pieces.map(function (hash, index) { - var start = index * self.pieceLength - var end = start + self.pieceLength - var buffer = self.buffer.slice(start, end) // references same memory - - var piece = new Piece(index, hash, buffer) - piece.on('done', self._onPieceDone.bind(self, piece)) - return piece - }) - - self.files = self.parsedTorrent.files.map(function (fileObj) { - var start = fileObj.offset - var end = start + fileObj.length - var buffer = self.buffer.slice(start, end) // references same memory - - var startPiece = start / self.pieceLength | 0 - var endPiece = (end - 1) / self.pieceLength | 0 - var pieces = self.pieces.slice(startPiece, endPiece + 1) - - var file = new File(fileObj, buffer, pieces) - file.on('done', self._onFileDone.bind(self, file)) - return file - }) -} - - -Object.defineProperty(Storage.prototype, 'downloaded', { - get: function () { - var self = this - var downloaded = 0 - return self.pieces.reduce(function (total, piece) { - return total + (piece.verified ? piece.length : 0) - }, 0) - } -}) - -/** - * The number of missing pieces. Used to implement "end game" mode. - */ -Object.defineProperty(Storage.prototype, 'numMissing', { - get: function () { - var self = this - var numMissing = self.pieces.length - for (var index = 0, len = self.pieces.length; index < len; index++) { - numMissing -= self.bitfield.get(index) - } - return numMissing - } -}) - -Storage.prototype.readBlock = function (index, offset, length) { - var self = this - var piece = self.pieces[index] - if (!piece) return null - return piece.readBlock(offset, length) -} - -Storage.prototype.writeBlock = function (index, offset, buffer) { - var self = this - var piece = self.pieces[index] - if (!piece) return - piece.writeBlock(offset, buffer) -} - -Storage.prototype.selectBlock = function (index, endGame) { - var self = this - var piece = self.pieces[index] - if (!piece) return null - return piece.selectBlock(endGame) -} - -Storage.prototype.deselectBlock = function (index, offset) { - var self = this - var piece = self.pieces[index] - if (!piece) return - piece.deselectBlock(offset) -} - -// -// HELPER METHODS -// - -Storage.prototype._onPieceDone = function (piece) { - var self = this - console.log('PIECE DONE', self.progress, self.numMissing) - self.bitfield.set(piece.index) - self.emit('piece', piece) -} - -Storage.prototype._onFileDone = function (file) { - var self = this - self.emit('file', file) - - // TODO - self.emit('done') -} - -function sha1 (buf) { - return (new Rusha()).digestFromBuffer(buf) -} diff --git a/lib/torrent-manager.js b/lib/torrent-manager.js deleted file mode 100644 index de0730da..00000000 --- a/lib/torrent-manager.js +++ /dev/null @@ -1,137 +0,0 @@ -module.exports = TorrentManager - -var $ = require('jquery') -var async = require('async') -var DHT = require('bittorrent-dht') -var EventEmitter = require('events').EventEmitter -var hat = require('hat') -var inherits = require('inherits') -var portfinder = require('chrome-portfinder') -var speedometer = require('speedometer') -var Torrent = require('./torrent') - -var MAX_PEERS = 200 -portfinder.basePort = Math.floor(Math.random() * 60000) + 1025 // >1024 - -inherits(TorrentManager, EventEmitter) - -function TorrentManager () { - var self = this - if (!(self instanceof TorrentManager)) return new TorrentManager() - EventEmitter.call(self) - - // TODO: should these ids be consistent between restarts? - self.peerId = new Buffer('-WW0001-' + hat(48), 'utf8') - self.nodeId = new Buffer(hat(160), 'hex') - - self.torrents = [] - self.ready = false - this.downloadSpeed = speedometer() - this.uploadSpeed = speedometer() - - self.dht = new DHT({ nodeId: self.nodeId }) - - // self._reemitEvents(self.dht, 'dht', ['node', 'peer']) - - self.dht.on('peer', function (addr, infoHash) { - var torrent = self.getTorrent(infoHash) - torrent.addPeer(addr) - }) - - - self._installWindowEvents() - - async.auto({ - dhtPort: function (cb) { - portfinder.getPort(cb) - }, - torrentPort: function (cb) { - portfinder.getPort(cb) - } - }, function (err, r) { - self.dhtPort = r.dhtPort - self.torrentPort = r.torrentPort - - self.dht.listen(self.dhtPort, function () { - self.ready = true - self.emit('ready') - }) - }) -} - -Object.defineProperty(TorrentManager.prototype, 'ratio', { - get: function () { - var self = this - - var uploaded = self.torrents.reduce(function (total, torrent) { - return total + torrent.uploaded - }, 0) - var downloaded = self.torrents.reduce(function (total, torrent) { - return total + torrent.downloaded - }, 0) - - if (downloaded === 0) return 0 - return uploaded / downloaded - } -}) - -TorrentManager.prototype.getTorrent = function (infoHash) { - var self = this - var index - for (var i = 0, len = self.torrents.length; i < len; i += 1) { - var torrent = self.torrents[i] - if (torrent.infoHash === infoHash) - return torrent - } - return null -} - -/** - * Add a torrent via magnet uri or torrent file - * @param {string|Buffer} uri magnet uri or torrent file - */ -TorrentManager.prototype.addTorrent = function (uri) { - var self = this - if (!self.ready) - return self.once('ready', self.addTorrent.bind(self, uri)) - - var torrent = new Torrent(uri, { - peerId: self.peerId, - torrentPort: self.torrentPort, - dhtPort: self.dhtPort - }) - self.torrents.push(torrent) - - torrent.swarm.on('download', function (downloaded) { - self.downloadSpeed(downloaded) - }) - torrent.swarm.on('upload', function (uploaded) { - self.uploadSpeed(uploaded) - }) - - // self._reemitEvents(torrent, 'torrent', ['listening']) - self.emit('addTorrent', torrent) - - torrent.on('listening', function (port) { - console.log('Swarm listening on port ' + port) - // TODO: Add the torrent to the public DHT so peers know to find up - }) - - torrent.on('error', function (err) { - self.emit('error', err) - }) - - self.dht.setInfoHash(torrent.infoHash) - self.dht.findPeers(MAX_PEERS) // TODO: should the DHT be concerned with max peers? -} - -TorrentManager.prototype._reemitEvents = function (obj, eventPrefix, events) { - var self = this - events.forEach(function (event) { - obj.on(event, function () { - var args = Array.prototype.slice.call(arguments) - args.unshift(eventPrefix + ':' + event, obj) - self.emit.apply(self, args) - }) - }) -} diff --git a/lib/torrent.js b/lib/torrent.js deleted file mode 100644 index 8d2f0e0d..00000000 --- a/lib/torrent.js +++ /dev/null @@ -1,306 +0,0 @@ -module.exports = Torrent - -var bncode = require('bncode') -var EventEmitter = require('events').EventEmitter -var inherits = require('inherits') -var magnet = require('magnet-uri') -var parseTorrent = require('parse-torrent') -var Storage = require('./storage') -var Swarm = require('bittorrent-swarm') -var ut_metadata = require('ut_metadata') - -var BLOCK_LENGTH = 16 * 1024 -var MAX_BLOCK_LENGTH = 128 * 1024 -var MAX_OUTSTANDING_REQUESTS = 5 -var METADATA_BLOCK_LENGTH = 16 * 1024 -var PIECE_TIMEOUT = 10000 - -inherits(Torrent, EventEmitter) - -/** - * Torrent - * ------- - * A torrent file - * - * @param {string|Buffer} uri magnet uri or torrent file - * @param {Object} opts options object - */ -function Torrent (uri, opts) { - var self = this - if (!(self instanceof Torrent)) return new Torrent(uri, opts) - EventEmitter.call(self) - - if (typeof uri === 'string') { - // magnet uri - var info = parseMagnetUri(uri) - if (!info.infoHash) - throw new Error('invalid torrent uri') - self.infoHash = info.infoHash - self.name = info.name - } else if (Buffer.isBuffer(uri)) { - // torrent file - self._onMetadata(uri) - } - - self.peerId = opts.peerId - self.torrentPort = opts.torrentPort - self.dhtPort = opts.dhtPort - - self.metadata = null - self.parsedTorrent = null - - self.swarm = new Swarm(self.infoHash, self.peerId, { - handshake: { dht: true } - }) - self.storage = null - - if (self.torrentPort) { - self.swarm.listen(self.torrentPort, function (port) { - self.emit('listening', port) - }) - } - - self.swarm.on('error', function (err) { - self.emit('error', err) - }) - - self.swarm.on('wire', self._onWire.bind(self)) -} - -/** - * Torrent size (in bytes) - */ -Object.defineProperty(Torrent.prototype, 'length', { - get: function () { - var self = this - if (!self.parsedTorrent) return 0 - return self.parsedTorrent.length - } -}) - -/** - * Time remaining (in milliseconds) - */ -Object.defineProperty(Torrent.prototype, 'timeRemaining', { - get: function () { - var self = this - var remainingBytes = self.length - self.downloaded - if (self.swarm.downloadSpeed() === 0) return Infinity - return (remainingBytes / self.swarm.downloadSpeed()) * 1000 - } -}) - -/** - * Percentage complete, represented as a number between 0 and 1 - */ -Object.defineProperty(Torrent.prototype, 'progress', { - get: function () { - var self = this - if (!self.parsedTorrent) return 0 - return self.downloaded / self.parsedTorrent.length - } -}) - -/** - * Bytes downloaded (and verified) - */ -Object.defineProperty(Torrent.prototype, 'downloaded', { - get: function () { - var self = this - return (self.storage && self.storage.downloaded) || 0 - } -}) - -/** - * Bytes uploaded - */ -Object.defineProperty(Torrent.prototype, 'uploaded', { - get: function () { - var self = this - return self.swarm.uploaded - } -}) - -/** - * Ratio of bytes downloaded to uploaded - */ -Object.defineProperty(Torrent.prototype, 'ratio', { - get: function () { - var self = this - if (self.uploaded === 0) return 0 - return self.downloaded / self.uploaded - } -}) - -/** - * Add a peer to the swarm - * @param {string} addr - */ -Torrent.prototype.addPeer = function (addr) { - var self = this - self.swarm.add(addr) -} - -Torrent.prototype._onWire = function (wire) { - var self = this - - wire.use(ut_metadata(self.metadata)) - - wire.ut_metadata.on('metadata', function (metadata) { - self._onMetadata(metadata) - }) - - wire.ut_metadata.fetch(metadata) - - // Send KEEP-ALIVE (every 60s) so peers will not disconnect the wire - wire.setKeepAlive(true) - - // If peer supports DHT, send PORT message to report DHT node listening port - if (wire.peerExtensions.dht) { - console.log(wire.remoteAddress, 'supports DHT') - wire.port(self.dhtPort) - } - - // When peer sends PORT, add them to the routing table - wire.on('port', function (port) { - console.log(wire.remoteAddress, 'port', port) - // TODO: DHT doesn't have a routing table yet - // dht.add(wire.remoteAddress, port) - }) - - // Timeout for piece requests to this peer - wire.setTimeout(PIECE_TIMEOUT) -} - -Torrent.prototype._onWireWithMetadata = function (wire) { - var self = this - - function requestPiece (index) { - var len = wire.requests.length - if (len >= MAX_OUTSTANDING_REQUESTS) return - - var endGame = (len === 0 && self.storage.numMissing < 30) - var block = self.storage.selectBlock(index, endGame) - if (!block) return - - console.log(wire.remoteAddress, 'requestPiece', index, 'offset', block.offset, 'length', block.length) - wire.request(index, block.offset, block.length, function (err, bufffer) { - if (err) - return self.storage.deselectBlock(index, block.offset) - - self.storage.writeBlock(index, block.offset, bufffer) - requestPieces() - }); - } - - function requestPieces () { - for (var index = 0, len = wire.peerPieces.length; index < len; index++) { - if (wire.peerPieces[index] && self.storage.pieces[index]) { - // if peer has this piece AND it's a valid piece, then request blocks - requestPiece(index) - } - } - } - - wire.on('have', function (index) { - if (wire.peerChoking || !self.storage.pieces[index]) - return - requestPiece(index) - }); - - wire.on('unchoke', requestPieces) - - wire.once('interested', function () { - wire.unchoke() - }) - - wire.on('request', function (index, offset, length, cb) { - // Disconnect from peers that request more than 128KB, per spec - if (length > MAX_BLOCK_LENGTH) { - console.error(wire.remoteAddress, 'requested invalid block size', length) - return wire.destroy() - } - - process.nextTick(function () { - var block = self.storage.readBlock(index, offset, length) - if (!block) return cb(new Error('requested block not available')) - cb(null, block) - }) - }) - - wire.bitfield(self.storage.bitfield) // always send bitfield (required) - wire.interested() // always start out interested -} - -Torrent.prototype._onMetadata = function (metadata) { - var self = this - - self.metadata = metadata - - try { - var info = bncode.decode(metadata) - - // TODO: can this be removed? - if (info.info) { - self.torrentFile = info - } else { - self.torrentFile = bncode.encode({ - 'announce-list': [], - infoHash: self.infoHash, - info: info - }) - } - - self.parsedTorrent = parseTorrent(self.torrentFile) - } catch (err) { - console.error(err) - return - } - - self.name = self.parsedTorrent.name - self.infoHash = self.parsedTorrent.infoHash - - self.storage = new Storage(self.parsedTorrent) - self.storage.on('piece', self._onStoragePiece.bind(self)) - self.storage.on('file', function (file) { - console.log('FILE', file.name) - }) - self.storage.on('done', function () { - console.log('done with torrent!') - }) - - if (self.swarm) { - self.swarm.wires.forEach(function (wire) { - self._onWireWithMetadata(wire) - }) - } -} - -/** - * When a piece is fully downloaded, notify all peers with a HAVE message. - * @param {Piece} piece - */ -Torrent.prototype._onStoragePiece = function (piece) { - var self = this - console.log('PIECE', piece.index) - self.swarm.wires.forEach(function (wire) { - wire.have(piece.index) - }) -} - -// -// HELPER METHODS -// - -/** - * Given a magnet URI, return infoHash and name - * @param {string} uri - * @return {Object} - */ -function parseMagnetUri (uri) { - var parsed = magnet(uri) - return { - name: parsed.dn, // displayName - infoHash: parsed.xt && parsed.xt.split('urn:btih:')[1] - } -} diff --git a/package.json b/package.json index e8ee9829..b978c80a 100644 --- a/package.json +++ b/package.json @@ -15,14 +15,13 @@ }, "dependencies": { "bittorrent-client": "^0.0.1", + "numeral": "^1.5.3", + "network-address": "0.0.4", "chalk": "^0.4.0", "clivas": "^0.1.4", "concat-stream": "^1.4.4", "inherits": "^2.0.1", - "mime": "^1.2.11", - "minimist": "^0.0.8", - "pump": "^0.3.1", - "range-parser": "^1.0.0" + "minimist": "^0.0.8" }, "devDependencies": { "tape": "2.x" diff --git a/views/torrent.html b/views/torrent.html deleted file mode 100644 index 292b4324..00000000 --- a/views/torrent.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
- -
-
-
-
- - -
- -
- Downloading from of peers - DL: /s UL: /s -
-
-
\ No newline at end of file From 7830df188978c7c6f364c2ebfa4ecae0fa65ff54 Mon Sep 17 00:00:00 2001 From: fisch0920 Date: Mon, 12 May 2014 04:38:44 -0400 Subject: [PATCH 2/3] fix for invalid torrents passed via commandline --- bin/cmd.js | 7 ++++++- index.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/cmd.js b/bin/cmd.js index 1025a7ac..bdf75588 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -87,7 +87,12 @@ client.on('error', function (err) { clivas.line('{red:error} ' + err) }) -client.add(torrentId, function (torrent) { +client.add(torrentId, function (err, torrent) { + if (err) { + clivas.line('{red:error} ' + err) + process.exit(1) + } + function updateMetadata () { if (torrent) { clivas.clear() diff --git a/index.js b/index.js index 318a76da..4a2ac451 100644 --- a/index.js +++ b/index.js @@ -60,8 +60,8 @@ WebTorrent.prototype.add = function (torrentId, cb) { // assume it's a filesystem path fs.readFile(torrentId, function (err, torrent) { if (err) { - return cb(new Error('Cannot add torrent. Require one of: magnet uri, ' + - 'info hash, torrent file, http url, or filesystem path')) + return cb(new Error('Cannot add torrent "' + torrentId + '". Torrent id must be one of: magnet uri, ' + + 'info hash, torrent file, http url, or filesystem path.')) } onTorrentId(torrent) }) From c84b50acdba65ebb6d1449cd01a58b904efbbc7b Mon Sep 17 00:00:00 2001 From: fisch0920 Date: Mon, 12 May 2014 05:17:29 -0400 Subject: [PATCH 3/3] removed unintended double-newline --- bin/cmd.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/cmd.js b/bin/cmd.js index bdf75588..01a75aff 100755 --- a/bin/cmd.js +++ b/bin/cmd.js @@ -15,7 +15,6 @@ var numeral = require('numeral') var address = require('network-address') var WebTorrent = require('../') - var TMP = os.tmp function usage () {