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,245 @@
|
|
1
|
+
# Written by John Hoffman
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from random import shuffle
|
5
|
+
from traceback import print_exc
|
6
|
+
try:
|
7
|
+
True
|
8
|
+
except:
|
9
|
+
True = 1
|
10
|
+
False = 0
|
11
|
+
|
12
|
+
|
13
|
+
class FileSelector:
|
14
|
+
def __init__(self, files, piece_length, bufferdir,
|
15
|
+
storage, storagewrapper, sched, failfunc):
|
16
|
+
self.files = files
|
17
|
+
self.storage = storage
|
18
|
+
self.storagewrapper = storagewrapper
|
19
|
+
self.sched = sched
|
20
|
+
self.failfunc = failfunc
|
21
|
+
self.downloader = None
|
22
|
+
self.picker = None
|
23
|
+
|
24
|
+
storage.set_bufferdir(bufferdir)
|
25
|
+
|
26
|
+
self.numfiles = len(files)
|
27
|
+
self.priority = [1] * self.numfiles
|
28
|
+
self.new_priority = None
|
29
|
+
self.new_partials = None
|
30
|
+
self.filepieces = []
|
31
|
+
total = 0L
|
32
|
+
for file, length in files:
|
33
|
+
if not length:
|
34
|
+
self.filepieces.append(())
|
35
|
+
else:
|
36
|
+
pieces = range( int(total/piece_length),
|
37
|
+
int((total+length-1)/piece_length)+1 )
|
38
|
+
self.filepieces.append(tuple(pieces))
|
39
|
+
total += length
|
40
|
+
self.numpieces = int((total+piece_length-1)/piece_length)
|
41
|
+
self.piece_priority = [1] * self.numpieces
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
def init_priority(self, new_priority):
|
46
|
+
try:
|
47
|
+
assert len(new_priority) == self.numfiles
|
48
|
+
for v in new_priority:
|
49
|
+
assert type(v) in (type(0),type(0L))
|
50
|
+
assert v >= -1
|
51
|
+
assert v <= 2
|
52
|
+
except:
|
53
|
+
# print_exc()
|
54
|
+
return False
|
55
|
+
try:
|
56
|
+
files_updated = False
|
57
|
+
for f in xrange(self.numfiles):
|
58
|
+
if new_priority[f] < 0:
|
59
|
+
self.storage.disable_file(f)
|
60
|
+
files_updated = True
|
61
|
+
if files_updated:
|
62
|
+
self.storage.reset_file_status()
|
63
|
+
self.new_priority = new_priority
|
64
|
+
except (IOError, OSError), e:
|
65
|
+
self.failfunc("can't open partial file for "
|
66
|
+
+ self.files[f][0] + ': ' + str(e))
|
67
|
+
return False
|
68
|
+
return True
|
69
|
+
|
70
|
+
'''
|
71
|
+
d['priority'] = [file #1 priority [,file #2 priority...] ]
|
72
|
+
a list of download priorities for each file.
|
73
|
+
Priority may be -1, 0, 1, 2. -1 = download disabled,
|
74
|
+
0 = highest, 1 = normal, 2 = lowest.
|
75
|
+
Also see Storage.pickle and StorageWrapper.pickle for additional keys.
|
76
|
+
'''
|
77
|
+
def unpickle(self, d):
|
78
|
+
if d.has_key('priority'):
|
79
|
+
if not self.init_priority(d['priority']):
|
80
|
+
return
|
81
|
+
pieces = self.storage.unpickle(d)
|
82
|
+
if not pieces: # don't bother, nothing restoreable
|
83
|
+
return
|
84
|
+
new_piece_priority = self._get_piece_priority_list(self.new_priority)
|
85
|
+
self.storagewrapper.reblock([i == -1 for i in new_piece_priority])
|
86
|
+
self.new_partials = self.storagewrapper.unpickle(d, pieces)
|
87
|
+
|
88
|
+
|
89
|
+
def tie_in(self, picker, cancelfunc, requestmorefunc, rerequestfunc):
|
90
|
+
self.picker = picker
|
91
|
+
self.cancelfunc = cancelfunc
|
92
|
+
self.requestmorefunc = requestmorefunc
|
93
|
+
self.rerequestfunc = rerequestfunc
|
94
|
+
|
95
|
+
if self.new_priority:
|
96
|
+
self.priority = self.new_priority
|
97
|
+
self.new_priority = None
|
98
|
+
self.new_piece_priority = self._set_piece_priority(self.priority)
|
99
|
+
|
100
|
+
if self.new_partials:
|
101
|
+
shuffle(self.new_partials)
|
102
|
+
for p in self.new_partials:
|
103
|
+
self.picker.requested(p)
|
104
|
+
self.new_partials = None
|
105
|
+
|
106
|
+
|
107
|
+
def _set_files_disabled(self, old_priority, new_priority):
|
108
|
+
old_disabled = [p == -1 for p in old_priority]
|
109
|
+
new_disabled = [p == -1 for p in new_priority]
|
110
|
+
data_to_update = []
|
111
|
+
for f in xrange(self.numfiles):
|
112
|
+
if new_disabled[f] != old_disabled[f]:
|
113
|
+
data_to_update.extend(self.storage.get_piece_update_list(f))
|
114
|
+
buffer = []
|
115
|
+
for piece, start, length in data_to_update:
|
116
|
+
if self.storagewrapper.has_data(piece):
|
117
|
+
data = self.storagewrapper.read_raw(piece, start, length)
|
118
|
+
if data is None:
|
119
|
+
return False
|
120
|
+
buffer.append((piece, start, data))
|
121
|
+
|
122
|
+
files_updated = False
|
123
|
+
try:
|
124
|
+
for f in xrange(self.numfiles):
|
125
|
+
if new_disabled[f] and not old_disabled[f]:
|
126
|
+
self.storage.disable_file(f)
|
127
|
+
files_updated = True
|
128
|
+
if old_disabled[f] and not new_disabled[f]:
|
129
|
+
self.storage.enable_file(f)
|
130
|
+
files_updated = True
|
131
|
+
except (IOError, OSError), e:
|
132
|
+
if new_disabled[f]:
|
133
|
+
msg = "can't open partial file for "
|
134
|
+
else:
|
135
|
+
msg = 'unable to open '
|
136
|
+
self.failfunc(msg + self.files[f][0] + ': ' + str(e))
|
137
|
+
return False
|
138
|
+
if files_updated:
|
139
|
+
self.storage.reset_file_status()
|
140
|
+
|
141
|
+
changed_pieces = {}
|
142
|
+
for piece, start, data in buffer:
|
143
|
+
if not self.storagewrapper.write_raw(piece, start, data):
|
144
|
+
return False
|
145
|
+
data.release()
|
146
|
+
changed_pieces[piece] = 1
|
147
|
+
if not self.storagewrapper.doublecheck_data(changed_pieces):
|
148
|
+
return False
|
149
|
+
|
150
|
+
return True
|
151
|
+
|
152
|
+
|
153
|
+
def _get_piece_priority_list(self, file_priority_list):
|
154
|
+
l = [-1] * self.numpieces
|
155
|
+
for f in xrange(self.numfiles):
|
156
|
+
if file_priority_list[f] == -1:
|
157
|
+
continue
|
158
|
+
for i in self.filepieces[f]:
|
159
|
+
if l[i] == -1:
|
160
|
+
l[i] = file_priority_list[f]
|
161
|
+
continue
|
162
|
+
l[i] = min(l[i],file_priority_list[f])
|
163
|
+
return l
|
164
|
+
|
165
|
+
|
166
|
+
def _set_piece_priority(self, new_priority):
|
167
|
+
was_complete = self.storagewrapper.am_I_complete()
|
168
|
+
new_piece_priority = self._get_piece_priority_list(new_priority)
|
169
|
+
pieces = range(self.numpieces)
|
170
|
+
shuffle(pieces)
|
171
|
+
new_blocked = []
|
172
|
+
new_unblocked = []
|
173
|
+
for piece in pieces:
|
174
|
+
self.picker.set_priority(piece,new_piece_priority[piece])
|
175
|
+
o = self.piece_priority[piece] == -1
|
176
|
+
n = new_piece_priority[piece] == -1
|
177
|
+
if n and not o:
|
178
|
+
new_blocked.append(piece)
|
179
|
+
if o and not n:
|
180
|
+
new_unblocked.append(piece)
|
181
|
+
if new_blocked:
|
182
|
+
self.cancelfunc(new_blocked)
|
183
|
+
self.storagewrapper.reblock([i == -1 for i in new_piece_priority])
|
184
|
+
if new_unblocked:
|
185
|
+
self.requestmorefunc(new_unblocked)
|
186
|
+
if was_complete and not self.storagewrapper.am_I_complete():
|
187
|
+
self.rerequestfunc()
|
188
|
+
|
189
|
+
return new_piece_priority
|
190
|
+
|
191
|
+
|
192
|
+
def set_priorities_now(self, new_priority = None):
|
193
|
+
if not new_priority:
|
194
|
+
new_priority = self.new_priority
|
195
|
+
self.new_priority = None # potential race condition
|
196
|
+
if not new_priority:
|
197
|
+
return
|
198
|
+
old_priority = self.priority
|
199
|
+
self.priority = new_priority
|
200
|
+
if not self._set_files_disabled(old_priority, new_priority):
|
201
|
+
return
|
202
|
+
self.piece_priority = self._set_piece_priority(new_priority)
|
203
|
+
|
204
|
+
def set_priorities(self, new_priority):
|
205
|
+
self.new_priority = new_priority
|
206
|
+
self.sched(self.set_priorities_now)
|
207
|
+
|
208
|
+
def set_priority(self, f, p):
|
209
|
+
new_priority = self.get_priorities()
|
210
|
+
new_priority[f] = p
|
211
|
+
self.set_priorities(new_priority)
|
212
|
+
|
213
|
+
def get_priorities(self):
|
214
|
+
priority = self.new_priority
|
215
|
+
if not priority:
|
216
|
+
priority = self.priority # potential race condition
|
217
|
+
return [i for i in priority]
|
218
|
+
|
219
|
+
def __setitem__(self, index, val):
|
220
|
+
self.set_priority(index, val)
|
221
|
+
|
222
|
+
def __getitem__(self, index):
|
223
|
+
try:
|
224
|
+
return self.new_priority[index]
|
225
|
+
except:
|
226
|
+
return self.priority[index]
|
227
|
+
|
228
|
+
|
229
|
+
def finish(self):
|
230
|
+
for f in xrange(self.numfiles):
|
231
|
+
if self.priority[f] == -1:
|
232
|
+
self.storage.delete_file(f)
|
233
|
+
|
234
|
+
def pickle(self):
|
235
|
+
d = {'priority': self.priority}
|
236
|
+
try:
|
237
|
+
s = self.storage.pickle()
|
238
|
+
sw = self.storagewrapper.pickle()
|
239
|
+
for k in s.keys():
|
240
|
+
d[k] = s[k]
|
241
|
+
for k in sw.keys():
|
242
|
+
d[k] = sw[k]
|
243
|
+
except (IOError, OSError):
|
244
|
+
pass
|
245
|
+
return d
|
@@ -0,0 +1,251 @@
|
|
1
|
+
# Written by John Hoffman
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from BitTornado.CurrentRateMeasure import Measure
|
5
|
+
from random import randint
|
6
|
+
from urlparse import urlparse
|
7
|
+
from httplib import HTTPConnection
|
8
|
+
from urllib import quote
|
9
|
+
from threading import Thread
|
10
|
+
from BitTornado.__init__ import product_name,version_short
|
11
|
+
try:
|
12
|
+
True
|
13
|
+
except:
|
14
|
+
True = 1
|
15
|
+
False = 0
|
16
|
+
|
17
|
+
EXPIRE_TIME = 60 * 60
|
18
|
+
|
19
|
+
VERSION = product_name+'/'+version_short
|
20
|
+
|
21
|
+
class haveComplete:
|
22
|
+
def complete(self):
|
23
|
+
return True
|
24
|
+
def __getitem__(self, x):
|
25
|
+
return True
|
26
|
+
haveall = haveComplete()
|
27
|
+
|
28
|
+
class SingleDownload:
|
29
|
+
def __init__(self, downloader, url):
|
30
|
+
self.downloader = downloader
|
31
|
+
self.baseurl = url
|
32
|
+
try:
|
33
|
+
(scheme, self.netloc, path, pars, query, fragment) = urlparse(url)
|
34
|
+
except:
|
35
|
+
self.downloader.errorfunc('cannot parse http seed address: '+url)
|
36
|
+
return
|
37
|
+
if scheme != 'http':
|
38
|
+
self.downloader.errorfunc('http seed url not http: '+url)
|
39
|
+
return
|
40
|
+
try:
|
41
|
+
self.connection = HTTPConnection(self.netloc)
|
42
|
+
except:
|
43
|
+
self.downloader.errorfunc('cannot connect to http seed: '+url)
|
44
|
+
return
|
45
|
+
self.seedurl = path
|
46
|
+
if pars:
|
47
|
+
self.seedurl += ';'+pars
|
48
|
+
self.seedurl += '?'
|
49
|
+
if query:
|
50
|
+
self.seedurl += query+'&'
|
51
|
+
self.seedurl += 'info_hash='+quote(self.downloader.infohash)
|
52
|
+
|
53
|
+
self.measure = Measure(downloader.max_rate_period)
|
54
|
+
self.index = None
|
55
|
+
self.url = ''
|
56
|
+
self.requests = []
|
57
|
+
self.request_size = 0
|
58
|
+
self.endflag = False
|
59
|
+
self.error = None
|
60
|
+
self.retry_period = 30
|
61
|
+
self._retry_period = None
|
62
|
+
self.errorcount = 0
|
63
|
+
self.goodseed = False
|
64
|
+
self.active = False
|
65
|
+
self.cancelled = False
|
66
|
+
self.resched(randint(2,10))
|
67
|
+
|
68
|
+
def resched(self, len = None):
|
69
|
+
if len is None:
|
70
|
+
len = self.retry_period
|
71
|
+
if self.errorcount > 3:
|
72
|
+
len = len * (self.errorcount - 2)
|
73
|
+
self.downloader.rawserver.add_task(self.download, len)
|
74
|
+
|
75
|
+
def _want(self, index):
|
76
|
+
if self.endflag:
|
77
|
+
return self.downloader.storage.do_I_have_requests(index)
|
78
|
+
else:
|
79
|
+
return self.downloader.storage.is_unstarted(index)
|
80
|
+
|
81
|
+
def download(self):
|
82
|
+
self.cancelled = False
|
83
|
+
if self.downloader.picker.am_I_complete():
|
84
|
+
self.downloader.downloads.remove(self)
|
85
|
+
return
|
86
|
+
self.index = self.downloader.picker.next(haveall, self._want)
|
87
|
+
if ( self.index is None and not self.endflag
|
88
|
+
and not self.downloader.peerdownloader.has_downloaders() ):
|
89
|
+
self.endflag = True
|
90
|
+
self.index = self.downloader.picker.next(haveall, self._want)
|
91
|
+
if self.index is None:
|
92
|
+
self.endflag = True
|
93
|
+
self.resched()
|
94
|
+
else:
|
95
|
+
self.url = ( self.seedurl+'&piece='+str(self.index) )
|
96
|
+
self._get_requests()
|
97
|
+
if self.request_size < self.downloader.storage._piecelen(self.index):
|
98
|
+
self.url += '&ranges='+self._request_ranges()
|
99
|
+
rq = Thread(target = self._request)
|
100
|
+
rq.setDaemon(False)
|
101
|
+
rq.start()
|
102
|
+
self.active = True
|
103
|
+
|
104
|
+
def _request(self):
|
105
|
+
import encodings.ascii
|
106
|
+
import encodings.punycode
|
107
|
+
import encodings.idna
|
108
|
+
|
109
|
+
self.error = None
|
110
|
+
self.received_data = None
|
111
|
+
try:
|
112
|
+
self.connection.request('GET',self.url, None,
|
113
|
+
{'User-Agent': VERSION})
|
114
|
+
r = self.connection.getresponse()
|
115
|
+
self.connection_status = r.status
|
116
|
+
self.received_data = r.read()
|
117
|
+
except Exception, e:
|
118
|
+
self.error = 'error accessing http seed: '+str(e)
|
119
|
+
try:
|
120
|
+
self.connection.close()
|
121
|
+
except:
|
122
|
+
pass
|
123
|
+
try:
|
124
|
+
self.connection = HTTPConnection(self.netloc)
|
125
|
+
except:
|
126
|
+
self.connection = None # will cause an exception and retry next cycle
|
127
|
+
self.downloader.rawserver.add_task(self.request_finished)
|
128
|
+
|
129
|
+
def request_finished(self):
|
130
|
+
self.active = False
|
131
|
+
if self.error is not None:
|
132
|
+
if self.goodseed:
|
133
|
+
self.downloader.errorfunc(self.error)
|
134
|
+
self.errorcount += 1
|
135
|
+
if self.received_data:
|
136
|
+
self.errorcount = 0
|
137
|
+
if not self._got_data():
|
138
|
+
self.received_data = None
|
139
|
+
if not self.received_data:
|
140
|
+
self._release_requests()
|
141
|
+
self.downloader.peerdownloader.piece_flunked(self.index)
|
142
|
+
if self._retry_period:
|
143
|
+
self.resched(self._retry_period)
|
144
|
+
self._retry_period = None
|
145
|
+
return
|
146
|
+
self.resched()
|
147
|
+
|
148
|
+
def _got_data(self):
|
149
|
+
if self.connection_status == 503: # seed is busy
|
150
|
+
try:
|
151
|
+
self.retry_period = max(int(self.received_data),5)
|
152
|
+
except:
|
153
|
+
pass
|
154
|
+
return False
|
155
|
+
if self.connection_status != 200:
|
156
|
+
self.errorcount += 1
|
157
|
+
return False
|
158
|
+
self._retry_period = 1
|
159
|
+
if len(self.received_data) != self.request_size:
|
160
|
+
if self.goodseed:
|
161
|
+
self.downloader.errorfunc('corrupt data from http seed - redownloading')
|
162
|
+
return False
|
163
|
+
self.measure.update_rate(len(self.received_data))
|
164
|
+
self.downloader.measurefunc(len(self.received_data))
|
165
|
+
if self.cancelled:
|
166
|
+
return False
|
167
|
+
if not self._fulfill_requests():
|
168
|
+
return False
|
169
|
+
if not self.goodseed:
|
170
|
+
self.goodseed = True
|
171
|
+
self.downloader.seedsfound += 1
|
172
|
+
if self.downloader.storage.do_I_have(self.index):
|
173
|
+
self.downloader.picker.complete(self.index)
|
174
|
+
self.downloader.peerdownloader.check_complete(self.index)
|
175
|
+
self.downloader.gotpiecefunc(self.index)
|
176
|
+
return True
|
177
|
+
|
178
|
+
def _get_requests(self):
|
179
|
+
self.requests = []
|
180
|
+
self.request_size = 0L
|
181
|
+
while self.downloader.storage.do_I_have_requests(self.index):
|
182
|
+
r = self.downloader.storage.new_request(self.index)
|
183
|
+
self.requests.append(r)
|
184
|
+
self.request_size += r[1]
|
185
|
+
self.requests.sort()
|
186
|
+
|
187
|
+
def _fulfill_requests(self):
|
188
|
+
start = 0L
|
189
|
+
success = True
|
190
|
+
while self.requests:
|
191
|
+
begin, length = self.requests.pop(0)
|
192
|
+
if not self.downloader.storage.piece_came_in(self.index, begin,
|
193
|
+
self.received_data[start:start+length]):
|
194
|
+
success = False
|
195
|
+
break
|
196
|
+
start += length
|
197
|
+
return success
|
198
|
+
|
199
|
+
def _release_requests(self):
|
200
|
+
for begin, length in self.requests:
|
201
|
+
self.downloader.storage.request_lost(self.index, begin, length)
|
202
|
+
self.requests = []
|
203
|
+
|
204
|
+
def _request_ranges(self):
|
205
|
+
s = ''
|
206
|
+
begin, length = self.requests[0]
|
207
|
+
for begin1, length1 in self.requests[1:]:
|
208
|
+
if begin + length == begin1:
|
209
|
+
length += length1
|
210
|
+
continue
|
211
|
+
else:
|
212
|
+
if s:
|
213
|
+
s += ','
|
214
|
+
s += str(begin)+'-'+str(begin+length-1)
|
215
|
+
begin, length = begin1, length1
|
216
|
+
if s:
|
217
|
+
s += ','
|
218
|
+
s += str(begin)+'-'+str(begin+length-1)
|
219
|
+
return s
|
220
|
+
|
221
|
+
|
222
|
+
class HTTPDownloader:
|
223
|
+
def __init__(self, storage, picker, rawserver,
|
224
|
+
finflag, errorfunc, peerdownloader,
|
225
|
+
max_rate_period, infohash, measurefunc, gotpiecefunc):
|
226
|
+
self.storage = storage
|
227
|
+
self.picker = picker
|
228
|
+
self.rawserver = rawserver
|
229
|
+
self.finflag = finflag
|
230
|
+
self.errorfunc = errorfunc
|
231
|
+
self.peerdownloader = peerdownloader
|
232
|
+
self.infohash = infohash
|
233
|
+
self.max_rate_period = max_rate_period
|
234
|
+
self.gotpiecefunc = gotpiecefunc
|
235
|
+
self.measurefunc = measurefunc
|
236
|
+
self.downloads = []
|
237
|
+
self.seedsfound = 0
|
238
|
+
|
239
|
+
def make_download(self, url):
|
240
|
+
self.downloads.append(SingleDownload(self, url))
|
241
|
+
return self.downloads[-1]
|
242
|
+
|
243
|
+
def get_downloads(self):
|
244
|
+
if self.finflag.isSet():
|
245
|
+
return []
|
246
|
+
return self.downloads
|
247
|
+
|
248
|
+
def cancel_piece_download(self, pieces):
|
249
|
+
for d in self.downloads:
|
250
|
+
if d.active and d.index in pieces:
|
251
|
+
d.cancelled = True
|