murder 0.0.0.pre

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. data/.gitignore +1 -0
  2. data/LICENSE +17 -0
  3. data/README +224 -0
  4. data/Rakefile +52 -0
  5. data/VERSION +1 -0
  6. data/dist/BitTornado/BT1/Choker.py +128 -0
  7. data/dist/BitTornado/BT1/Connecter.py +288 -0
  8. data/dist/BitTornado/BT1/Downloader.py +594 -0
  9. data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
  10. data/dist/BitTornado/BT1/Encrypter.py +333 -0
  11. data/dist/BitTornado/BT1/FileSelector.py +245 -0
  12. data/dist/BitTornado/BT1/Filter.py +12 -0
  13. data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
  14. data/dist/BitTornado/BT1/NatCheck.py +95 -0
  15. data/dist/BitTornado/BT1/PiecePicker.py +320 -0
  16. data/dist/BitTornado/BT1/Rerequester.py +426 -0
  17. data/dist/BitTornado/BT1/Statistics.py +177 -0
  18. data/dist/BitTornado/BT1/Storage.py +584 -0
  19. data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
  20. data/dist/BitTornado/BT1/StreamCheck.py +135 -0
  21. data/dist/BitTornado/BT1/T2T.py +193 -0
  22. data/dist/BitTornado/BT1/Uploader.py +145 -0
  23. data/dist/BitTornado/BT1/__init__.py +1 -0
  24. data/dist/BitTornado/BT1/btformats.py +100 -0
  25. data/dist/BitTornado/BT1/fakeopen.py +89 -0
  26. data/dist/BitTornado/BT1/makemetafile.py +263 -0
  27. data/dist/BitTornado/BT1/track.py +1067 -0
  28. data/dist/BitTornado/ConfigDir.py +401 -0
  29. data/dist/BitTornado/ConfigReader.py +1068 -0
  30. data/dist/BitTornado/ConnChoice.py +31 -0
  31. data/dist/BitTornado/CreateIcons.py +105 -0
  32. data/dist/BitTornado/CurrentRateMeasure.py +37 -0
  33. data/dist/BitTornado/HTTPHandler.py +167 -0
  34. data/dist/BitTornado/PSYCO.py +5 -0
  35. data/dist/BitTornado/RateLimiter.py +153 -0
  36. data/dist/BitTornado/RateMeasure.py +75 -0
  37. data/dist/BitTornado/RawServer.py +195 -0
  38. data/dist/BitTornado/ServerPortHandler.py +188 -0
  39. data/dist/BitTornado/SocketHandler.py +375 -0
  40. data/dist/BitTornado/__init__.py +63 -0
  41. data/dist/BitTornado/bencode.py +319 -0
  42. data/dist/BitTornado/bitfield.py +162 -0
  43. data/dist/BitTornado/clock.py +27 -0
  44. data/dist/BitTornado/download_bt1.py +882 -0
  45. data/dist/BitTornado/inifile.py +169 -0
  46. data/dist/BitTornado/iprangeparse.py +194 -0
  47. data/dist/BitTornado/launchmanycore.py +381 -0
  48. data/dist/BitTornado/natpunch.py +254 -0
  49. data/dist/BitTornado/parseargs.py +137 -0
  50. data/dist/BitTornado/parsedir.py +150 -0
  51. data/dist/BitTornado/piecebuffer.py +86 -0
  52. data/dist/BitTornado/selectpoll.py +109 -0
  53. data/dist/BitTornado/subnetparse.py +218 -0
  54. data/dist/BitTornado/torrentlistparse.py +38 -0
  55. data/dist/BitTornado/zurllib.py +100 -0
  56. data/dist/murder_client.py +291 -0
  57. data/dist/murder_make_torrent.py +46 -0
  58. data/dist/murder_tracker.py +28 -0
  59. data/doc/examples/Capfile +28 -0
  60. data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
  61. data/lib/murder.rb +43 -0
  62. data/lib/murder/admin.rb +47 -0
  63. data/lib/murder/murder.rb +121 -0
  64. data/murder.gemspec +101 -0
  65. metadata +129 -0
