murder 0.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +17 -0
- data/README +224 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/dist/BitTornado/BT1/Choker.py +128 -0
- data/dist/BitTornado/BT1/Connecter.py +288 -0
- data/dist/BitTornado/BT1/Downloader.py +594 -0
- data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
- data/dist/BitTornado/BT1/Encrypter.py +333 -0
- data/dist/BitTornado/BT1/FileSelector.py +245 -0
- data/dist/BitTornado/BT1/Filter.py +12 -0
- data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
- data/dist/BitTornado/BT1/NatCheck.py +95 -0
- data/dist/BitTornado/BT1/PiecePicker.py +320 -0
- data/dist/BitTornado/BT1/Rerequester.py +426 -0
- data/dist/BitTornado/BT1/Statistics.py +177 -0
- data/dist/BitTornado/BT1/Storage.py +584 -0
- data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
- data/dist/BitTornado/BT1/StreamCheck.py +135 -0
- data/dist/BitTornado/BT1/T2T.py +193 -0
- data/dist/BitTornado/BT1/Uploader.py +145 -0
- data/dist/BitTornado/BT1/__init__.py +1 -0
- data/dist/BitTornado/BT1/btformats.py +100 -0
- data/dist/BitTornado/BT1/fakeopen.py +89 -0
- data/dist/BitTornado/BT1/makemetafile.py +263 -0
- data/dist/BitTornado/BT1/track.py +1067 -0
- data/dist/BitTornado/ConfigDir.py +401 -0
- data/dist/BitTornado/ConfigReader.py +1068 -0
- data/dist/BitTornado/ConnChoice.py +31 -0
- data/dist/BitTornado/CreateIcons.py +105 -0
- data/dist/BitTornado/CurrentRateMeasure.py +37 -0
- data/dist/BitTornado/HTTPHandler.py +167 -0
- data/dist/BitTornado/PSYCO.py +5 -0
- data/dist/BitTornado/RateLimiter.py +153 -0
- data/dist/BitTornado/RateMeasure.py +75 -0
- data/dist/BitTornado/RawServer.py +195 -0
- data/dist/BitTornado/ServerPortHandler.py +188 -0
- data/dist/BitTornado/SocketHandler.py +375 -0
- data/dist/BitTornado/__init__.py +63 -0
- data/dist/BitTornado/bencode.py +319 -0
- data/dist/BitTornado/bitfield.py +162 -0
- data/dist/BitTornado/clock.py +27 -0
- data/dist/BitTornado/download_bt1.py +882 -0
- data/dist/BitTornado/inifile.py +169 -0
- data/dist/BitTornado/iprangeparse.py +194 -0
- data/dist/BitTornado/launchmanycore.py +381 -0
- data/dist/BitTornado/natpunch.py +254 -0
- data/dist/BitTornado/parseargs.py +137 -0
- data/dist/BitTornado/parsedir.py +150 -0
- data/dist/BitTornado/piecebuffer.py +86 -0
- data/dist/BitTornado/selectpoll.py +109 -0
- data/dist/BitTornado/subnetparse.py +218 -0
- data/dist/BitTornado/torrentlistparse.py +38 -0
- data/dist/BitTornado/zurllib.py +100 -0
- data/dist/murder_client.py +291 -0
- data/dist/murder_make_torrent.py +46 -0
- data/dist/murder_tracker.py +28 -0
- data/doc/examples/Capfile +28 -0
- data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
- data/lib/murder.rb +43 -0
- data/lib/murder/admin.rb +47 -0
- data/lib/murder/murder.rb +121 -0
- data/murder.gemspec +101 -0
- 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()
|