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,1045 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from BitTornado.bitfield import Bitfield
|
5
|
+
from sha import sha
|
6
|
+
from BitTornado.clock import clock
|
7
|
+
from traceback import print_exc
|
8
|
+
from random import randrange
|
9
|
+
try:
|
10
|
+
True
|
11
|
+
except:
|
12
|
+
True = 1
|
13
|
+
False = 0
|
14
|
+
try:
|
15
|
+
from bisect import insort
|
16
|
+
except:
|
17
|
+
def insort(l, item):
|
18
|
+
l.append(item)
|
19
|
+
l.sort()
|
20
|
+
|
21
|
+
DEBUG = False
|
22
|
+
|
23
|
+
STATS_INTERVAL = 0.2
|
24
|
+
|
25
|
+
def dummy_status(fractionDone = None, activity = None):
|
26
|
+
pass
|
27
|
+
|
28
|
+
class Olist:
|
29
|
+
def __init__(self, l = []):
|
30
|
+
self.d = {}
|
31
|
+
for i in l:
|
32
|
+
self.d[i] = 1
|
33
|
+
def __len__(self):
|
34
|
+
return len(self.d)
|
35
|
+
def includes(self, i):
|
36
|
+
return self.d.has_key(i)
|
37
|
+
def add(self, i):
|
38
|
+
self.d[i] = 1
|
39
|
+
def extend(self, l):
|
40
|
+
for i in l:
|
41
|
+
self.d[i] = 1
|
42
|
+
def pop(self, n=0):
|
43
|
+
# assert self.d
|
44
|
+
k = self.d.keys()
|
45
|
+
if n == 0:
|
46
|
+
i = min(k)
|
47
|
+
elif n == -1:
|
48
|
+
i = max(k)
|
49
|
+
else:
|
50
|
+
k.sort()
|
51
|
+
i = k[n]
|
52
|
+
del self.d[i]
|
53
|
+
return i
|
54
|
+
def remove(self, i):
|
55
|
+
if self.d.has_key(i):
|
56
|
+
del self.d[i]
|
57
|
+
|
58
|
+
class fakeflag:
|
59
|
+
def __init__(self, state=False):
|
60
|
+
self.state = state
|
61
|
+
def wait(self):
|
62
|
+
pass
|
63
|
+
def isSet(self):
|
64
|
+
return self.state
|
65
|
+
|
66
|
+
|
67
|
+
class StorageWrapper:
|
68
|
+
def __init__(self, storage, request_size, hashes,
|
69
|
+
piece_size, finished, failed,
|
70
|
+
statusfunc = dummy_status, flag = fakeflag(), check_hashes = True,
|
71
|
+
data_flunked = lambda x: None, backfunc = None,
|
72
|
+
config = {}, unpauseflag = fakeflag(True) ):
|
73
|
+
self.storage = storage
|
74
|
+
self.request_size = long(request_size)
|
75
|
+
self.hashes = hashes
|
76
|
+
self.piece_size = long(piece_size)
|
77
|
+
self.piece_length = long(piece_size)
|
78
|
+
self.finished = finished
|
79
|
+
self.failed = failed
|
80
|
+
self.statusfunc = statusfunc
|
81
|
+
self.flag = flag
|
82
|
+
self.check_hashes = check_hashes
|
83
|
+
self.data_flunked = data_flunked
|
84
|
+
self.backfunc = backfunc
|
85
|
+
self.config = config
|
86
|
+
self.unpauseflag = unpauseflag
|
87
|
+
|
88
|
+
self.alloc_type = config.get('alloc_type','normal')
|
89
|
+
self.double_check = config.get('double_check', 0)
|
90
|
+
self.triple_check = config.get('triple_check', 0)
|
91
|
+
if self.triple_check:
|
92
|
+
self.double_check = True
|
93
|
+
self.bgalloc_enabled = False
|
94
|
+
self.bgalloc_active = False
|
95
|
+
self.total_length = storage.get_total_length()
|
96
|
+
self.amount_left = self.total_length
|
97
|
+
if self.total_length <= self.piece_size * (len(hashes) - 1):
|
98
|
+
raise ValueError, 'bad data in responsefile - total too small'
|
99
|
+
if self.total_length > self.piece_size * len(hashes):
|
100
|
+
raise ValueError, 'bad data in responsefile - total too big'
|
101
|
+
self.numactive = [0] * len(hashes)
|
102
|
+
self.inactive_requests = [1] * len(hashes)
|
103
|
+
self.amount_inactive = self.total_length
|
104
|
+
self.amount_obtained = 0
|
105
|
+
self.amount_desired = self.total_length
|
106
|
+
self.have = Bitfield(len(hashes))
|
107
|
+
self.have_cloaked_data = None
|
108
|
+
self.blocked = [False] * len(hashes)
|
109
|
+
self.blocked_holes = []
|
110
|
+
self.blocked_movein = Olist()
|
111
|
+
self.blocked_moveout = Olist()
|
112
|
+
self.waschecked = [False] * len(hashes)
|
113
|
+
self.places = {}
|
114
|
+
self.holes = []
|
115
|
+
self.stat_active = {}
|
116
|
+
self.stat_new = {}
|
117
|
+
self.dirty = {}
|
118
|
+
self.stat_numflunked = 0
|
119
|
+
self.stat_numdownloaded = 0
|
120
|
+
self.stat_numfound = 0
|
121
|
+
self.download_history = {}
|
122
|
+
self.failed_pieces = {}
|
123
|
+
self.out_of_place = 0
|
124
|
+
self.write_buf_max = config['write_buffer_size']*1048576L
|
125
|
+
self.write_buf_size = 0L
|
126
|
+
self.write_buf = {} # structure: piece: [(start, data), ...]
|
127
|
+
self.write_buf_list = []
|
128
|
+
|
129
|
+
self.initialize_tasks = [
|
130
|
+
['checking existing data', 0, self.init_hashcheck, self.hashcheckfunc],
|
131
|
+
['moving data', 1, self.init_movedata, self.movedatafunc],
|
132
|
+
['allocating disk space', 1, self.init_alloc, self.allocfunc] ]
|
133
|
+
|
134
|
+
self.backfunc(self._bgalloc,0.1)
|
135
|
+
self.backfunc(self._bgsync,max(self.config['auto_flush']*60,60))
|
136
|
+
|
137
|
+
def _bgsync(self):
|
138
|
+
if self.config['auto_flush']:
|
139
|
+
self.sync()
|
140
|
+
self.backfunc(self._bgsync,max(self.config['auto_flush']*60,60))
|
141
|
+
|
142
|
+
|
143
|
+
def old_style_init(self):
|
144
|
+
while self.initialize_tasks:
|
145
|
+
msg, done, init, next = self.initialize_tasks.pop(0)
|
146
|
+
if init():
|
147
|
+
self.statusfunc(activity = msg, fractionDone = done)
|
148
|
+
t = clock() + STATS_INTERVAL
|
149
|
+
x = 0
|
150
|
+
while x is not None:
|
151
|
+
if t < clock():
|
152
|
+
t = clock() + STATS_INTERVAL
|
153
|
+
self.statusfunc(fractionDone = x)
|
154
|
+
self.unpauseflag.wait()
|
155
|
+
if self.flag.isSet():
|
156
|
+
return False
|
157
|
+
x = next()
|
158
|
+
|
159
|
+
self.statusfunc(fractionDone = 0)
|
160
|
+
return True
|
161
|
+
|
162
|
+
|
163
|
+
def initialize(self, donefunc, statusfunc = None):
|
164
|
+
self.initialize_done = donefunc
|
165
|
+
if statusfunc is None:
|
166
|
+
statusfunc = self.statusfunc
|
167
|
+
self.initialize_status = statusfunc
|
168
|
+
self.initialize_next = None
|
169
|
+
|
170
|
+
self.backfunc(self._initialize)
|
171
|
+
|
172
|
+
def _initialize(self):
|
173
|
+
if not self.unpauseflag.isSet():
|
174
|
+
self.backfunc(self._initialize, 1)
|
175
|
+
return
|
176
|
+
|
177
|
+
if self.initialize_next:
|
178
|
+
x = self.initialize_next()
|
179
|
+
if x is None:
|
180
|
+
self.initialize_next = None
|
181
|
+
else:
|
182
|
+
self.initialize_status(fractionDone = x)
|
183
|
+
else:
|
184
|
+
if not self.initialize_tasks:
|
185
|
+
self.initialize_done()
|
186
|
+
return
|
187
|
+
msg, done, init, next = self.initialize_tasks.pop(0)
|
188
|
+
if init():
|
189
|
+
self.initialize_status(activity = msg, fractionDone = done)
|
190
|
+
self.initialize_next = next
|
191
|
+
|
192
|
+
self.backfunc(self._initialize)
|
193
|
+
|
194
|
+
|
195
|
+
def init_hashcheck(self):
|
196
|
+
if self.flag.isSet():
|
197
|
+
return False
|
198
|
+
self.check_list = []
|
199
|
+
if len(self.hashes) == 0 or self.amount_left == 0:
|
200
|
+
self.check_total = 0
|
201
|
+
self.finished()
|
202
|
+
return False
|
203
|
+
|
204
|
+
self.check_targets = {}
|
205
|
+
got = {}
|
206
|
+
for p,v in self.places.items():
|
207
|
+
assert not got.has_key(v)
|
208
|
+
got[v] = 1
|
209
|
+
for i in xrange(len(self.hashes)):
|
210
|
+
if self.places.has_key(i): # restored from pickled
|
211
|
+
self.check_targets[self.hashes[i]] = []
|
212
|
+
if self.places[i] == i:
|
213
|
+
continue
|
214
|
+
else:
|
215
|
+
assert not got.has_key(i)
|
216
|
+
self.out_of_place += 1
|
217
|
+
if got.has_key(i):
|
218
|
+
continue
|
219
|
+
if self._waspre(i):
|
220
|
+
if self.blocked[i]:
|
221
|
+
self.places[i] = i
|
222
|
+
else:
|
223
|
+
self.check_list.append(i)
|
224
|
+
continue
|
225
|
+
if not self.check_hashes:
|
226
|
+
self.failed('told file complete on start-up, but data is missing')
|
227
|
+
return False
|
228
|
+
self.holes.append(i)
|
229
|
+
if self.blocked[i] or self.check_targets.has_key(self.hashes[i]):
|
230
|
+
self.check_targets[self.hashes[i]] = [] # in case of a hash collision, discard
|
231
|
+
else:
|
232
|
+
self.check_targets[self.hashes[i]] = [i]
|
233
|
+
self.check_total = len(self.check_list)
|
234
|
+
self.check_numchecked = 0.0
|
235
|
+
self.lastlen = self._piecelen(len(self.hashes) - 1)
|
236
|
+
self.numchecked = 0.0
|
237
|
+
return self.check_total > 0
|
238
|
+
|
239
|
+
def _markgot(self, piece, pos):
|
240
|
+
if DEBUG:
|
241
|
+
print str(piece)+' at '+str(pos)
|
242
|
+
self.places[piece] = pos
|
243
|
+
self.have[piece] = True
|
244
|
+
len = self._piecelen(piece)
|
245
|
+
self.amount_obtained += len
|
246
|
+
self.amount_left -= len
|
247
|
+
self.amount_inactive -= len
|
248
|
+
self.inactive_requests[piece] = None
|
249
|
+
self.waschecked[piece] = self.check_hashes
|
250
|
+
self.stat_numfound += 1
|
251
|
+
|
252
|
+
def hashcheckfunc(self):
|
253
|
+
if self.flag.isSet():
|
254
|
+
return None
|
255
|
+
if not self.check_list:
|
256
|
+
return None
|
257
|
+
|
258
|
+
i = self.check_list.pop(0)
|
259
|
+
if not self.check_hashes:
|
260
|
+
self._markgot(i, i)
|
261
|
+
else:
|
262
|
+
d1 = self.read_raw(i,0,self.lastlen)
|
263
|
+
if d1 is None:
|
264
|
+
return None
|
265
|
+
sh = sha(d1[:])
|
266
|
+
d1.release()
|
267
|
+
sp = sh.digest()
|
268
|
+
d2 = self.read_raw(i,self.lastlen,self._piecelen(i)-self.lastlen)
|
269
|
+
if d2 is None:
|
270
|
+
return None
|
271
|
+
sh.update(d2[:])
|
272
|
+
d2.release()
|
273
|
+
s = sh.digest()
|
274
|
+
if s == self.hashes[i]:
|
275
|
+
self._markgot(i, i)
|
276
|
+
elif ( self.check_targets.get(s)
|
277
|
+
and self._piecelen(i) == self._piecelen(self.check_targets[s][-1]) ):
|
278
|
+
self._markgot(self.check_targets[s].pop(), i)
|
279
|
+
self.out_of_place += 1
|
280
|
+
elif ( not self.have[-1] and sp == self.hashes[-1]
|
281
|
+
and (i == len(self.hashes) - 1
|
282
|
+
or not self._waspre(len(self.hashes) - 1)) ):
|
283
|
+
self._markgot(len(self.hashes) - 1, i)
|
284
|
+
self.out_of_place += 1
|
285
|
+
else:
|
286
|
+
self.places[i] = i
|
287
|
+
self.numchecked += 1
|
288
|
+
if self.amount_left == 0:
|
289
|
+
self.finished()
|
290
|
+
return (self.numchecked / self.check_total)
|
291
|
+
|
292
|
+
|
293
|
+
def init_movedata(self):
|
294
|
+
if self.flag.isSet():
|
295
|
+
return False
|
296
|
+
if self.alloc_type != 'sparse':
|
297
|
+
return False
|
298
|
+
self.storage.top_off() # sets file lengths to their final size
|
299
|
+
self.movelist = []
|
300
|
+
if self.out_of_place == 0:
|
301
|
+
for i in self.holes:
|
302
|
+
self.places[i] = i
|
303
|
+
self.holes = []
|
304
|
+
return False
|
305
|
+
self.tomove = float(self.out_of_place)
|
306
|
+
for i in xrange(len(self.hashes)):
|
307
|
+
if not self.places.has_key(i):
|
308
|
+
self.places[i] = i
|
309
|
+
elif self.places[i] != i:
|
310
|
+
self.movelist.append(i)
|
311
|
+
self.holes = []
|
312
|
+
return True
|
313
|
+
|
314
|
+
def movedatafunc(self):
|
315
|
+
if self.flag.isSet():
|
316
|
+
return None
|
317
|
+
if not self.movelist:
|
318
|
+
return None
|
319
|
+
i = self.movelist.pop(0)
|
320
|
+
old = self.read_raw(self.places[i], 0, self._piecelen(i))
|
321
|
+
if old is None:
|
322
|
+
return None
|
323
|
+
if not self.write_raw(i, 0, old):
|
324
|
+
return None
|
325
|
+
if self.double_check and self.have[i]:
|
326
|
+
if self.triple_check:
|
327
|
+
old.release()
|
328
|
+
old = self.read_raw( i, 0, self._piecelen(i),
|
329
|
+
flush_first = True )
|
330
|
+
if old is None:
|
331
|
+
return None
|
332
|
+
if sha(old[:]).digest() != self.hashes[i]:
|
333
|
+
self.failed('download corrupted; please restart and resume')
|
334
|
+
return None
|
335
|
+
old.release()
|
336
|
+
|
337
|
+
self.places[i] = i
|
338
|
+
self.tomove -= 1
|
339
|
+
return (self.tomove / self.out_of_place)
|
340
|
+
|
341
|
+
|
342
|
+
def init_alloc(self):
|
343
|
+
if self.flag.isSet():
|
344
|
+
return False
|
345
|
+
if not self.holes:
|
346
|
+
return False
|
347
|
+
self.numholes = float(len(self.holes))
|
348
|
+
self.alloc_buf = chr(0xFF) * self.piece_size
|
349
|
+
if self.alloc_type == 'pre-allocate':
|
350
|
+
self.bgalloc_enabled = True
|
351
|
+
return True
|
352
|
+
if self.alloc_type == 'background':
|
353
|
+
self.bgalloc_enabled = True
|
354
|
+
if self.blocked_moveout:
|
355
|
+
return True
|
356
|
+
return False
|
357
|
+
|
358
|
+
|
359
|
+
def _allocfunc(self):
|
360
|
+
while self.holes:
|
361
|
+
n = self.holes.pop(0)
|
362
|
+
if self.blocked[n]: # assume not self.blocked[index]
|
363
|
+
if not self.blocked_movein:
|
364
|
+
self.blocked_holes.append(n)
|
365
|
+
continue
|
366
|
+
if not self.places.has_key(n):
|
367
|
+
b = self.blocked_movein.pop(0)
|
368
|
+
oldpos = self._move_piece(b, n)
|
369
|
+
self.places[oldpos] = oldpos
|
370
|
+
return None
|
371
|
+
if self.places.has_key(n):
|
372
|
+
oldpos = self._move_piece(n, n)
|
373
|
+
self.places[oldpos] = oldpos
|
374
|
+
return None
|
375
|
+
return n
|
376
|
+
return None
|
377
|
+
|
378
|
+
def allocfunc(self):
|
379
|
+
if self.flag.isSet():
|
380
|
+
return None
|
381
|
+
|
382
|
+
if self.blocked_moveout:
|
383
|
+
self.bgalloc_active = True
|
384
|
+
n = self._allocfunc()
|
385
|
+
if n is not None:
|
386
|
+
if self.blocked_moveout.includes(n):
|
387
|
+
self.blocked_moveout.remove(n)
|
388
|
+
b = n
|
389
|
+
else:
|
390
|
+
b = self.blocked_moveout.pop(0)
|
391
|
+
oldpos = self._move_piece(b,n)
|
392
|
+
self.places[oldpos] = oldpos
|
393
|
+
return len(self.holes) / self.numholes
|
394
|
+
|
395
|
+
if self.holes and self.bgalloc_enabled:
|
396
|
+
self.bgalloc_active = True
|
397
|
+
n = self._allocfunc()
|
398
|
+
if n is not None:
|
399
|
+
self.write_raw(n, 0, self.alloc_buf[:self._piecelen(n)])
|
400
|
+
self.places[n] = n
|
401
|
+
return len(self.holes) / self.numholes
|
402
|
+
|
403
|
+
self.bgalloc_active = False
|
404
|
+
return None
|
405
|
+
|
406
|
+
def bgalloc(self):
|
407
|
+
if self.bgalloc_enabled:
|
408
|
+
if not self.holes and not self.blocked_moveout and self.backfunc:
|
409
|
+
self.backfunc(self.storage.flush)
|
410
|
+
# force a flush whenever the "finish allocation" button is hit
|
411
|
+
self.bgalloc_enabled = True
|
412
|
+
return False
|
413
|
+
|
414
|
+
def _bgalloc(self):
|
415
|
+
self.allocfunc()
|
416
|
+
if self.config.get('alloc_rate',0) < 0.1:
|
417
|
+
self.config['alloc_rate'] = 0.1
|
418
|
+
self.backfunc( self._bgalloc,
|
419
|
+
float(self.piece_size)/(self.config['alloc_rate']*1048576) )
|
420
|
+
|
421
|
+
|
422
|
+
def _waspre(self, piece):
|
423
|
+
return self.storage.was_preallocated(piece * self.piece_size, self._piecelen(piece))
|
424
|
+
|
425
|
+
def _piecelen(self, piece):
|
426
|
+
if piece < len(self.hashes) - 1:
|
427
|
+
return self.piece_size
|
428
|
+
else:
|
429
|
+
return self.total_length - (piece * self.piece_size)
|
430
|
+
|
431
|
+
def get_amount_left(self):
|
432
|
+
return self.amount_left
|
433
|
+
|
434
|
+
def do_I_have_anything(self):
|
435
|
+
return self.amount_left < self.total_length
|
436
|
+
|
437
|
+
def _make_inactive(self, index):
|
438
|
+
length = self._piecelen(index)
|
439
|
+
l = []
|
440
|
+
x = 0
|
441
|
+
while x + self.request_size < length:
|
442
|
+
l.append((x, self.request_size))
|
443
|
+
x += self.request_size
|
444
|
+
l.append((x, length - x))
|
445
|
+
self.inactive_requests[index] = l
|
446
|
+
|
447
|
+
def is_endgame(self):
|
448
|
+
return not self.amount_inactive
|
449
|
+
|
450
|
+
def am_I_complete(self):
|
451
|
+
return self.amount_obtained == self.amount_desired
|
452
|
+
|
453
|
+
def reset_endgame(self, requestlist):
|
454
|
+
for index, begin, length in requestlist:
|
455
|
+
self.request_lost(index, begin, length)
|
456
|
+
|
457
|
+
def get_have_list(self):
|
458
|
+
return self.have.tostring()
|
459
|
+
|
460
|
+
def get_have_list_cloaked(self):
|
461
|
+
if self.have_cloaked_data is None:
|
462
|
+
newhave = Bitfield(copyfrom = self.have)
|
463
|
+
unhaves = []
|
464
|
+
n = min(randrange(2,5),len(self.hashes)) # between 2-4 unless torrent is small
|
465
|
+
while len(unhaves) < n:
|
466
|
+
unhave = randrange(min(32,len(self.hashes))) # all in first 4 bytes
|
467
|
+
if not unhave in unhaves:
|
468
|
+
unhaves.append(unhave)
|
469
|
+
newhave[unhave] = False
|
470
|
+
self.have_cloaked_data = (newhave.tostring(), unhaves)
|
471
|
+
return self.have_cloaked_data
|
472
|
+
|
473
|
+
def do_I_have(self, index):
|
474
|
+
return self.have[index]
|
475
|
+
|
476
|
+
def do_I_have_requests(self, index):
|
477
|
+
return not not self.inactive_requests[index]
|
478
|
+
|
479
|
+
def is_unstarted(self, index):
|
480
|
+
return ( not self.have[index] and not self.numactive[index]
|
481
|
+
and not self.dirty.has_key(index) )
|
482
|
+
|
483
|
+
def get_hash(self, index):
|
484
|
+
return self.hashes[index]
|
485
|
+
|
486
|
+
def get_stats(self):
|
487
|
+
return self.amount_obtained, self.amount_desired
|
488
|
+
|
489
|
+
def new_request(self, index):
|
490
|
+
# returns (begin, length)
|
491
|
+
if self.inactive_requests[index] == 1:
|
492
|
+
self._make_inactive(index)
|
493
|
+
self.numactive[index] += 1
|
494
|
+
self.stat_active[index] = 1
|
495
|
+
if not self.dirty.has_key(index):
|
496
|
+
self.stat_new[index] = 1
|
497
|
+
rs = self.inactive_requests[index]
|
498
|
+
# r = min(rs)
|
499
|
+
# rs.remove(r)
|
500
|
+
r = rs.pop(0)
|
501
|
+
self.amount_inactive -= r[1]
|
502
|
+
return r
|
503
|
+
|
504
|
+
|
505
|
+
def write_raw(self, index, begin, data):
|
506
|
+
try:
|
507
|
+
self.storage.write(self.piece_size * index + begin, data)
|
508
|
+
return True
|
509
|
+
except IOError, e:
|
510
|
+
self.failed('IO Error: ' + str(e))
|
511
|
+
return False
|
512
|
+
|
513
|
+
|
514
|
+
def _write_to_buffer(self, piece, start, data):
|
515
|
+
if not self.write_buf_max:
|
516
|
+
return self.write_raw(self.places[piece], start, data)
|
517
|
+
self.write_buf_size += len(data)
|
518
|
+
while self.write_buf_size > self.write_buf_max:
|
519
|
+
old = self.write_buf_list.pop(0)
|
520
|
+
if not self._flush_buffer(old, True):
|
521
|
+
return False
|
522
|
+
if self.write_buf.has_key(piece):
|
523
|
+
self.write_buf_list.remove(piece)
|
524
|
+
else:
|
525
|
+
self.write_buf[piece] = []
|
526
|
+
self.write_buf_list.append(piece)
|
527
|
+
self.write_buf[piece].append((start,data))
|
528
|
+
return True
|
529
|
+
|
530
|
+
def _flush_buffer(self, piece, popped = False):
|
531
|
+
if not self.write_buf.has_key(piece):
|
532
|
+
return True
|
533
|
+
if not popped:
|
534
|
+
self.write_buf_list.remove(piece)
|
535
|
+
l = self.write_buf[piece]
|
536
|
+
del self.write_buf[piece]
|
537
|
+
l.sort()
|
538
|
+
for start, data in l:
|
539
|
+
self.write_buf_size -= len(data)
|
540
|
+
if not self.write_raw(self.places[piece], start, data):
|
541
|
+
return False
|
542
|
+
return True
|
543
|
+
|
544
|
+
def sync(self):
|
545
|
+
spots = {}
|
546
|
+
for p in self.write_buf_list:
|
547
|
+
spots[self.places[p]] = p
|
548
|
+
l = spots.keys()
|
549
|
+
l.sort()
|
550
|
+
for i in l:
|
551
|
+
try:
|
552
|
+
self._flush_buffer(spots[i])
|
553
|
+
except:
|
554
|
+
pass
|
555
|
+
try:
|
556
|
+
self.storage.sync()
|
557
|
+
except IOError, e:
|
558
|
+
self.failed('IO Error: ' + str(e))
|
559
|
+
except OSError, e:
|
560
|
+
self.failed('OS Error: ' + str(e))
|
561
|
+
|
562
|
+
|
563
|
+
def _move_piece(self, index, newpos):
|
564
|
+
oldpos = self.places[index]
|
565
|
+
if DEBUG:
|
566
|
+
print 'moving '+str(index)+' from '+str(oldpos)+' to '+str(newpos)
|
567
|
+
assert oldpos != index
|
568
|
+
assert oldpos != newpos
|
569
|
+
assert index == newpos or not self.places.has_key(newpos)
|
570
|
+
old = self.read_raw(oldpos, 0, self._piecelen(index))
|
571
|
+
if old is None:
|
572
|
+
return -1
|
573
|
+
if not self.write_raw(newpos, 0, old):
|
574
|
+
return -1
|
575
|
+
self.places[index] = newpos
|
576
|
+
if self.have[index] and (
|
577
|
+
self.triple_check or (self.double_check and index == newpos) ):
|
578
|
+
if self.triple_check:
|
579
|
+
old.release()
|
580
|
+
old = self.read_raw(newpos, 0, self._piecelen(index),
|
581
|
+
flush_first = True)
|
582
|
+
if old is None:
|
583
|
+
return -1
|
584
|
+
if sha(old[:]).digest() != self.hashes[index]:
|
585
|
+
self.failed('download corrupted; please restart and resume')
|
586
|
+
return -1
|
587
|
+
old.release()
|
588
|
+
|
589
|
+
if self.blocked[index]:
|
590
|
+
self.blocked_moveout.remove(index)
|
591
|
+
if self.blocked[newpos]:
|
592
|
+
self.blocked_movein.remove(index)
|
593
|
+
else:
|
594
|
+
self.blocked_movein.add(index)
|
595
|
+
else:
|
596
|
+
self.blocked_movein.remove(index)
|
597
|
+
if self.blocked[newpos]:
|
598
|
+
self.blocked_moveout.add(index)
|
599
|
+
else:
|
600
|
+
self.blocked_moveout.remove(index)
|
601
|
+
|
602
|
+
return oldpos
|
603
|
+
|
604
|
+
def _clear_space(self, index):
|
605
|
+
h = self.holes.pop(0)
|
606
|
+
n = h
|
607
|
+
if self.blocked[n]: # assume not self.blocked[index]
|
608
|
+
if not self.blocked_movein:
|
609
|
+
self.blocked_holes.append(n)
|
610
|
+
return True # repeat
|
611
|
+
if not self.places.has_key(n):
|
612
|
+
b = self.blocked_movein.pop(0)
|
613
|
+
oldpos = self._move_piece(b, n)
|
614
|
+
if oldpos < 0:
|
615
|
+
return False
|
616
|
+
n = oldpos
|
617
|
+
if self.places.has_key(n):
|
618
|
+
oldpos = self._move_piece(n, n)
|
619
|
+
if oldpos < 0:
|
620
|
+
return False
|
621
|
+
n = oldpos
|
622
|
+
if index == n or index in self.holes:
|
623
|
+
if n == h:
|
624
|
+
self.write_raw(n, 0, self.alloc_buf[:self._piecelen(n)])
|
625
|
+
self.places[index] = n
|
626
|
+
if self.blocked[n]:
|
627
|
+
# because n may be a spot cleared 10 lines above, it's possible
|
628
|
+
# for it to be blocked. While that spot could be left cleared
|
629
|
+
# and a new spot allocated, this condition might occur several
|
630
|
+
# times in a row, resulting in a significant amount of disk I/O,
|
631
|
+
# delaying the operation of the engine. Rather than do this,
|
632
|
+
# queue the piece to be moved out again, which will be performed
|
633
|
+
# by the background allocator, with which data movement is
|
634
|
+
# automatically limited.
|
635
|
+
self.blocked_moveout.add(index)
|
636
|
+
return False
|
637
|
+
for p, v in self.places.items():
|
638
|
+
if v == index:
|
639
|
+
break
|
640
|
+
else:
|
641
|
+
self.failed('download corrupted; please restart and resume')
|
642
|
+
return False
|
643
|
+
self._move_piece(p, n)
|
644
|
+
self.places[index] = index
|
645
|
+
return False
|
646
|
+
|
647
|
+
|
648
|
+
def piece_came_in(self, index, begin, piece, source = None):
|
649
|
+
assert not self.have[index]
|
650
|
+
|
651
|
+
if not self.places.has_key(index):
|
652
|
+
while self._clear_space(index):
|
653
|
+
pass
|
654
|
+
if DEBUG:
|
655
|
+
print 'new place for '+str(index)+' at '+str(self.places[index])
|
656
|
+
if self.flag.isSet():
|
657
|
+
return
|
658
|
+
|
659
|
+
if self.failed_pieces.has_key(index):
|
660
|
+
old = self.read_raw(self.places[index], begin, len(piece))
|
661
|
+
if old is None:
|
662
|
+
return True
|
663
|
+
if old[:].tostring() != piece:
|
664
|
+
try:
|
665
|
+
self.failed_pieces[index][self.download_history[index][begin]] = 1
|
666
|
+
except:
|
667
|
+
self.failed_pieces[index][None] = 1
|
668
|
+
old.release()
|
669
|
+
self.download_history.setdefault(index,{})[begin] = source
|
670
|
+
|
671
|
+
if not self._write_to_buffer(index, begin, piece):
|
672
|
+
return True
|
673
|
+
|
674
|
+
self.amount_obtained += len(piece)
|
675
|
+
self.dirty.setdefault(index,[]).append((begin, len(piece)))
|
676
|
+
self.numactive[index] -= 1
|
677
|
+
assert self.numactive[index] >= 0
|
678
|
+
if not self.numactive[index]:
|
679
|
+
del self.stat_active[index]
|
680
|
+
if self.stat_new.has_key(index):
|
681
|
+
del self.stat_new[index]
|
682
|
+
|
683
|
+
if self.inactive_requests[index] or self.numactive[index]:
|
684
|
+
return True
|
685
|
+
|
686
|
+
del self.dirty[index]
|
687
|
+
if not self._flush_buffer(index):
|
688
|
+
return True
|
689
|
+
length = self._piecelen(index)
|
690
|
+
data = self.read_raw(self.places[index], 0, length,
|
691
|
+
flush_first = self.triple_check)
|
692
|
+
if data is None:
|
693
|
+
return True
|
694
|
+
hash = sha(data[:]).digest()
|
695
|
+
data.release()
|
696
|
+
if hash != self.hashes[index]:
|
697
|
+
|
698
|
+
self.amount_obtained -= length
|
699
|
+
self.data_flunked(length, index)
|
700
|
+
self.inactive_requests[index] = 1
|
701
|
+
self.amount_inactive += length
|
702
|
+
self.stat_numflunked += 1
|
703
|
+
|
704
|
+
self.failed_pieces[index] = {}
|
705
|
+
allsenders = {}
|
706
|
+
for d in self.download_history[index].values():
|
707
|
+
allsenders[d] = 1
|
708
|
+
if len(allsenders) == 1:
|
709
|
+
culprit = allsenders.keys()[0]
|
710
|
+
if culprit is not None:
|
711
|
+
culprit.failed(index, bump = True)
|
712
|
+
del self.failed_pieces[index] # found the culprit already
|
713
|
+
|
714
|
+
return False
|
715
|
+
|
716
|
+
self.have[index] = True
|
717
|
+
self.inactive_requests[index] = None
|
718
|
+
self.waschecked[index] = True
|
719
|
+
self.amount_left -= length
|
720
|
+
self.stat_numdownloaded += 1
|
721
|
+
|
722
|
+
for d in self.download_history[index].values():
|
723
|
+
if d is not None:
|
724
|
+
d.good(index)
|
725
|
+
del self.download_history[index]
|
726
|
+
if self.failed_pieces.has_key(index):
|
727
|
+
for d in self.failed_pieces[index].keys():
|
728
|
+
if d is not None:
|
729
|
+
d.failed(index)
|
730
|
+
del self.failed_pieces[index]
|
731
|
+
|
732
|
+
if self.amount_left == 0:
|
733
|
+
self.finished()
|
734
|
+
return True
|
735
|
+
|
736
|
+
|
737
|
+
def request_lost(self, index, begin, length):
|
738
|
+
assert not (begin, length) in self.inactive_requests[index]
|
739
|
+
insort(self.inactive_requests[index], (begin, length))
|
740
|
+
self.amount_inactive += length
|
741
|
+
self.numactive[index] -= 1
|
742
|
+
if not self.numactive[index]:
|
743
|
+
del self.stat_active[index]
|
744
|
+
if self.stat_new.has_key(index):
|
745
|
+
del self.stat_new[index]
|
746
|
+
|
747
|
+
|
748
|
+
def get_piece(self, index, begin, length):
|
749
|
+
if not self.have[index]:
|
750
|
+
return None
|
751
|
+
data = None
|
752
|
+
if not self.waschecked[index]:
|
753
|
+
data = self.read_raw(self.places[index], 0, self._piecelen(index))
|
754
|
+
if data is None:
|
755
|
+
return None
|
756
|
+
if sha(data[:]).digest() != self.hashes[index]:
|
757
|
+
self.failed('told file complete on start-up, but piece failed hash check')
|
758
|
+
return None
|
759
|
+
self.waschecked[index] = True
|
760
|
+
if length == -1 and begin == 0:
|
761
|
+
return data # optimization
|
762
|
+
if length == -1:
|
763
|
+
if begin > self._piecelen(index):
|
764
|
+
return None
|
765
|
+
length = self._piecelen(index)-begin
|
766
|
+
if begin == 0:
|
767
|
+
return self.read_raw(self.places[index], 0, length)
|
768
|
+
elif begin + length > self._piecelen(index):
|
769
|
+
return None
|
770
|
+
if data is not None:
|
771
|
+
s = data[begin:begin+length]
|
772
|
+
data.release()
|
773
|
+
return s
|
774
|
+
data = self.read_raw(self.places[index], begin, length)
|
775
|
+
if data is None:
|
776
|
+
return None
|
777
|
+
s = data.getarray()
|
778
|
+
data.release()
|
779
|
+
return s
|
780
|
+
|
781
|
+
def read_raw(self, piece, begin, length, flush_first = False):
|
782
|
+
try:
|
783
|
+
return self.storage.read(self.piece_size * piece + begin,
|
784
|
+
length, flush_first)
|
785
|
+
except IOError, e:
|
786
|
+
self.failed('IO Error: ' + str(e))
|
787
|
+
return None
|
788
|
+
|
789
|
+
|
790
|
+
def set_file_readonly(self, n):
|
791
|
+
try:
|
792
|
+
self.storage.set_readonly(n)
|
793
|
+
except IOError, e:
|
794
|
+
self.failed('IO Error: ' + str(e))
|
795
|
+
except OSError, e:
|
796
|
+
self.failed('OS Error: ' + str(e))
|
797
|
+
|
798
|
+
|
799
|
+
def has_data(self, index):
|
800
|
+
return index not in self.holes and index not in self.blocked_holes
|
801
|
+
|
802
|
+
def doublecheck_data(self, pieces_to_check):
|
803
|
+
if not self.double_check:
|
804
|
+
return
|
805
|
+
sources = []
|
806
|
+
for p,v in self.places.items():
|
807
|
+
if pieces_to_check.has_key(v):
|
808
|
+
sources.append(p)
|
809
|
+
assert len(sources) == len(pieces_to_check)
|
810
|
+
sources.sort()
|
811
|
+
for index in sources:
|
812
|
+
if self.have[index]:
|
813
|
+
piece = self.read_raw(self.places[index],0,self._piecelen(index),
|
814
|
+
flush_first = True )
|
815
|
+
if piece is None:
|
816
|
+
return False
|
817
|
+
if sha(piece[:]).digest() != self.hashes[index]:
|
818
|
+
self.failed('download corrupted; please restart and resume')
|
819
|
+
return False
|
820
|
+
piece.release()
|
821
|
+
return True
|
822
|
+
|
823
|
+
|
824
|
+
def reblock(self, new_blocked):
|
825
|
+
# assume downloads have already been canceled and chunks made inactive
|
826
|
+
for i in xrange(len(new_blocked)):
|
827
|
+
if new_blocked[i] and not self.blocked[i]:
|
828
|
+
length = self._piecelen(i)
|
829
|
+
self.amount_desired -= length
|
830
|
+
if self.have[i]:
|
831
|
+
self.amount_obtained -= length
|
832
|
+
continue
|
833
|
+
if self.inactive_requests[i] == 1:
|
834
|
+
self.amount_inactive -= length
|
835
|
+
continue
|
836
|
+
inactive = 0
|
837
|
+
for nb, nl in self.inactive_requests[i]:
|
838
|
+
inactive += nl
|
839
|
+
self.amount_inactive -= inactive
|
840
|
+
self.amount_obtained -= length - inactive
|
841
|
+
|
842
|
+
if self.blocked[i] and not new_blocked[i]:
|
843
|
+
length = self._piecelen(i)
|
844
|
+
self.amount_desired += length
|
845
|
+
if self.have[i]:
|
846
|
+
self.amount_obtained += length
|
847
|
+
continue
|
848
|
+
if self.inactive_requests[i] == 1:
|
849
|
+
self.amount_inactive += length
|
850
|
+
continue
|
851
|
+
inactive = 0
|
852
|
+
for nb, nl in self.inactive_requests[i]:
|
853
|
+
inactive += nl
|
854
|
+
self.amount_inactive += inactive
|
855
|
+
self.amount_obtained += length - inactive
|
856
|
+
|
857
|
+
self.blocked = new_blocked
|
858
|
+
|
859
|
+
self.blocked_movein = Olist()
|
860
|
+
self.blocked_moveout = Olist()
|
861
|
+
for p,v in self.places.items():
|
862
|
+
if p != v:
|
863
|
+
if self.blocked[p] and not self.blocked[v]:
|
864
|
+
self.blocked_movein.add(p)
|
865
|
+
elif self.blocked[v] and not self.blocked[p]:
|
866
|
+
self.blocked_moveout.add(p)
|
867
|
+
|
868
|
+
self.holes.extend(self.blocked_holes) # reset holes list
|
869
|
+
self.holes.sort()
|
870
|
+
self.blocked_holes = []
|
871
|
+
|
872
|
+
|
873
|
+
'''
|
874
|
+
Pickled data format:
|
875
|
+
|
876
|
+
d['pieces'] = either a string containing a bitfield of complete pieces,
|
877
|
+
or the numeric value "1" signifying a seed. If it is
|
878
|
+
a seed, d['places'] and d['partials'] should be empty
|
879
|
+
and needn't even exist.
|
880
|
+
d['partials'] = [ piece, [ offset, length... ]... ]
|
881
|
+
a list of partial data that had been previously
|
882
|
+
downloaded, plus the given offsets. Adjacent partials
|
883
|
+
are merged so as to save space, and so that if the
|
884
|
+
request size changes then new requests can be
|
885
|
+
calculated more efficiently.
|
886
|
+
d['places'] = [ piece, place, {,piece, place ...} ]
|
887
|
+
the piece index, and the place it's stored.
|
888
|
+
If d['pieces'] specifies a complete piece or d['partials']
|
889
|
+
specifies a set of partials for a piece which has no
|
890
|
+
entry in d['places'], it can be assumed that
|
891
|
+
place[index] = index. A place specified with no
|
892
|
+
corresponding data in d['pieces'] or d['partials']
|
893
|
+
indicates allocated space with no valid data, and is
|
894
|
+
reserved so it doesn't need to be hash-checked.
|
895
|
+
'''
|
896
|
+
def pickle(self):
|
897
|
+
if self.have.complete():
|
898
|
+
return {'pieces': 1}
|
899
|
+
pieces = Bitfield(len(self.hashes))
|
900
|
+
places = []
|
901
|
+
partials = []
|
902
|
+
for p in xrange(len(self.hashes)):
|
903
|
+
if self.blocked[p] or not self.places.has_key(p):
|
904
|
+
continue
|
905
|
+
h = self.have[p]
|
906
|
+
pieces[p] = h
|
907
|
+
pp = self.dirty.get(p)
|
908
|
+
if not h and not pp: # no data
|
909
|
+
places.extend([self.places[p],self.places[p]])
|
910
|
+
elif self.places[p] != p:
|
911
|
+
places.extend([p, self.places[p]])
|
912
|
+
if h or not pp:
|
913
|
+
continue
|
914
|
+
pp.sort()
|
915
|
+
r = []
|
916
|
+
while len(pp) > 1:
|
917
|
+
if pp[0][0]+pp[0][1] == pp[1][0]:
|
918
|
+
pp[0] = list(pp[0])
|
919
|
+
pp[0][1] += pp[1][1]
|
920
|
+
del pp[1]
|
921
|
+
else:
|
922
|
+
r.extend(pp[0])
|
923
|
+
del pp[0]
|
924
|
+
r.extend(pp[0])
|
925
|
+
partials.extend([p,r])
|
926
|
+
return {'pieces': pieces.tostring(), 'places': places, 'partials': partials}
|
927
|
+
|
928
|
+
|
929
|
+
def unpickle(self, data, valid_places):
|
930
|
+
got = {}
|
931
|
+
places = {}
|
932
|
+
dirty = {}
|
933
|
+
download_history = {}
|
934
|
+
stat_active = {}
|
935
|
+
stat_numfound = self.stat_numfound
|
936
|
+
amount_obtained = self.amount_obtained
|
937
|
+
amount_inactive = self.amount_inactive
|
938
|
+
amount_left = self.amount_left
|
939
|
+
inactive_requests = [x for x in self.inactive_requests]
|
940
|
+
restored_partials = []
|
941
|
+
|
942
|
+
try:
|
943
|
+
if data['pieces'] == 1: # a seed
|
944
|
+
assert not data.get('places',None)
|
945
|
+
assert not data.get('partials',None)
|
946
|
+
have = Bitfield(len(self.hashes))
|
947
|
+
for i in xrange(len(self.hashes)):
|
948
|
+
have[i] = True
|
949
|
+
assert have.complete()
|
950
|
+
_places = []
|
951
|
+
_partials = []
|
952
|
+
else:
|
953
|
+
have = Bitfield(len(self.hashes), data['pieces'])
|
954
|
+
_places = data['places']
|
955
|
+
assert len(_places) % 2 == 0
|
956
|
+
_places = [_places[x:x+2] for x in xrange(0,len(_places),2)]
|
957
|
+
_partials = data['partials']
|
958
|
+
assert len(_partials) % 2 == 0
|
959
|
+
_partials = [_partials[x:x+2] for x in xrange(0,len(_partials),2)]
|
960
|
+
|
961
|
+
for index, place in _places:
|
962
|
+
if place not in valid_places:
|
963
|
+
continue
|
964
|
+
assert not got.has_key(index)
|
965
|
+
assert not got.has_key(place)
|
966
|
+
places[index] = place
|
967
|
+
got[index] = 1
|
968
|
+
got[place] = 1
|
969
|
+
|
970
|
+
for index in xrange(len(self.hashes)):
|
971
|
+
if have[index]:
|
972
|
+
if not places.has_key(index):
|
973
|
+
if index not in valid_places:
|
974
|
+
have[index] = False
|
975
|
+
continue
|
976
|
+
assert not got.has_key(index)
|
977
|
+
places[index] = index
|
978
|
+
got[index] = 1
|
979
|
+
length = self._piecelen(index)
|
980
|
+
amount_obtained += length
|
981
|
+
stat_numfound += 1
|
982
|
+
amount_inactive -= length
|
983
|
+
amount_left -= length
|
984
|
+
inactive_requests[index] = None
|
985
|
+
|
986
|
+
for index, plist in _partials:
|
987
|
+
assert not dirty.has_key(index)
|
988
|
+
assert not have[index]
|
989
|
+
if not places.has_key(index):
|
990
|
+
if index not in valid_places:
|
991
|
+
continue
|
992
|
+
assert not got.has_key(index)
|
993
|
+
places[index] = index
|
994
|
+
got[index] = 1
|
995
|
+
assert len(plist) % 2 == 0
|
996
|
+
plist = [plist[x:x+2] for x in xrange(0,len(plist),2)]
|
997
|
+
dirty[index] = plist
|
998
|
+
stat_active[index] = 1
|
999
|
+
download_history[index] = {}
|
1000
|
+
# invert given partials
|
1001
|
+
length = self._piecelen(index)
|
1002
|
+
l = []
|
1003
|
+
if plist[0][0] > 0:
|
1004
|
+
l.append((0,plist[0][0]))
|
1005
|
+
for i in xrange(len(plist)-1):
|
1006
|
+
end = plist[i][0]+plist[i][1]
|
1007
|
+
assert not end > plist[i+1][0]
|
1008
|
+
l.append((end,plist[i+1][0]-end))
|
1009
|
+
end = plist[-1][0]+plist[-1][1]
|
1010
|
+
assert not end > length
|
1011
|
+
if end < length:
|
1012
|
+
l.append((end,length-end))
|
1013
|
+
# split them to request_size
|
1014
|
+
ll = []
|
1015
|
+
amount_obtained += length
|
1016
|
+
amount_inactive -= length
|
1017
|
+
for nb, nl in l:
|
1018
|
+
while nl > 0:
|
1019
|
+
r = min(nl,self.request_size)
|
1020
|
+
ll.append((nb,r))
|
1021
|
+
amount_inactive += r
|
1022
|
+
amount_obtained -= r
|
1023
|
+
nb += self.request_size
|
1024
|
+
nl -= self.request_size
|
1025
|
+
inactive_requests[index] = ll
|
1026
|
+
restored_partials.append(index)
|
1027
|
+
|
1028
|
+
assert amount_obtained + amount_inactive == self.amount_desired
|
1029
|
+
except:
|
1030
|
+
# print_exc()
|
1031
|
+
return [] # invalid data, discard everything
|
1032
|
+
|
1033
|
+
self.have = have
|
1034
|
+
self.places = places
|
1035
|
+
self.dirty = dirty
|
1036
|
+
self.download_history = download_history
|
1037
|
+
self.stat_active = stat_active
|
1038
|
+
self.stat_numfound = stat_numfound
|
1039
|
+
self.amount_obtained = amount_obtained
|
1040
|
+
self.amount_inactive = amount_inactive
|
1041
|
+
self.amount_left = amount_left
|
1042
|
+
self.inactive_requests = inactive_requests
|
1043
|
+
|
1044
|
+
return restored_partials
|
1045
|
+
|