1+ /* vim: set ts=2 sw=2 sts=2 et: */
2+
13exports . Client = Client
24exports . Server = Server
35
@@ -20,65 +22,68 @@ var ACTIONS = { CONNECT: 0, ANNOUNCE: 1 }
2022var EVENTS = { completed : 1 , started : 2 , stopped : 3 }
2123var MAX_UINT = 4294967295
2224
23- inherits ( Client , EventEmitter )
25+ inherits ( Tracker , EventEmitter )
2426
25- function Client ( peerId , port , torrent , opts ) {
27+ /**
28+ * An individual torrent tracker
29+ *
30+ * @param {Client } client parent bittorrent tracker client
31+ * @param {string } announceUrl announce url of tracker
32+ * @param {Number } interval interval in ms to send announce requests to the tracker
33+ * @param {Object } opts optional options
34+ */
35+ function Tracker ( client , announceUrl , interval , opts ) {
2636 var self = this
27- if ( ! ( self instanceof Client ) ) return new Client ( peerId , port , torrent , opts )
37+ if ( ! ( self instanceof Tracker ) ) return new Tracker ( client , announceUrl , interval , opts )
2838 EventEmitter . call ( self )
2939 self . _opts = opts || { }
30-
31- // required
32- self . _peerId = Buffer . isBuffer ( peerId )
33- ? peerId
34- : new Buffer ( peerId , 'utf8' )
35- self . _port = port
36- self . _infoHash = Buffer . isBuffer ( torrent . infoHash )
37- ? torrent . infoHash
38- : new Buffer ( torrent . infoHash , 'hex' )
39- self . _torrentLength = torrent . length
40- self . _announce = torrent . announce
41-
42- // optional
43- self . _numWant = self . _opts . numWant || 80
44- self . _intervalMs = self . _opts . interval || ( 30 * 60 * 1000 ) // default: 30 minutes
45-
40+
41+ self . client = client
42+
43+ self . _announceUrl = announceUrl
44+ self . _intervalMs = interval
4645 self . _interval = null
46+
47+ if ( self . _announceUrl . indexOf ( 'udp:' ) === 0 ) {
48+ self . _requestImpl = self . _requestUdp . bind ( self )
49+ } else {
50+ self . _requestImpl = self . _requestHttp . bind ( self )
51+ }
4752}
4853
49- Client . prototype . start = function ( opts ) {
54+ Tracker . prototype . start = function ( opts ) {
5055 var self = this
5156 opts = opts || { }
5257 opts . event = 'started'
5358 self . _request ( opts )
54-
59+
5560 self . setInterval ( self . _intervalMs ) // start announcing on intervals
5661}
5762
58- Client . prototype . stop = function ( opts ) {
63+ Tracker . prototype . stop = function ( opts ) {
5964 var self = this
6065 opts = opts || { }
6166 opts . event = 'stopped'
6267 self . _request ( opts )
63-
68+
6469 self . setInterval ( 0 ) // stop announcing on intervals
6570}
6671
67- Client . prototype . complete = function ( opts ) {
72+ Tracker . prototype . complete = function ( opts ) {
6873 var self = this
6974 opts = opts || { }
7075 opts . event = 'completed'
7176 opts . downloaded = self . _torrentLength
7277 self . _request ( opts )
7378}
7479
75- Client . prototype . update = function ( opts ) {
80+ Tracker . prototype . update = function ( opts ) {
7681 var self = this
7782 opts = opts || { }
7883 self . _request ( opts )
7984}
8085
81- Client . prototype . setInterval = function ( intervalMs ) {
86+ Tracker . prototype . setInterval = function ( intervalMs ) {
8287 var self = this
8388 if ( self . _interval ) {
8489 clearInterval ( self . _interval )
@@ -93,59 +98,53 @@ Client.prototype.setInterval = function (intervalMs) {
9398/**
9499 * Send a request to the tracker
95100 */
96- Client . prototype . _request = function ( opts ) {
101+ Tracker . prototype . _request = function ( opts ) {
97102 var self = this
98103 opts = extend ( {
99- info_hash : bytewiseEncodeURIComponent ( self . _infoHash ) ,
100- peer_id : bytewiseEncodeURIComponent ( self . _peerId ) ,
101- port : self . _port ,
102- left : self . _torrentLength - ( opts . downloaded || 0 ) ,
104+ info_hash : bytewiseEncodeURIComponent ( self . client . _infoHash ) ,
105+ peer_id : bytewiseEncodeURIComponent ( self . client . _peerId ) ,
106+ port : self . client . _port ,
107+ left : self . client . _torrentLength - ( opts . downloaded || 0 ) ,
103108 compact : 1 ,
104- numwant : self . _numWant ,
109+ numwant : self . client . _numWant ,
105110 uploaded : 0 , // default, user should provide real value
106111 downloaded : 0 // default, user should provide real value
107112 } , opts )
108-
113+
109114 if ( self . _trackerId ) {
110115 opts . trackerid = self . _trackerId
111116 }
112-
113- self . _announce . forEach ( function ( announceUrl ) {
114- if ( announceUrl . indexOf ( 'udp:' ) === 0 ) {
115- self . _requestUdp ( announceUrl , opts )
116- } else {
117- self . _requestHttp ( announceUrl , opts )
118- }
119- } )
117+
118+ self . _requestImpl ( opts )
120119}
121120
122- Client . prototype . _requestHttp = function ( announceUrl , opts ) {
121+ Tracker . prototype . _requestHttp = function ( opts ) {
123122 var self = this
124- var fullUrl = announceUrl + '?' + querystring . stringify ( opts )
123+ var fullUrl = self . _announceUrl + '?' + querystring . stringify ( opts )
125124
126125 var req = http . get ( fullUrl , function ( res ) {
127126 var data = ''
128127 if ( res . statusCode !== 200 ) {
129128 res . resume ( ) // consume the whole stream
130- self . emit ( 'error' , new Error ( 'Invalid response code ' + res . statusCode + ' from tracker' ) )
129+ self . client . emit ( 'error' , new Error ( 'Invalid response code ' + res . statusCode + ' from tracker' ) )
131130 return
132131 }
133132 res . on ( 'data' , function ( chunk ) {
134133 data += chunk
135134 } )
136135 res . on ( 'end' , function ( ) {
137- self . _handleResponse ( data , announceUrl )
136+ self . _handleResponse ( data )
138137 } )
139138 } )
140139
141140 req . on ( 'error' , function ( err ) {
142- self . emit ( 'error' , err )
141+ self . client . emit ( 'error' , err )
143142 } )
144143}
145144
146- Client . prototype . _requestUdp = function ( announceUrl , opts ) {
145+ Tracker . prototype . _requestUdp = function ( opts ) {
147146 var self = this
148- var parsedUrl = url . parse ( announceUrl )
147+ var parsedUrl = url . parse ( self . _announceUrl )
149148 var socket = dgram . createSocket ( 'udp4' )
150149 var transactionId = new Buffer ( hat ( 32 ) , 'hex' )
151150
@@ -158,7 +157,7 @@ Client.prototype._requestUdp = function (announceUrl, opts) {
158157 }
159158
160159 function error ( message ) {
161- self . emit ( 'error' , new Error ( message ) )
160+ self . client . emit ( 'error' , new Error ( message ) )
162161 socket . close ( )
163162 clearTimeout ( timeout )
164163 }
@@ -187,22 +186,21 @@ Client.prototype._requestUdp = function (announceUrl, opts) {
187186 return error ( new Error ( 'invalid announce message' ) )
188187 }
189188
190- // TODO: this should be stored per tracker, not globally for all trackers
191189 var interval = message . readUInt32BE ( 8 )
192190 if ( interval && ! self . _opts . interval && self . _intervalMs !== 0 ) {
193191 // use the interval the tracker recommends, UNLESS the user manually specifies an
194192 // interval they want to use
195193 self . setInterval ( interval * 1000 )
196194 }
197195
198- self . emit ( 'update' , {
199- announce : announceUrl ,
196+ self . client . emit ( 'update' , {
197+ announce : self . _announceUrl ,
200198 complete : message . readUInt32BE ( 16 ) ,
201199 incomplete : message . readUInt32BE ( 12 )
202200 } )
203201
204202 compact2string . multi ( message . slice ( 20 ) ) . forEach ( function ( addr ) {
205- self . emit ( 'peer' , addr )
203+ self . client . emit ( 'peer' , addr )
206204 } )
207205
208206 clearTimeout ( timeout )
@@ -225,16 +223,16 @@ Client.prototype._requestUdp = function (announceUrl, opts) {
225223 connectionId ,
226224 toUInt32 ( ACTIONS . ANNOUNCE ) ,
227225 transactionId ,
228- self . _infoHash ,
229- self . _peerId ,
226+ self . client . _infoHash ,
227+ self . client . _peerId ,
230228 toUInt64 ( opts . downloaded || 0 ) ,
231229 toUInt64 ( opts . left || 0 ) ,
232230 toUInt64 ( opts . uploaded || 0 ) ,
233231 toUInt32 ( EVENTS [ opts . event ] || 0 ) ,
234232 toUInt32 ( 0 ) , // ip address (optional)
235233 toUInt32 ( 0 ) , // key (optional)
236- toUInt32 ( self . _numWant ) ,
237- toUInt16 ( self . _port || 0 )
234+ toUInt32 ( self . client . _numWant ) ,
235+ toUInt16 ( self . client . _port || 0 )
238236 ] ) )
239237 }
240238
@@ -245,59 +243,132 @@ Client.prototype._requestUdp = function (announceUrl, opts) {
245243 ] ) )
246244}
247245
248- Client . prototype . _handleResponse = function ( data , announceUrl ) {
246+ Tracker . prototype . _handleResponse = function ( data ) {
249247 var self = this
250248
251249 try {
252250 data = bncode . decode ( data )
253251 } catch ( err ) {
254- return self . emit ( 'error' , new Error ( 'Error decoding tracker response: ' + err . message ) )
252+ return self . client . emit ( 'error' , new Error ( 'Error decoding tracker response: ' + err . message ) )
255253 }
256254 var failure = data [ 'failure reason' ]
257255 if ( failure ) {
258- return self . emit ( 'error' , new Error ( failure ) )
256+ return self . client . emit ( 'error' , new Error ( failure ) )
259257 }
260258
261259 var warning = data [ 'warning message' ]
262260 if ( warning ) {
263- self . emit ( 'warning' , warning ) ;
261+ self . client . emit ( 'warning' , warning ) ;
264262 }
265263
266- // TODO: this should be stored per tracker, not globally for all trackers
267264 var interval = data . interval || data [ 'min interval' ]
268265 if ( interval && ! self . _opts . interval && self . _intervalMs !== 0 ) {
269266 // use the interval the tracker recommends, UNLESS the user manually specifies an
270267 // interval they want to use
271268 self . setInterval ( interval * 1000 )
272269 }
273270
274- // TODO: this should be stored per tracker, not globally for all trackers
275271 var trackerId = data [ 'tracker id' ]
276272 if ( trackerId ) {
277273 // If absent, do not discard previous trackerId value
278274 self . _trackerId = trackerId
279275 }
280276
281- self . emit ( 'update' , {
282- announce : announceUrl ,
277+ self . client . emit ( 'update' , {
278+ announce : self . _announceUrl ,
283279 complete : data . complete ,
284280 incomplete : data . incomplete
285281 } )
286282
287283 if ( Buffer . isBuffer ( data . peers ) ) {
288284 // tracker returned compact response
289285 compact2string . multi ( data . peers ) . forEach ( function ( addr ) {
290- self . emit ( 'peer' , addr )
286+ self . client . emit ( 'peer' , addr )
291287 } )
292288 } else if ( Array . isArray ( data . peers ) ) {
293289 // tracker returned normal response
294290 data . peers . forEach ( function ( peer ) {
295291 var ip = peer . ip
296- self . emit ( 'peer' , ip [ 0 ] + '.' + ip [ 1 ] + '.' + ip [ 2 ] + '.' + ip [ 3 ] + ':' + peer . port )
292+ self . client . emit ( 'peer' , ip [ 0 ] + '.' + ip [ 1 ] + '.' + ip [ 2 ] + '.' + ip [ 3 ] + ':' + peer . port )
297293 } )
298294 }
299295}
300296
297+ inherits ( Client , EventEmitter )
298+
299+ /**
300+ * A Client manages tracker connections for a torrent.
301+ *
302+ * @param {string } peerId this peer's id
303+ * @param {Number } port port number that the client is listening on
304+ * @param {Object } torrent parsed torrent
305+ * @param {Object } opts optional options
306+ * @param {Number } opts.numWant number of peers to request
307+ * @param {Number } opts.interval interval in ms to send announce requests to the tracker
308+ */
309+ function Client ( peerId , port , torrent , opts ) {
310+ var self = this
311+ if ( ! ( self instanceof Client ) ) return new Client ( peerId , port , torrent , opts )
312+ EventEmitter . call ( self )
313+ self . _opts = opts || { }
314+
315+ // required
316+ self . _peerId = Buffer . isBuffer ( peerId )
317+ ? peerId
318+ : new Buffer ( peerId , 'utf8' )
319+ self . _port = port
320+ self . _infoHash = Buffer . isBuffer ( torrent . infoHash )
321+ ? torrent . infoHash
322+ : new Buffer ( torrent . infoHash , 'hex' )
323+ self . _torrentLength = torrent . length
324+ self . _announce = torrent . announce
325+
326+ // optional
327+ self . _numWant = self . _opts . numWant || 80
328+ self . _intervalMs = self . _opts . interval || ( 30 * 60 * 1000 ) // default: 30 minutes
329+
330+ self . _trackers = torrent . announce . map ( function ( announceUrl ) {
331+ return Tracker ( self , announceUrl , self . _intervalMs , self . _opts )
332+ } )
333+ }
334+
335+ Client . prototype . start = function ( opts ) {
336+ var self = this
337+ self . _trackers . forEach ( function ( tracker ) {
338+ tracker . start ( opts )
339+ } )
340+ }
341+
342+ Client . prototype . stop = function ( opts ) {
343+ var self = this
344+ self . _trackers . forEach ( function ( tracker ) {
345+ tracker . stop ( opts )
346+ } )
347+ }
348+
349+ Client . prototype . complete = function ( opts ) {
350+ var self = this
351+ self . _trackers . forEach ( function ( tracker ) {
352+ tracker . complete ( opts )
353+ } )
354+ }
355+
356+ Client . prototype . update = function ( opts ) {
357+ var self = this
358+ self . _trackers . forEach ( function ( tracker ) {
359+ tracker . update ( opts )
360+ } )
361+ }
362+
363+ Client . prototype . setInterval = function ( intervalMs ) {
364+ var self = this
365+ self . _intervalMs = intervalMs
366+
367+ self . _trackers . forEach ( function ( tracker ) {
368+ tracker . setInterval ( intervalMs )
369+ } )
370+ }
371+
301372inherits ( Server , EventEmitter )
302373
303374function Server ( opts ) {
@@ -452,7 +523,7 @@ Server.prototype._onHttpRequest = function (req, res) {
452523 return error ( 'unexpected `update` event from peer that is not in swarm' )
453524 }
454525
455- self . emit ( 'update' , addr , params )
526+ self . client . emit ( 'update' , addr , params )
456527 break
457528
458529 default :
@@ -476,6 +547,7 @@ Server.prototype._onHttpRequest = function (req, res) {
476547 }
477548
478549 res . end ( bncode . encode ( response ) )
550+ } else { // TODO: handle unofficial scrape messages
479551 }
480552}
481553
0 commit comments