From 4e13fd8695b35be8d5443e5678a2872aa38b7c17 Mon Sep 17 00:00:00 2001 From: Gaspard Culis Date: Thu, 13 Oct 2022 14:54:59 +0200 Subject: [PATCH] Added some JSDoc --- lib/client.js | 36 ++++++-- lib/torrent/torrent.js | 30 +++++++ lib/tracker/tracker.js | 184 ++++++++++++++++++++++------------------- 3 files changed, 159 insertions(+), 91 deletions(-) diff --git a/lib/client.js b/lib/client.js index 67e4417..8d3f96d 100644 --- a/lib/client.js +++ b/lib/client.js @@ -8,14 +8,17 @@ var log4js = require("log4js") var LOGGER = log4js.getLogger('client.js'); +Client() + /** * Create a new torrent client. - * - * Options: - * { id: '-NT0000-' || Buffer, - * downloadPath: '.', - * portRange: { start: 6881, end: 6889 }, - * logLevel: 'TRACE' || 'DEBUG' || 'INFO' || ... } + * @param { Object } options + * @param { '-NT0000-' | Buffer} options.id + * @param { String } options.downloadPath Defaults to '.' + * @param { Object } options.portRange + * @param { number } options.portRange.start Defaults to 6881 + * @param { number } options.portRange.end Defaults to 6889 + * @param { 'TRACE' | 'DEBUG' | 'INFO'} options.logLevel Not used */ var Client = function(options) { @@ -33,9 +36,18 @@ var Client = function(options) { this.id = padId(id); } + /** + * @type { Object. } + */ this.torrents = {}; + /** + * @type { String } + */ this.downloadPath = options.downloadPath || '.'; this._server = net.createServer(this._handleConnection.bind(this)); + /** + * @type { number } + */ this.port = listen(this._server, options.portRange); this._extensions = [ @@ -45,10 +57,18 @@ var Client = function(options) { dht.init(); }; +/** + * @param { function(Torrent) } ExtensionClass + */ Client.prototype.addExtension = function(ExtensionClass) { this._extensions.push(ExtensionClass); }; +/** + * + * @param { String } url Torrent file path or Magnet URL / link + * @returns { Torrent } The added torrent + */ Client.prototype.addTorrent = function(url) { var torrent = new Torrent(this.id, this.port, this.downloadPath, url, this._extensions.slice(0)); var client = this; @@ -63,6 +83,10 @@ Client.prototype.addTorrent = function(url) { return torrent; }; +/** + * + * @param { Torrent } torrent + */ Client.prototype.removeTorrent = function(torrent) { if (this.torrents[torrent.infoHash]) { this.torrents[torrent.infoHash].stop(); diff --git a/lib/torrent/torrent.js b/lib/torrent/torrent.js index 710a581..f7d1f6f 100644 --- a/lib/torrent/torrent.js +++ b/lib/torrent/torrent.js @@ -1,4 +1,5 @@ +const Tracker = require('../tracker/tracker'); var bencode = require('../util/bencode'), util = require('util'); @@ -19,6 +20,13 @@ var BitField = require('../util/bitfield'), var LOGGER = require('log4js').getLogger('torrent.js'); +/** + * @param { String } clientId + * @param { number } clientPort + * @param { String } downloadPath + * @param { String } dataUrl + * @param { Array } extensions + */ function Torrent(clientId, clientPort, downloadPath, dataUrl, extensions) { EventEmitter.call(this); @@ -28,6 +36,10 @@ function Torrent(clientId, clientPort, downloadPath, dataUrl, extensions) { this.infoHash = null; this.name = null; + /** + * @type { {downloaded: number, downloadRate: number, uploaded: number, uploadRate: number} } + * @readOnly + */ this.stats = { downloaded: 0, downloadRate: 0, @@ -35,9 +47,18 @@ function Torrent(clientId, clientPort, downloadPath, dataUrl, extensions) { uploadRate: 0 }; + /** + * @type { Object. } + */ this.peers = {}; + /** + * @type { Array } + */ this.trackers = []; this.bitfield = null; + /** + * @type { Torrent.COMPLETE | Torrent.ERROR | Torrent.INFO_HASH | Torrent.LOADING | Torrent.PEER | Torrent.PROGRESS | Torrent.READY } + */ this.status = null; this._setStatus(Torrent.LOADING); @@ -168,16 +189,25 @@ Torrent.prototype.addPeer = function(/* peer | id, address, port */) { } }; +/** + * @param { Tracker } tracker + */ Torrent.prototype.addTracker = function(tracker) { this.trackers.push(tracker); tracker.setTorrent(this); // tracker.on(Tracker.PEER, this.addPeer.bind(this)); }; +/** + * @returns { boolean } + */ Torrent.prototype.hasMetadata = function() { return this._metadata.isComplete(); }; +/** + * @returns { boolean } + */ Torrent.prototype.isComplete = function() { return this.bitfield.cardinality() === this.bitfield.length; }; diff --git a/lib/tracker/tracker.js b/lib/tracker/tracker.js index 4ae8495..aa12e4d 100644 --- a/lib/tracker/tracker.js +++ b/lib/tracker/tracker.js @@ -1,7 +1,8 @@ var bencode = require('../util/bencode'), - protocol = require('./protocol'), - util = require('util'); +protocol = require('./protocol'), +util = require('util'), +Torrent = require('../torrent'); var EventEmitter = require('events').EventEmitter; @@ -15,111 +16,124 @@ var WAITING = 'waiting'; var ANNOUNCE_START_INTERVAL = 5; var Tracker = function(urls) { - EventEmitter.call(this); - if (!Array.isArray(urls)) { - this._urls = [urls]; - } else { - this._urls = urls; - } - // TODO: need to step through URLs as part of announce process - this.url = require('url').parse(this._urls[0]); - this.torrent = null; - this.state = STOPPED; - this.seeders = 0; - this.leechers = 0; +EventEmitter.call(this); +if (!Array.isArray(urls)) { +this._urls = [urls]; +} else { +this._urls = urls; +} +// TODO: need to step through URLs as part of announce process +this.url = require('url').parse(this._urls[0]); +/** +* @type { Torrent } +*/ +this.torrent = null; +this.state = STOPPED; +this.seeders = 0; +this.leechers = 0; }; util.inherits(Tracker, EventEmitter); +/** +* +* @param { Torrent } torrent +*/ Tracker.prototype.setTorrent = function(torrent) { - this.torrent = torrent; +this.torrent = torrent; }; +new Tracker().start + +/** +* +* @param { function(String , String, number) } callback Parameters : peer_id: String, peer_ip: String, peer_port: number +*/ Tracker.prototype.start = function(callback) { - this.callback = callback; - this._announce('started'); +this.callback = callback; +this._announce('started'); }; Tracker.prototype.stop = function() { - this._announce('stopped'); +this._announce('stopped'); }; Tracker.prototype._announce = function(event) { - - LOGGER.debug('Announce' + (event ? ' ' + event : '')); - - var handlerClass = protocol[this.url.protocol], - tracker = this; - - if (handlerClass) { - var handler = new handlerClass(); - var data = { - peer_id: this.torrent.clientId, - info_hash: this.torrent.infoHash, - port: this.torrent.clientPort - }; - this.state = CONNECTING; - handler.handle(this, data, event, function(info, error) { - if (error) { - LOGGER.warn('announce error from ' + tracker.url.href + ': ' + error.message); - tracker.state = ERROR; - tracker.errorMessage = error.message; - if (event === 'started') { - LOGGER.warn('retry announce \'started\' in ' + ANNOUNCE_START_INTERVAL + 's'); - setTimeout(function() { - tracker._announce('started'); - }, ANNOUNCE_START_INTERVAL * 1000); - } - } else { - if (info.trackerId) { - tracker.trackerId = info.trackerId; - } - tracker.state = WAITING; - if (event === 'started') { - var interval = info.interval; - if (tracker.timeoutId) { - clearInterval(tracker.timeoutId); - } - if (interval) { - tracker.timeoutId = setInterval(function() { - tracker._announce(null); - }, interval * 1000); - } - } else if (event === 'stopped') { - clearInterval(tracker.timeoutId); - delete tracker.timeoutId; - tracker.state = STOPPED; - } + +LOGGER.debug('Announce' + (event ? ' ' + event : '')); + +var handlerClass = protocol[this.url.protocol], + tracker = this; + +if (handlerClass) { +var handler = new handlerClass(); +var data = { + peer_id: this.torrent.clientId, + info_hash: this.torrent.infoHash, + port: this.torrent.clientPort +}; +this.state = CONNECTING; +handler.handle(this, data, event, function(info, error) { + if (error) { + LOGGER.warn('announce error from ' + tracker.url.href + ': ' + error.message); + tracker.state = ERROR; + tracker.errorMessage = error.message; + if (event === 'started') { + LOGGER.warn('retry announce \'started\' in ' + ANNOUNCE_START_INTERVAL + 's'); + setTimeout(function() { + tracker._announce('started'); + }, ANNOUNCE_START_INTERVAL * 1000); + } + } else { + if (info.trackerId) { + tracker.trackerId = info.trackerId; + } + tracker.state = WAITING; + if (event === 'started') { + var interval = info.interval; + if (tracker.timeoutId) { + clearInterval(tracker.timeoutId); } - tracker._updateInfo(info); - }); + if (interval) { + tracker.timeoutId = setInterval(function() { + tracker._announce(null); + }, interval * 1000); + } + } else if (event === 'stopped') { + clearInterval(tracker.timeoutId); + delete tracker.timeoutId; + tracker.state = STOPPED; + } } + tracker._updateInfo(info); +}); +} }; Tracker.prototype._updateInfo = function(data) { - LOGGER.debug('Updating details from tracker. ' + (data && data.peers ? data.peers.length : 0) + ' new peers'); - if (data) { - this.seeders = data.seeders || 0; - this.leechers = data.leechers || 0; - if (data.peers) { - for (var i = 0; i < data.peers.length; i++) { - var peer = data.peers[i]; - this.callback(peer.peer_id, peer.ip, peer.port); - } - } - this.emit('updated'); +LOGGER.debug('Updating details from tracker. ' + (data && data.peers ? data.peers.length : 0) + ' new peers'); +if (data) { +this.seeders = data.seeders || 0; +this.leechers = data.leechers || 0; +if (data.peers) { + for (var i = 0; i < data.peers.length; i++) { + var peer = data.peers[i]; + this.callback(peer.peer_id, peer.ip, peer.port); } +} +this.emit('updated'); +} }; Tracker.createTrackers = function(announce, announceList) { - var trackers = []; - if (announceList) { - announceList.forEach(function(announce) { - trackers.push(new Tracker(announce)); - }); - } else { - trackers.push(new Tracker(announce)); - } - return trackers; +var trackers = []; +if (announceList) { +announceList.forEach(function(announce) { + trackers.push(new Tracker(announce)); +}); +} else { +trackers.push(new Tracker(announce)); +} +return trackers; }; module.exports = Tracker;