@@ -0,0 +1,594 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from BitTornado.CurrentRateMeasure import Measure
5
+ from BitTornado.bitfield import Bitfield
6
+ from random import shuffle
7
+ from BitTornado.clock import clock
8
+ try:
9
+ True
10
+ except:
11
+ True = 1
12
+ False = 0
13
+
14
+ EXPIRE_TIME = 60 * 60
15
+
16
+ class PerIPStats:
17
+ def __init__(self, ip):
18
+ self.numgood = 0
19
+ self.bad = {}
20
+ self.numconnections = 0
21
+ self.lastdownload = None
22
+ self.peerid = None
23
+
24
+ class BadDataGuard:
25
+ def __init__(self, download):
26
+ self.download = download
27
+ self.ip = download.ip
28
+ self.downloader = download.downloader
29
+ self.stats = self.downloader.perip[self.ip]
30
+ self.lastindex = None
31
+
32
+ def failed(self, index, bump = False):
33
+ self.stats.bad.setdefault(index, 0)
34
+ self.downloader.gotbaddata[self.ip] = 1
35
+ self.stats.bad[index] += 1
36
+ if len(self.stats.bad) > 1:
37
+ if self.download is not None:
38
+ self.downloader.try_kick(self.download)
39
+ elif self.stats.numconnections == 1 and self.stats.lastdownload is not None:
40
+ self.downloader.try_kick(self.stats.lastdownload)
41
+ if len(self.stats.bad) >= 3 and len(self.stats.bad) > int(self.stats.numgood/30):
42
+ self.downloader.try_ban(self.ip)
43
+ elif bump:
44
+ self.downloader.picker.bump(index)
45
+
46
+ def good(self, index):
47
+ # lastindex is a hack to only increase numgood by one for each good
48
+ # piece, however many chunks come from the connection(s) from this IP
49
+ if index != self.lastindex:
50
+ self.stats.numgood += 1
51
+ self.lastindex = index
52
+
53
+ class SingleDownload:
54
+ def __init__(self, downloader, connection):
55
+ self.downloader = downloader
56
+ self.connection = connection
57
+ self.choked = True
58
+ self.interested = False
59
+ self.active_requests = []
60
+ self.measure = Measure(downloader.max_rate_period)
61
+ self.peermeasure = Measure(downloader.max_rate_period)
62
+ self.have = Bitfield(downloader.numpieces)
63
+ self.last = -1000
64
+ self.last2 = -1000
65
+ self.example_interest = None
66
+ self.backlog = 2
67
+ self.ip = connection.get_ip()
68
+ self.guard = BadDataGuard(self)
69
+
70
+ def _backlog(self, just_unchoked):
71
+ self.backlog = min(
72
+ 2+int(4*self.measure.get_rate()/self.downloader.chunksize),
73
+ (2*just_unchoked)+self.downloader.queue_limit() )
74
+ if self.backlog > 50:
75
+ self.backlog = max(50, self.backlog * 0.075)
76
+ return self.backlog
77
+
78
+ def disconnected(self):
79
+ self.downloader.lost_peer(self)
80
+ if self.have.complete():
81
+ self.downloader.picker.lost_seed()
82
+ else:
83
+ for i in xrange(len(self.have)):
84
+ if self.have[i]:
85
+ self.downloader.picker.lost_have(i)
86
+ if self.have.complete() and self.downloader.storage.is_endgame():
87
+ self.downloader.add_disconnected_seed(self.connection.get_readable_id())
88
+ self._letgo()
89
+ self.guard.download = None
90
+
91
+ def _letgo(self):
92
+ if self.downloader.queued_out.has_key(self):
93
+ del self.downloader.queued_out[self]
94
+ if not self.active_requests:
95
+ return
96
+ if self.downloader.endgamemode:
97
+ self.active_requests = []
98
+ return
99
+ lost = {}
100
+ for index, begin, length in self.active_requests:
101
+ self.downloader.storage.request_lost(index, begin, length)
102
+ lost[index] = 1
103
+ lost = lost.keys()
104
+ self.active_requests = []
105
+ if self.downloader.paused:
106
+ return
107
+ ds = [d for d in self.downloader.downloads if not d.choked]
108
+ shuffle(ds)
109
+ for d in ds:
110
+ d._request_more()
111
+ for d in self.downloader.downloads:
112
+ if d.choked and not d.interested:
113
+ for l in lost:
114
+ if d.have[l] and self.downloader.storage.do_I_have_requests(l):
115
+ d.send_interested()
116
+ break
117
+
118
+ def got_choke(self):
119
+ if not self.choked:
120
+ self.choked = True
121
+ self._letgo()
122
+
123
+ def got_unchoke(self):
124
+ if self.choked:
125
+ self.choked = False
126
+ if self.interested:
127
+ self._request_more(new_unchoke = True)
128
+ self.last2 = clock()
129
+
130
+ def is_choked(self):
131
+ return self.choked
132
+
133
+ def is_interested(self):
134
+ return self.interested
135
+
136
+ def send_interested(self):
137
+ if not self.interested:
138
+ self.interested = True
139
+ self.connection.send_interested()
140
+ if not self.choked:
141
+ self.last2 = clock()
142
+
143
+ def send_not_interested(self):
144
+ if self.interested:
145
+ self.interested = False
146
+ self.connection.send_not_interested()
147
+
148
+ def got_piece(self, index, begin, piece):
149
+ length = len(piece)
150
+ try:
151
+ self.active_requests.remove((index, begin, length))
152
+ except ValueError:
153
+ self.downloader.discarded += length
154
+ return False
155
+ if self.downloader.endgamemode:
156
+ self.downloader.all_requests.remove((index, begin, length))
157
+ self.last = clock()
158
+ self.last2 = clock()
159
+ self.measure.update_rate(length)
160
+ self.downloader.measurefunc(length)
161
+ if not self.downloader.storage.piece_came_in(index, begin, piece, self.guard):
162
+ self.downloader.piece_flunked(index)
163
+ return False
164
+ if self.downloader.storage.do_I_have(index):
165
+ self.downloader.picker.complete(index)
166
+ if self.downloader.endgamemode:
167
+ for d in self.downloader.downloads:
168
+ if d is not self:
169
+ if d.interested:
170
+ if d.choked:
171
+ assert not d.active_requests
172
+ d.fix_download_endgame()
173
+ else:
174
+ try:
175
+ d.active_requests.remove((index, begin, length))
176
+ except ValueError:
177
+ continue
178
+ d.connection.send_cancel(index, begin, length)
179
+ d.fix_download_endgame()
180
+ else:
181
+ assert not d.active_requests
182
+ self._request_more()
183
+ self.downloader.check_complete(index)
184
+ return self.downloader.storage.do_I_have(index)
185
+
186
+ def _request_more(self, new_unchoke = False):
187
+ assert not self.choked
188
+ if self.downloader.endgamemode:
189
+ self.fix_download_endgame(new_unchoke)
190
+ return
191
+ if self.downloader.paused:
192
+ return
193
+ if len(self.active_requests) >= self._backlog(new_unchoke):
194
+ if not (self.active_requests or self.backlog):
195
+ self.downloader.queued_out[self] = 1
196
+ return
197
+ lost_interests = []
198
+ while len(self.active_requests) < self.backlog:
199
+ interest = self.downloader.picker.next(self.have,
200
+ self.downloader.storage.do_I_have_requests,
201
+ self.downloader.too_many_partials())
202
+ if interest is None:
203
+ break
204
+ self.example_interest = interest
205
+ self.send_interested()
206
+ loop = True
207
+ while len(self.active_requests) < self.backlog and loop:
208
+ begin, length = self.downloader.storage.new_request(interest)
209
+ self.downloader.picker.requested(interest)
210
+ self.active_requests.append((interest, begin, length))
211
+ self.connection.send_request(interest, begin, length)
212
+ self.downloader.chunk_requested(length)
213
+ if not self.downloader.storage.do_I_have_requests(interest):
214
+ loop = False
215
+ lost_interests.append(interest)
216
+ if not self.active_requests:
217
+ self.send_not_interested()
218
+ if lost_interests:
219
+ for d in self.downloader.downloads:
220
+ if d.active_requests or not d.interested:
221
+ continue
222
+ if d.example_interest is not None and self.downloader.storage.do_I_have_requests(d.example_interest):
223
+ continue
224
+ for lost in lost_interests:
225
+ if d.have[lost]:
226
+ break
227
+ else:
228
+ continue
229
+ interest = self.downloader.picker.next(d.have,
230
+ self.downloader.storage.do_I_have_requests,
231
+ self.downloader.too_many_partials())
232
+ if interest is None:
233
+ d.send_not_interested()
234
+ else:
235
+ d.example_interest = interest
236
+ if self.downloader.storage.is_endgame():
237
+ self.downloader.start_endgame()
238
+
239
+
240
+ def fix_download_endgame(self, new_unchoke = False):
241
+ if self.downloader.paused:
242
+ return
243
+ if len(self.active_requests) >= self._backlog(new_unchoke):
244
+ if not (self.active_requests or self.backlog) and not self.choked:
245
+ self.downloader.queued_out[self] = 1
246
+ return
247
+ want = [a for a in self.downloader.all_requests if self.have[a[0]] and a not in self.active_requests]
248
+ if not (self.active_requests or want):
249
+ self.send_not_interested()
250
+ return
251
+ if want:
252
+ self.send_interested()
253
+ if self.choked:
254
+ return
255
+ shuffle(want)
256
+ del want[self.backlog - len(self.active_requests):]
257
+ self.active_requests.extend(want)
258
+ for piece, begin, length in want:
259
+ self.connection.send_request(piece, begin, length)
260
+ self.downloader.chunk_requested(length)
261
+
262
+ def got_have(self, index):
263
+ if index == self.downloader.numpieces-1:
264
+ self.downloader.totalmeasure.update_rate(self.downloader.storage.total_length-(self.downloader.numpieces-1)*self.downloader.storage.piece_length)
265
+ self.peermeasure.update_rate(self.downloader.storage.total_length-(self.downloader.numpieces-1)*self.downloader.storage.piece_length)
266
+ else:
267
+ self.downloader.totalmeasure.update_rate(self.downloader.storage.piece_length)
268
+ self.peermeasure.update_rate(self.downloader.storage.piece_length)
269
+ if not self.have[index]:
270
+ self.have[index] = True
271
+ self.downloader.picker.got_have(index)
272
+ if self.have.complete():
273
+ self.downloader.picker.became_seed()
274
+ if self.downloader.storage.am_I_complete():
275
+ self.downloader.add_disconnected_seed(self.connection.get_readable_id())
276
+ self.connection.close()
277
+ elif self.downloader.endgamemode:
278
+ self.fix_download_endgame()
279
+ elif ( not self.downloader.paused
280
+ and not self.downloader.picker.is_blocked(index)
281
+ and self.downloader.storage.do_I_have_requests(index) ):
282
+ if not self.choked:
283
+ self._request_more()
284
+ else:
285
+ self.send_interested()
286
+ return self.have.complete()
287
+
288
+ def _check_interests(self):
289
+ if self.interested or self.downloader.paused:
290
+ return
291
+ for i in xrange(len(self.have)):
292
+ if ( self.have[i] and not self.downloader.picker.is_blocked(i)
293
+ and ( self.downloader.endgamemode
294
+ or self.downloader.storage.do_I_have_requests(i) ) ):
295
+ self.send_interested()
296
+ return
297
+
298
+ def got_have_bitfield(self, have):
299
+ if self.downloader.storage.am_I_complete() and have.complete():
300
+ if self.downloader.super_seeding:
301
+ self.connection.send_bitfield(have.tostring()) # be nice, show you're a seed too
302
+ self.connection.close()
303
+ self.downloader.add_disconnected_seed(self.connection.get_readable_id())
304
+ return False
305
+ self.have = have
306
+ if have.complete():
307
+ self.downloader.picker.got_seed()
308
+ else:
309
+ for i in xrange(len(have)):
310
+ if have[i]:
311
+ self.downloader.picker.got_have(i)
312
+ if self.downloader.endgamemode and not self.downloader.paused:
313
+ for piece, begin, length in self.downloader.all_requests:
314
+ if self.have[piece]:
315
+ self.send_interested()
316
+ break
317
+ else:
318
+ self._check_interests()
319
+ return have.complete()
320
+
321
+ def get_rate(self):
322
+ return self.measure.get_rate()
323
+
324
+ def is_snubbed(self):
325
+ if ( self.interested and not self.choked
326
+ and clock() - self.last2 > self.downloader.snub_time ):
327
+ for index, begin, length in self.active_requests:
328
+ self.connection.send_cancel(index, begin, length)
329
+ self.got_choke() # treat it just like a choke
330
+ return clock() - self.last > self.downloader.snub_time
331
+
332
+
333
+ class Downloader:
334
+ def __init__(self, storage, picker, backlog, max_rate_period,
335
+ numpieces, chunksize, measurefunc, snub_time,
336
+ kickbans_ok, kickfunc, banfunc):
337
+ self.storage = storage
338
+ self.picker = picker
339
+ self.backlog = backlog
340
+ self.max_rate_period = max_rate_period
341
+ self.measurefunc = measurefunc
342
+ self.totalmeasure = Measure(max_rate_period*storage.piece_length/storage.request_size)
343
+ self.numpieces = numpieces
344
+ self.chunksize = chunksize
345
+ self.snub_time = snub_time
346
+ self.kickfunc = kickfunc
347
+ self.banfunc = banfunc
348
+ self.disconnectedseeds = {}
349
+ self.downloads = []
350
+ self.perip = {}
351
+ self.gotbaddata = {}
352
+ self.kicked = {}
353
+ self.banned = {}
354
+ self.kickbans_ok = kickbans_ok
355
+ self.kickbans_halted = False
356
+ self.super_seeding = False
357
+ self.endgamemode = False
358
+ self.endgame_queued_pieces = []
359
+ self.all_requests = []
360
+ self.discarded = 0L
361
+ # self.download_rate = 25000 # 25K/s test rate
362
+ self.download_rate = 0
363
+ self.bytes_requested = 0
364
+ self.last_time = clock()
365
+ self.queued_out = {}
366
+ self.requeueing = False
367
+ self.paused = False
368
+
369
+ def set_download_rate(self, rate):
370
+ self.download_rate = rate * 1000
371
+ self.bytes_requested = 0
372
+
373
+ def queue_limit(self):
374
+ if not self.download_rate:
375
+ return 10e10 # that's a big queue!
376
+ t = clock()
377
+ self.bytes_requested -= (t - self.last_time) * self.download_rate
378
+ self.last_time = t
379
+ if not self.requeueing and self.queued_out and self.bytes_requested < 0:
380
+ self.requeueing = True
381
+ q = self.queued_out.keys()
382
+ shuffle(q)
383
+ self.queued_out = {}
384
+ for d in q:
385
+ d._request_more()
386
+ self.requeueing = False
387
+ if -self.bytes_requested > 5*self.download_rate:
388
+ self.bytes_requested = -5*self.download_rate
389
+ return max(int(-self.bytes_requested/self.chunksize),0)
390
+
391
+ def chunk_requested(self, size):
392
+ self.bytes_requested += size
393
+
394
+ external_data_received = chunk_requested
395
+
396
+ def make_download(self, connection):
397
+ ip = connection.get_ip()
398
+ if self.perip.has_key(ip):
399
+ perip = self.perip[ip]
400
+ else:
401
+ perip = self.perip.setdefault(ip, PerIPStats(ip))
402
+ perip.peerid = connection.get_readable_id()
403
+ perip.numconnections += 1
404
+ d = SingleDownload(self, connection)
405
+ perip.lastdownload = d
406
+ self.downloads.append(d)
407
+ return d
408
+
409
+ def piece_flunked(self, index):
410
+ if self.paused:
411
+ return
412
+ if self.endgamemode:
413
+ if self.downloads:
414
+ while self.storage.do_I_have_requests(index):
415
+ nb, nl = self.storage.new_request(index)
416
+ self.all_requests.append((index, nb, nl))
417
+ for d in self.downloads:
418
+ d.fix_download_endgame()
419
+ return
420
+ self._reset_endgame()
421
+ return
422
+ ds = [d for d in self.downloads if not d.choked]
423
+ shuffle(ds)
424
+ for d in ds:
425
+ d._request_more()
426
+ ds = [d for d in self.downloads if not d.interested and d.have[index]]
427
+ for d in ds:
428
+ d.example_interest = index
429
+ d.send_interested()
430
+
431
+ def has_downloaders(self):
432
+ return len(self.downloads)
433
+
434
+ def lost_peer(self, download):
435
+ ip = download.ip
436
+ self.perip[ip].numconnections -= 1
437
+ if self.perip[ip].lastdownload == download:
438
+ self.perip[ip].lastdownload = None
439
+ self.downloads.remove(download)
440
+ if self.endgamemode and not self.downloads: # all peers gone
441
+ self._reset_endgame()
442
+
443
+ def _reset_endgame(self):
444
+ self.storage.reset_endgame(self.all_requests)
445
+ self.endgamemode = False
446
+ self.all_requests = []
447
+ self.endgame_queued_pieces = []
448
+
449
+
450
+ def add_disconnected_seed(self, id):
451
+ # if not self.disconnectedseeds.has_key(id):
452
+ # self.picker.seed_seen_recently()
453
+ self.disconnectedseeds[id]=clock()
454
+
455
+ # def expire_disconnected_seeds(self):
456
+
457
+ def num_disconnected_seeds(self):
458
+ # first expire old ones
459
+ expired = []
460
+ for id,t in self.disconnectedseeds.items():
461
+ if clock() - t > EXPIRE_TIME: #Expire old seeds after so long
462
+ expired.append(id)
463
+ for id in expired:
464
+ # self.picker.seed_disappeared()
465
+ del self.disconnectedseeds[id]
466
+ return len(self.disconnectedseeds)
467
+ # if this isn't called by a stats-gathering function
468
+ # it should be scheduled to run every minute or two.
469
+
470
+ def _check_kicks_ok(self):
471
+ if len(self.gotbaddata) > 10:
472
+ self.kickbans_ok = False
473
+ self.kickbans_halted = True
474
+ return self.kickbans_ok and len(self.downloads) > 2
475
+
476
+ def try_kick(self, download):
477
+ if self._check_kicks_ok():
478
+ download.guard.download = None
479
+ ip = download.ip
480
+ id = download.connection.get_readable_id()
481
+ self.kicked[ip] = id
482
+ self.perip[ip].peerid = id
483
+ self.kickfunc(download.connection)
484
+
485
+ def try_ban(self, ip):
486
+ if self._check_kicks_ok():
487
+ self.banfunc(ip)
488
+ self.banned[ip] = self.perip[ip].peerid
489
+ if self.kicked.has_key(ip):
490
+ del self.kicked[ip]
491
+
492
+ def set_super_seed(self):
493
+ self.super_seeding = True
494
+
495
+ def check_complete(self, index):
496
+ if self.endgamemode and not self.all_requests:
497
+ self.endgamemode = False
498
+ if self.endgame_queued_pieces and not self.endgamemode:
499
+ self.requeue_piece_download()
500
+ if self.storage.am_I_complete():
501
+ assert not self.all_requests
502
+ assert not self.endgamemode
503
+ for d in [i for i in self.downloads if i.have.complete()]:
504
+ d.connection.send_have(index) # be nice, tell the other seed you completed
505
+ self.add_disconnected_seed(d.connection.get_readable_id())
506
+ d.connection.close()
507
+ return True
508
+ return False
509
+
510
+ def too_many_partials(self):
511
+ return len(self.storage.dirty) > (len(self.downloads)/2)
512
+
513
+
514
+ def cancel_piece_download(self, pieces):
515
+ if self.endgamemode:
516
+ if self.endgame_queued_pieces:
517
+ for piece in pieces:
518
+ try:
519
+ self.endgame_queued_pieces.remove(piece)
520
+ except:
521
+ pass
522
+ new_all_requests = []
523
+ for index, nb, nl in self.all_requests:
524
+ if index in pieces:
525
+ self.storage.request_lost(index, nb, nl)
526
+ else:
527
+ new_all_requests.append((index, nb, nl))
528
+ self.all_requests = new_all_requests
529
+
530
+ for d in self.downloads:
531
+ hit = False
532
+ for index, nb, nl in d.active_requests:
533
+ if index in pieces:
534
+ hit = True
535
+ d.connection.send_cancel(index, nb, nl)
536
+ if not self.endgamemode:
537
+ self.storage.request_lost(index, nb, nl)
538
+ if hit:
539
+ d.active_requests = [ r for r in d.active_requests
540
+ if r[0] not in pieces ]
541
+ d._request_more()
542
+ if not self.endgamemode and d.choked:
543
+ d._check_interests()
544
+
545
+ def requeue_piece_download(self, pieces = []):
546
+ if self.endgame_queued_pieces:
547
+ for piece in pieces:
548
+ if not piece in self.endgame_queued_pieces:
549
+ self.endgame_queued_pieces.append(piece)
550
+ pieces = self.endgame_queued_pieces
551
+ if self.endgamemode:
552
+ if self.all_requests:
553
+ self.endgame_queued_pieces = pieces
554
+ return
555
+ self.endgamemode = False
556
+ self.endgame_queued_pieces = None
557
+
558
+ ds = [d for d in self.downloads]
559
+ shuffle(ds)
560
+ for d in ds:
561
+ if d.choked:
562
+ d._check_interests()
563
+ else:
564
+ d._request_more()
565
+
566
+ def start_endgame(self):
567
+ assert not self.endgamemode
568
+ self.endgamemode = True
569
+ assert not self.all_requests
570
+ for d in self.downloads:
571
+ if d.active_requests:
572
+ assert d.interested and not d.choked
573
+ for request in d.active_requests:
574
+ assert not request in self.all_requests
575
+ self.all_requests.append(request)
576
+ for d in self.downloads:
577
+ d.fix_download_endgame()
578
+
579
+ def pause(self, flag):
580
+ self.paused = flag
581
+ if flag:
582
+ for d in self.downloads:
583
+ for index, begin, length in d.active_requests:
584
+ d.connection.send_cancel(index, begin, length)
585
+ d._letgo()
586
+ d.send_not_interested()
587
+ if self.endgamemode:
588
+ self._reset_endgame()
589
+ else:
590
+ shuffle(self.downloads)
591
+ for d in self.downloads:
592
+ d._check_interests()
593
+ if d.interested and not d.choked:
594
+ d._request_more()