murder 0.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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()