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,95 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from cStringIO import StringIO
|
5
|
+
from socket import error as socketerror
|
6
|
+
from traceback import print_exc
|
7
|
+
try:
|
8
|
+
True
|
9
|
+
except:
|
10
|
+
True = 1
|
11
|
+
False = 0
|
12
|
+
|
13
|
+
protocol_name = 'BitTorrent protocol'
|
14
|
+
|
15
|
+
# header, reserved, download id, my id, [length, message]
|
16
|
+
|
17
|
+
class NatCheck:
|
18
|
+
def __init__(self, resultfunc, downloadid, peerid, ip, port, rawserver):
|
19
|
+
self.resultfunc = resultfunc
|
20
|
+
self.downloadid = downloadid
|
21
|
+
self.peerid = peerid
|
22
|
+
self.ip = ip
|
23
|
+
self.port = port
|
24
|
+
self.closed = False
|
25
|
+
self.buffer = StringIO()
|
26
|
+
self.next_len = 1
|
27
|
+
self.next_func = self.read_header_len
|
28
|
+
try:
|
29
|
+
self.connection = rawserver.start_connection((ip, port), self)
|
30
|
+
self.connection.write(chr(len(protocol_name)) + protocol_name +
|
31
|
+
(chr(0) * 8) + downloadid)
|
32
|
+
except socketerror:
|
33
|
+
self.answer(False)
|
34
|
+
except IOError:
|
35
|
+
self.answer(False)
|
36
|
+
|
37
|
+
def answer(self, result):
|
38
|
+
self.closed = True
|
39
|
+
try:
|
40
|
+
self.connection.close()
|
41
|
+
except AttributeError:
|
42
|
+
pass
|
43
|
+
self.resultfunc(result, self.downloadid, self.peerid, self.ip, self.port)
|
44
|
+
|
45
|
+
def read_header_len(self, s):
|
46
|
+
if ord(s) != len(protocol_name):
|
47
|
+
return None
|
48
|
+
return len(protocol_name), self.read_header
|
49
|
+
|
50
|
+
def read_header(self, s):
|
51
|
+
if s != protocol_name:
|
52
|
+
return None
|
53
|
+
return 8, self.read_reserved
|
54
|
+
|
55
|
+
def read_reserved(self, s):
|
56
|
+
return 20, self.read_download_id
|
57
|
+
|
58
|
+
def read_download_id(self, s):
|
59
|
+
if s != self.downloadid:
|
60
|
+
return None
|
61
|
+
return 20, self.read_peer_id
|
62
|
+
|
63
|
+
def read_peer_id(self, s):
|
64
|
+
if s != self.peerid:
|
65
|
+
return None
|
66
|
+
self.answer(True)
|
67
|
+
return None
|
68
|
+
|
69
|
+
def data_came_in(self, connection, s):
|
70
|
+
while True:
|
71
|
+
if self.closed:
|
72
|
+
return
|
73
|
+
i = self.next_len - self.buffer.tell()
|
74
|
+
if i > len(s):
|
75
|
+
self.buffer.write(s)
|
76
|
+
return
|
77
|
+
self.buffer.write(s[:i])
|
78
|
+
s = s[i:]
|
79
|
+
m = self.buffer.getvalue()
|
80
|
+
self.buffer.reset()
|
81
|
+
self.buffer.truncate()
|
82
|
+
x = self.next_func(m)
|
83
|
+
if x is None:
|
84
|
+
if not self.closed:
|
85
|
+
self.answer(False)
|
86
|
+
return
|
87
|
+
self.next_len, self.next_func = x
|
88
|
+
|
89
|
+
def connection_lost(self, connection):
|
90
|
+
if not self.closed:
|
91
|
+
self.closed = True
|
92
|
+
self.resultfunc(False, self.downloadid, self.peerid, self.ip, self.port)
|
93
|
+
|
94
|
+
def connection_flushed(self, connection):
|
95
|
+
pass
|
@@ -0,0 +1,320 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from random import randrange, shuffle
|
5
|
+
from BitTornado.clock import clock
|
6
|
+
try:
|
7
|
+
True
|
8
|
+
except:
|
9
|
+
True = 1
|
10
|
+
False = 0
|
11
|
+
|
12
|
+
class PiecePicker:
|
13
|
+
def __init__(self, numpieces,
|
14
|
+
rarest_first_cutoff = 1, rarest_first_priority_cutoff = 3,
|
15
|
+
priority_step = 20):
|
16
|
+
self.rarest_first_cutoff = rarest_first_cutoff
|
17
|
+
self.rarest_first_priority_cutoff = rarest_first_priority_cutoff + priority_step
|
18
|
+
self.priority_step = priority_step
|
19
|
+
self.cutoff = rarest_first_priority_cutoff
|
20
|
+
self.numpieces = numpieces
|
21
|
+
self.started = []
|
22
|
+
self.totalcount = 0
|
23
|
+
self.numhaves = [0] * numpieces
|
24
|
+
self.priority = [1] * numpieces
|
25
|
+
self.removed_partials = {}
|
26
|
+
self.crosscount = [numpieces]
|
27
|
+
self.crosscount2 = [numpieces]
|
28
|
+
self.has = [0] * numpieces
|
29
|
+
self.numgot = 0
|
30
|
+
self.done = False
|
31
|
+
self.seed_connections = {}
|
32
|
+
self.past_ips = {}
|
33
|
+
self.seed_time = None
|
34
|
+
self.superseed = False
|
35
|
+
self.seeds_connected = 0
|
36
|
+
self._init_interests()
|
37
|
+
|
38
|
+
def _init_interests(self):
|
39
|
+
self.interests = [[] for x in xrange(self.priority_step)]
|
40
|
+
self.level_in_interests = [self.priority_step] * self.numpieces
|
41
|
+
interests = range(self.numpieces)
|
42
|
+
shuffle(interests)
|
43
|
+
self.pos_in_interests = [0] * self.numpieces
|
44
|
+
for i in xrange(self.numpieces):
|
45
|
+
self.pos_in_interests[interests[i]] = i
|
46
|
+
self.interests.append(interests)
|
47
|
+
|
48
|
+
|
49
|
+
def got_have(self, piece):
|
50
|
+
self.totalcount+=1
|
51
|
+
numint = self.numhaves[piece]
|
52
|
+
self.numhaves[piece] += 1
|
53
|
+
self.crosscount[numint] -= 1
|
54
|
+
if numint+1==len(self.crosscount):
|
55
|
+
self.crosscount.append(0)
|
56
|
+
self.crosscount[numint+1] += 1
|
57
|
+
if not self.done:
|
58
|
+
numintplus = numint+self.has[piece]
|
59
|
+
self.crosscount2[numintplus] -= 1
|
60
|
+
if numintplus+1 == len(self.crosscount2):
|
61
|
+
self.crosscount2.append(0)
|
62
|
+
self.crosscount2[numintplus+1] += 1
|
63
|
+
numint = self.level_in_interests[piece]
|
64
|
+
self.level_in_interests[piece] += 1
|
65
|
+
if self.superseed:
|
66
|
+
self.seed_got_haves[piece] += 1
|
67
|
+
numint = self.level_in_interests[piece]
|
68
|
+
self.level_in_interests[piece] += 1
|
69
|
+
elif self.has[piece] or self.priority[piece] == -1:
|
70
|
+
return
|
71
|
+
if numint == len(self.interests) - 1:
|
72
|
+
self.interests.append([])
|
73
|
+
self._shift_over(piece, self.interests[numint], self.interests[numint + 1])
|
74
|
+
|
75
|
+
def lost_have(self, piece):
|
76
|
+
self.totalcount-=1
|
77
|
+
numint = self.numhaves[piece]
|
78
|
+
self.numhaves[piece] -= 1
|
79
|
+
self.crosscount[numint] -= 1
|
80
|
+
self.crosscount[numint-1] += 1
|
81
|
+
if not self.done:
|
82
|
+
numintplus = numint+self.has[piece]
|
83
|
+
self.crosscount2[numintplus] -= 1
|
84
|
+
self.crosscount2[numintplus-1] += 1
|
85
|
+
numint = self.level_in_interests[piece]
|
86
|
+
self.level_in_interests[piece] -= 1
|
87
|
+
if self.superseed:
|
88
|
+
numint = self.level_in_interests[piece]
|
89
|
+
self.level_in_interests[piece] -= 1
|
90
|
+
elif self.has[piece] or self.priority[piece] == -1:
|
91
|
+
return
|
92
|
+
self._shift_over(piece, self.interests[numint], self.interests[numint - 1])
|
93
|
+
|
94
|
+
def _shift_over(self, piece, l1, l2):
|
95
|
+
assert self.superseed or (not self.has[piece] and self.priority[piece] >= 0)
|
96
|
+
parray = self.pos_in_interests
|
97
|
+
p = parray[piece]
|
98
|
+
assert l1[p] == piece
|
99
|
+
q = l1[-1]
|
100
|
+
l1[p] = q
|
101
|
+
parray[q] = p
|
102
|
+
del l1[-1]
|
103
|
+
newp = randrange(len(l2)+1)
|
104
|
+
if newp == len(l2):
|
105
|
+
parray[piece] = len(l2)
|
106
|
+
l2.append(piece)
|
107
|
+
else:
|
108
|
+
old = l2[newp]
|
109
|
+
parray[old] = len(l2)
|
110
|
+
l2.append(old)
|
111
|
+
l2[newp] = piece
|
112
|
+
parray[piece] = newp
|
113
|
+
|
114
|
+
|
115
|
+
def got_seed(self):
|
116
|
+
self.seeds_connected += 1
|
117
|
+
self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)
|
118
|
+
|
119
|
+
def became_seed(self):
|
120
|
+
self.got_seed()
|
121
|
+
self.totalcount -= self.numpieces
|
122
|
+
self.numhaves = [i-1 for i in self.numhaves]
|
123
|
+
if self.superseed or not self.done:
|
124
|
+
self.level_in_interests = [i-1 for i in self.level_in_interests]
|
125
|
+
if self.interests:
|
126
|
+
del self.interests[0]
|
127
|
+
del self.crosscount[0]
|
128
|
+
if not self.done:
|
129
|
+
del self.crosscount2[0]
|
130
|
+
|
131
|
+
def lost_seed(self):
|
132
|
+
self.seeds_connected -= 1
|
133
|
+
self.cutoff = max(self.rarest_first_priority_cutoff-self.seeds_connected,0)
|
134
|
+
|
135
|
+
|
136
|
+
def requested(self, piece):
|
137
|
+
if piece not in self.started:
|
138
|
+
self.started.append(piece)
|
139
|
+
|
140
|
+
def _remove_from_interests(self, piece, keep_partial = False):
|
141
|
+
l = self.interests[self.level_in_interests[piece]]
|
142
|
+
p = self.pos_in_interests[piece]
|
143
|
+
assert l[p] == piece
|
144
|
+
q = l[-1]
|
145
|
+
l[p] = q
|
146
|
+
self.pos_in_interests[q] = p
|
147
|
+
del l[-1]
|
148
|
+
try:
|
149
|
+
self.started.remove(piece)
|
150
|
+
if keep_partial:
|
151
|
+
self.removed_partials[piece] = 1
|
152
|
+
except ValueError:
|
153
|
+
pass
|
154
|
+
|
155
|
+
def complete(self, piece):
|
156
|
+
assert not self.has[piece]
|
157
|
+
self.has[piece] = 1
|
158
|
+
self.numgot += 1
|
159
|
+
if self.numgot == self.numpieces:
|
160
|
+
self.done = True
|
161
|
+
self.crosscount2 = self.crosscount
|
162
|
+
else:
|
163
|
+
numhaves = self.numhaves[piece]
|
164
|
+
self.crosscount2[numhaves] -= 1
|
165
|
+
if numhaves+1 == len(self.crosscount2):
|
166
|
+
self.crosscount2.append(0)
|
167
|
+
self.crosscount2[numhaves+1] += 1
|
168
|
+
self._remove_from_interests(piece)
|
169
|
+
|
170
|
+
|
171
|
+
def next(self, haves, wantfunc, complete_first = False):
|
172
|
+
cutoff = self.numgot < self.rarest_first_cutoff
|
173
|
+
complete_first = (complete_first or cutoff) and not haves.complete()
|
174
|
+
best = None
|
175
|
+
bestnum = 2 ** 30
|
176
|
+
for i in self.started:
|
177
|
+
if haves[i] and wantfunc(i):
|
178
|
+
if self.level_in_interests[i] < bestnum:
|
179
|
+
best = i
|
180
|
+
bestnum = self.level_in_interests[i]
|
181
|
+
if best is not None:
|
182
|
+
if complete_first or (cutoff and len(self.interests) > self.cutoff):
|
183
|
+
return best
|
184
|
+
if haves.complete():
|
185
|
+
r = [ (0, min(bestnum,len(self.interests))) ]
|
186
|
+
elif cutoff and len(self.interests) > self.cutoff:
|
187
|
+
r = [ (self.cutoff, min(bestnum,len(self.interests))),
|
188
|
+
(0, self.cutoff) ]
|
189
|
+
else:
|
190
|
+
r = [ (0, min(bestnum,len(self.interests))) ]
|
191
|
+
for lo,hi in r:
|
192
|
+
for i in xrange(lo,hi):
|
193
|
+
for j in self.interests[i]:
|
194
|
+
if haves[j] and wantfunc(j):
|
195
|
+
return j
|
196
|
+
if best is not None:
|
197
|
+
return best
|
198
|
+
return None
|
199
|
+
|
200
|
+
|
201
|
+
def am_I_complete(self):
|
202
|
+
return self.done
|
203
|
+
|
204
|
+
def bump(self, piece):
|
205
|
+
l = self.interests[self.level_in_interests[piece]]
|
206
|
+
pos = self.pos_in_interests[piece]
|
207
|
+
del l[pos]
|
208
|
+
l.append(piece)
|
209
|
+
for i in range(pos,len(l)):
|
210
|
+
self.pos_in_interests[l[i]] = i
|
211
|
+
try:
|
212
|
+
self.started.remove(piece)
|
213
|
+
except:
|
214
|
+
pass
|
215
|
+
|
216
|
+
def set_priority(self, piece, p):
|
217
|
+
if self.superseed:
|
218
|
+
return False # don't muck with this if you're a superseed
|
219
|
+
oldp = self.priority[piece]
|
220
|
+
if oldp == p:
|
221
|
+
return False
|
222
|
+
self.priority[piece] = p
|
223
|
+
if p == -1:
|
224
|
+
# when setting priority -1,
|
225
|
+
# make sure to cancel any downloads for this piece
|
226
|
+
if not self.has[piece]:
|
227
|
+
self._remove_from_interests(piece, True)
|
228
|
+
return True
|
229
|
+
if oldp == -1:
|
230
|
+
level = self.numhaves[piece] + (self.priority_step * p)
|
231
|
+
self.level_in_interests[piece] = level
|
232
|
+
if self.has[piece]:
|
233
|
+
return True
|
234
|
+
while len(self.interests) < level+1:
|
235
|
+
self.interests.append([])
|
236
|
+
l2 = self.interests[level]
|
237
|
+
parray = self.pos_in_interests
|
238
|
+
newp = randrange(len(l2)+1)
|
239
|
+
if newp == len(l2):
|
240
|
+
parray[piece] = len(l2)
|
241
|
+
l2.append(piece)
|
242
|
+
else:
|
243
|
+
old = l2[newp]
|
244
|
+
parray[old] = len(l2)
|
245
|
+
l2.append(old)
|
246
|
+
l2[newp] = piece
|
247
|
+
parray[piece] = newp
|
248
|
+
if self.removed_partials.has_key(piece):
|
249
|
+
del self.removed_partials[piece]
|
250
|
+
self.started.append(piece)
|
251
|
+
# now go to downloader and try requesting more
|
252
|
+
return True
|
253
|
+
numint = self.level_in_interests[piece]
|
254
|
+
newint = numint + ((p - oldp) * self.priority_step)
|
255
|
+
self.level_in_interests[piece] = newint
|
256
|
+
if self.has[piece]:
|
257
|
+
return False
|
258
|
+
while len(self.interests) < newint+1:
|
259
|
+
self.interests.append([])
|
260
|
+
self._shift_over(piece, self.interests[numint], self.interests[newint])
|
261
|
+
return False
|
262
|
+
|
263
|
+
def is_blocked(self, piece):
|
264
|
+
return self.priority[piece] < 0
|
265
|
+
|
266
|
+
|
267
|
+
def set_superseed(self):
|
268
|
+
assert self.done
|
269
|
+
self.superseed = True
|
270
|
+
self.seed_got_haves = [0] * self.numpieces
|
271
|
+
self._init_interests() # assume everyone is disconnected
|
272
|
+
|
273
|
+
def next_have(self, connection, looser_upload):
|
274
|
+
if self.seed_time is None:
|
275
|
+
self.seed_time = clock()
|
276
|
+
return None
|
277
|
+
if clock() < self.seed_time+10: # wait 10 seconds after seeing the first peers
|
278
|
+
return None # to give time to grab have lists
|
279
|
+
if not connection.upload.super_seeding:
|
280
|
+
return None
|
281
|
+
olddl = self.seed_connections.get(connection)
|
282
|
+
if olddl is None:
|
283
|
+
ip = connection.get_ip()
|
284
|
+
olddl = self.past_ips.get(ip)
|
285
|
+
if olddl is not None: # peer reconnected
|
286
|
+
self.seed_connections[connection] = olddl
|
287
|
+
if olddl is not None:
|
288
|
+
if looser_upload:
|
289
|
+
num = 1 # send a new have even if it hasn't spread that piece elsewhere
|
290
|
+
else:
|
291
|
+
num = 2
|
292
|
+
if self.seed_got_haves[olddl] < num:
|
293
|
+
return None
|
294
|
+
if not connection.upload.was_ever_interested: # it never downloaded it?
|
295
|
+
connection.upload.skipped_count += 1
|
296
|
+
if connection.upload.skipped_count >= 3: # probably another stealthed seed
|
297
|
+
return -1 # signal to close it
|
298
|
+
for tier in self.interests:
|
299
|
+
for piece in tier:
|
300
|
+
if not connection.download.have[piece]:
|
301
|
+
seedint = self.level_in_interests[piece]
|
302
|
+
self.level_in_interests[piece] += 1 # tweak it up one, so you don't duplicate effort
|
303
|
+
if seedint == len(self.interests) - 1:
|
304
|
+
self.interests.append([])
|
305
|
+
self._shift_over(piece,
|
306
|
+
self.interests[seedint], self.interests[seedint + 1])
|
307
|
+
self.seed_got_haves[piece] = 0 # reset this
|
308
|
+
self.seed_connections[connection] = piece
|
309
|
+
connection.upload.seed_have_list.append(piece)
|
310
|
+
return piece
|
311
|
+
return -1 # something screwy; terminate connection
|
312
|
+
|
313
|
+
def lost_peer(self, connection):
|
314
|
+
olddl = self.seed_connections.get(connection)
|
315
|
+
if olddl is None:
|
316
|
+
return
|
317
|
+
del self.seed_connections[connection]
|
318
|
+
self.past_ips[connection.get_ip()] = olddl
|
319
|
+
if self.seed_got_haves[olddl] == 1:
|
320
|
+
self.seed_got_haves[olddl] = 0
|
@@ -0,0 +1,426 @@
|
|
1
|
+
# Written by Bram Cohen
|
2
|
+
# modified for multitracker operation by John Hoffman
|
3
|
+
# see LICENSE.txt for license information
|
4
|
+
|
5
|
+
from BitTornado.zurllib import urlopen, quote
|
6
|
+
from urlparse import urlparse, urlunparse
|
7
|
+
from socket import gethostbyname
|
8
|
+
from btformats import check_peers
|
9
|
+
from BitTornado.bencode import bdecode
|
10
|
+
from threading import Thread, Lock
|
11
|
+
from cStringIO import StringIO
|
12
|
+
from traceback import print_exc
|
13
|
+
from socket import error, gethostbyname
|
14
|
+
from random import shuffle
|
15
|
+
from sha import sha
|
16
|
+
from time import time
|
17
|
+
try:
|
18
|
+
from os import getpid
|
19
|
+
except ImportError:
|
20
|
+
def getpid():
|
21
|
+
return 1
|
22
|
+
|
23
|
+
try:
|
24
|
+
True
|
25
|
+
except:
|
26
|
+
True = 1
|
27
|
+
False = 0
|
28
|
+
|
29
|
+
mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-'
|
30
|
+
keys = {}
|
31
|
+
basekeydata = str(getpid()) + repr(time()) + 'tracker'
|
32
|
+
|
33
|
+
def add_key(tracker):
|
34
|
+
key = ''
|
35
|
+
for i in sha(basekeydata+tracker).digest()[-6:]:
|
36
|
+
key += mapbase64[ord(i) & 0x3F]
|
37
|
+
keys[tracker] = key
|
38
|
+
|
39
|
+
def get_key(tracker):
|
40
|
+
try:
|
41
|
+
return "&key="+keys[tracker]
|
42
|
+
except:
|
43
|
+
add_key(tracker)
|
44
|
+
return "&key="+keys[tracker]
|
45
|
+
|
46
|
+
class fakeflag:
|
47
|
+
def __init__(self, state=False):
|
48
|
+
self.state = state
|
49
|
+
def wait(self):
|
50
|
+
pass
|
51
|
+
def isSet(self):
|
52
|
+
return self.state
|
53
|
+
|
54
|
+
class Rerequester:
|
55
|
+
def __init__(self, trackerlist, interval, sched, howmany, minpeers,
|
56
|
+
connect, externalsched, amount_left, up, down,
|
57
|
+
port, ip, myid, infohash, timeout, errorfunc, excfunc,
|
58
|
+
maxpeers, doneflag, upratefunc, downratefunc,
|
59
|
+
unpauseflag = fakeflag(True),
|
60
|
+
seed_id = '', seededfunc = None, force_rapid_update = False ):
|
61
|
+
|
62
|
+
self.excfunc = excfunc
|
63
|
+
newtrackerlist = []
|
64
|
+
for tier in trackerlist:
|
65
|
+
if len(tier)>1:
|
66
|
+
shuffle(tier)
|
67
|
+
newtrackerlist += [tier]
|
68
|
+
self.trackerlist = newtrackerlist
|
69
|
+
self.lastsuccessful = ''
|
70
|
+
self.rejectedmessage = 'rejected by tracker - '
|
71
|
+
|
72
|
+
self.url = ('?info_hash=%s&peer_id=%s&port=%s' %
|
73
|
+
(quote(infohash), quote(myid), str(port)))
|
74
|
+
self.ip = ip
|
75
|
+
self.interval = interval
|
76
|
+
self.last = None
|
77
|
+
self.trackerid = None
|
78
|
+
self.announce_interval = 30 * 60
|
79
|
+
self.sched = sched
|
80
|
+
self.howmany = howmany
|
81
|
+
self.minpeers = minpeers
|
82
|
+
self.connect = connect
|
83
|
+
self.externalsched = externalsched
|
84
|
+
self.amount_left = amount_left
|
85
|
+
self.up = up
|
86
|
+
self.down = down
|
87
|
+
self.timeout = timeout
|
88
|
+
self.errorfunc = errorfunc
|
89
|
+
self.maxpeers = maxpeers
|
90
|
+
self.doneflag = doneflag
|
91
|
+
self.upratefunc = upratefunc
|
92
|
+
self.downratefunc = downratefunc
|
93
|
+
self.unpauseflag = unpauseflag
|
94
|
+
if seed_id:
|
95
|
+
self.url += '&seed_id='+quote(seed_id)
|
96
|
+
self.seededfunc = seededfunc
|
97
|
+
if seededfunc:
|
98
|
+
self.url += '&check_seeded=1'
|
99
|
+
self.force_rapid_update = force_rapid_update
|
100
|
+
self.last_failed = True
|
101
|
+
self.never_succeeded = True
|
102
|
+
self.errorcodes = {}
|
103
|
+
self.lock = SuccessLock()
|
104
|
+
self.special = None
|
105
|
+
self.stopped = False
|
106
|
+
|
107
|
+
def start(self):
|
108
|
+
self.sched(self.c, self.interval/2)
|
109
|
+
self.d(0)
|
110
|
+
|
111
|
+
def c(self):
|
112
|
+
if self.stopped:
|
113
|
+
return
|
114
|
+
if not self.unpauseflag.isSet() and (
|
115
|
+
self.howmany() < self.minpeers or self.force_rapid_update ):
|
116
|
+
self.announce(3, self._c)
|
117
|
+
else:
|
118
|
+
self._c()
|
119
|
+
|
120
|
+
def _c(self):
|
121
|
+
self.sched(self.c, self.interval)
|
122
|
+
|
123
|
+
def d(self, event = 3):
|
124
|
+
if self.stopped:
|
125
|
+
return
|
126
|
+
if not self.unpauseflag.isSet():
|
127
|
+
self._d()
|
128
|
+
return
|
129
|
+
self.announce(event, self._d)
|
130
|
+
|
131
|
+
def _d(self):
|
132
|
+
if self.never_succeeded:
|
133
|
+
self.sched(self.d, 60) # retry in 60 seconds
|
134
|
+
elif self.force_rapid_update:
|
135
|
+
return
|
136
|
+
else:
|
137
|
+
self.sched(self.d, self.announce_interval)
|
138
|
+
|
139
|
+
|
140
|
+
def hit(self, event = 3):
|
141
|
+
if not self.unpauseflag.isSet() and (
|
142
|
+
self.howmany() < self.minpeers or self.force_rapid_update ):
|
143
|
+
self.announce(event)
|
144
|
+
|
145
|
+
def announce(self, event = 3, callback = lambda: None, specialurl = None):
|
146
|
+
|
147
|
+
if specialurl is not None:
|
148
|
+
s = self.url+'&uploaded=0&downloaded=0&left=1' # don't add to statistics
|
149
|
+
if self.howmany() >= self.maxpeers:
|
150
|
+
s += '&numwant=0'
|
151
|
+
else:
|
152
|
+
s += '&no_peer_id=1&compact=1'
|
153
|
+
self.last_failed = True # force true, so will display an error
|
154
|
+
self.special = specialurl
|
155
|
+
self.rerequest(s, callback)
|
156
|
+
return
|
157
|
+
|
158
|
+
else:
|
159
|
+
s = ('%s&uploaded=%s&downloaded=%s&left=%s' %
|
160
|
+
(self.url, str(self.up()), str(self.down()),
|
161
|
+
str(self.amount_left())))
|
162
|
+
if self.last is not None:
|
163
|
+
s += '&last=' + quote(str(self.last))
|
164
|
+
if self.trackerid is not None:
|
165
|
+
s += '&trackerid=' + quote(str(self.trackerid))
|
166
|
+
if self.howmany() >= self.maxpeers:
|
167
|
+
s += '&numwant=0'
|
168
|
+
else:
|
169
|
+
s += '&no_peer_id=1&compact=1'
|
170
|
+
if event != 3:
|
171
|
+
s += '&event=' + ['started', 'completed', 'stopped'][event]
|
172
|
+
if event == 2:
|
173
|
+
self.stopped = True
|
174
|
+
self.rerequest(s, callback)
|
175
|
+
|
176
|
+
|
177
|
+
def snoop(self, peers, callback = lambda: None): # tracker call support
|
178
|
+
self.rerequest(self.url
|
179
|
+
+'&event=stopped&port=0&uploaded=0&downloaded=0&left=1&tracker=1&numwant='
|
180
|
+
+str(peers), callback)
|
181
|
+
|
182
|
+
|
183
|
+
def rerequest(self, s, callback):
|
184
|
+
if not self.lock.isfinished(): # still waiting for prior cycle to complete??
|
185
|
+
def retry(self = self, s = s, callback = callback):
|
186
|
+
self.rerequest(s, callback)
|
187
|
+
self.sched(retry,5) # retry in 5 seconds
|
188
|
+
return
|
189
|
+
self.lock.reset()
|
190
|
+
rq = Thread(target = self._rerequest, args = [s, callback])
|
191
|
+
rq.setDaemon(False)
|
192
|
+
rq.start()
|
193
|
+
|
194
|
+
def _rerequest(self, s, callback):
|
195
|
+
try:
|
196
|
+
def fail (self = self, callback = callback):
|
197
|
+
self._fail(callback)
|
198
|
+
if self.ip:
|
199
|
+
try:
|
200
|
+
s += '&ip=' + gethostbyname(self.ip)
|
201
|
+
except:
|
202
|
+
self.errorcodes['troublecode'] = 'unable to resolve: '+self.ip
|
203
|
+
self.externalsched(fail)
|
204
|
+
self.errorcodes = {}
|
205
|
+
if self.special is None:
|
206
|
+
for t in range(len(self.trackerlist)):
|
207
|
+
for tr in range(len(self.trackerlist[t])):
|
208
|
+
tracker = self.trackerlist[t][tr]
|
209
|
+
if self.rerequest_single(tracker, s, callback):
|
210
|
+
if not self.last_failed and tr != 0:
|
211
|
+
del self.trackerlist[t][tr]
|
212
|
+
self.trackerlist[t] = [tracker] + self.trackerlist[t]
|
213
|
+
return
|
214
|
+
else:
|
215
|
+
tracker = self.special
|
216
|
+
self.special = None
|
217
|
+
if self.rerequest_single(tracker, s, callback):
|
218
|
+
return
|
219
|
+
# no success from any tracker
|
220
|
+
self.externalsched(fail)
|
221
|
+
except:
|
222
|
+
self.exception(callback)
|
223
|
+
|
224
|
+
|
225
|
+
def _fail(self, callback):
|
226
|
+
if ( (self.upratefunc() < 100 and self.downratefunc() < 100)
|
227
|
+
or not self.amount_left() ):
|
228
|
+
for f in ['rejected', 'bad_data', 'troublecode']:
|
229
|
+
if self.errorcodes.has_key(f):
|
230
|
+
r = self.errorcodes[f]
|
231
|
+
break
|
232
|
+
else:
|
233
|
+
r = 'Problem connecting to tracker - unspecified error'
|
234
|
+
return
|
235
|
+
self.errorfunc(r)
|
236
|
+
|
237
|
+
self.last_failed = True
|
238
|
+
self.lock.give_up()
|
239
|
+
self.externalsched(callback)
|
240
|
+
|
241
|
+
|
242
|
+
def rerequest_single(self, t, s, callback):
|
243
|
+
l = self.lock.set()
|
244
|
+
rq = Thread(target = self._rerequest_single, args = [t, s+get_key(t), l, callback])
|
245
|
+
rq.setDaemon(False)
|
246
|
+
rq.start()
|
247
|
+
self.lock.wait()
|
248
|
+
if self.lock.success:
|
249
|
+
self.lastsuccessful = t
|
250
|
+
self.last_failed = False
|
251
|
+
self.never_succeeded = False
|
252
|
+
return True
|
253
|
+
if not self.last_failed and self.lastsuccessful == t:
|
254
|
+
# if the last tracker hit was successful, and you've just tried the tracker
|
255
|
+
# you'd contacted before, don't go any further, just fail silently.
|
256
|
+
self.last_failed = True
|
257
|
+
self.externalsched(callback)
|
258
|
+
self.lock.give_up()
|
259
|
+
return True
|
260
|
+
return False # returns true if it wants rerequest() to exit
|
261
|
+
|
262
|
+
|
263
|
+
def _rerequest_single(self, t, s, l, callback):
|
264
|
+
try:
|
265
|
+
closer = [None]
|
266
|
+
def timedout(self = self, l = l, closer = closer):
|
267
|
+
if self.lock.trip(l):
|
268
|
+
self.errorcodes['troublecode'] = 'Problem connecting to tracker - timeout exceeded'
|
269
|
+
self.lock.unwait(l)
|
270
|
+
try:
|
271
|
+
closer[0]()
|
272
|
+
except:
|
273
|
+
pass
|
274
|
+
|
275
|
+
self.externalsched(timedout, self.timeout)
|
276
|
+
|
277
|
+
err = None
|
278
|
+
try:
|
279
|
+
h = urlopen(t+s)
|
280
|
+
closer[0] = h.close
|
281
|
+
data = h.read()
|
282
|
+
except (IOError, error), e:
|
283
|
+
err = 'Problem connecting to tracker - ' + str(e)
|
284
|
+
except:
|
285
|
+
err = 'Problem connecting to tracker'
|
286
|
+
try:
|
287
|
+
h.close()
|
288
|
+
except:
|
289
|
+
pass
|
290
|
+
if err:
|
291
|
+
if self.lock.trip(l):
|
292
|
+
self.errorcodes['troublecode'] = err
|
293
|
+
self.lock.unwait(l)
|
294
|
+
return
|
295
|
+
|
296
|
+
if data == '':
|
297
|
+
if self.lock.trip(l):
|
298
|
+
self.errorcodes['troublecode'] = 'no data from tracker'
|
299
|
+
self.lock.unwait(l)
|
300
|
+
return
|
301
|
+
|
302
|
+
try:
|
303
|
+
r = bdecode(data, sloppy=1)
|
304
|
+
check_peers(r)
|
305
|
+
except ValueError, e:
|
306
|
+
if self.lock.trip(l):
|
307
|
+
self.errorcodes['bad_data'] = 'bad data from tracker - ' + str(e)
|
308
|
+
self.lock.unwait(l)
|
309
|
+
return
|
310
|
+
|
311
|
+
if r.has_key('failure reason'):
|
312
|
+
if self.lock.trip(l):
|
313
|
+
self.errorcodes['rejected'] = self.rejectedmessage + r['failure reason']
|
314
|
+
self.lock.unwait(l)
|
315
|
+
return
|
316
|
+
|
317
|
+
if self.lock.trip(l, True): # success!
|
318
|
+
self.lock.unwait(l)
|
319
|
+
else:
|
320
|
+
callback = lambda: None # attempt timed out, don't do a callback
|
321
|
+
|
322
|
+
# even if the attempt timed out, go ahead and process data
|
323
|
+
def add(self = self, r = r, callback = callback):
|
324
|
+
self.postrequest(r, callback)
|
325
|
+
self.externalsched(add)
|
326
|
+
except:
|
327
|
+
self.exception(callback)
|
328
|
+
|
329
|
+
|
330
|
+
def postrequest(self, r, callback):
|
331
|
+
if r.has_key('warning message'):
|
332
|
+
self.errorfunc('warning from tracker - ' + r['warning message'])
|
333
|
+
self.announce_interval = r.get('interval', self.announce_interval)
|
334
|
+
self.interval = r.get('min interval', self.interval)
|
335
|
+
self.trackerid = r.get('tracker id', self.trackerid)
|
336
|
+
self.last = r.get('last')
|
337
|
+
# ps = len(r['peers']) + self.howmany()
|
338
|
+
p = r['peers']
|
339
|
+
peers = []
|
340
|
+
if type(p) == type(''):
|
341
|
+
for x in xrange(0, len(p), 6):
|
342
|
+
ip = '.'.join([str(ord(i)) for i in p[x:x+4]])
|
343
|
+
port = (ord(p[x+4]) << 8) | ord(p[x+5])
|
344
|
+
peers.append(((ip, port), 0))
|
345
|
+
else:
|
346
|
+
for x in p:
|
347
|
+
peers.append(((x['ip'].strip(), x['port']), x.get('peer id',0)))
|
348
|
+
ps = len(peers) + self.howmany()
|
349
|
+
if ps < self.maxpeers:
|
350
|
+
if self.doneflag.isSet():
|
351
|
+
if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2:
|
352
|
+
self.last = None
|
353
|
+
else:
|
354
|
+
if r.get('num peers', 1000) > ps * 1.2:
|
355
|
+
self.last = None
|
356
|
+
if self.seededfunc and r.get('seeded'):
|
357
|
+
self.seededfunc()
|
358
|
+
elif peers:
|
359
|
+
shuffle(peers)
|
360
|
+
self.connect(peers)
|
361
|
+
callback()
|
362
|
+
|
363
|
+
def exception(self, callback):
|
364
|
+
data = StringIO()
|
365
|
+
print_exc(file = data)
|
366
|
+
def r(s = data.getvalue(), callback = callback):
|
367
|
+
if self.excfunc:
|
368
|
+
self.excfunc(s)
|
369
|
+
else:
|
370
|
+
print s
|
371
|
+
callback()
|
372
|
+
self.externalsched(r)
|
373
|
+
|
374
|
+
|
375
|
+
class SuccessLock:
|
376
|
+
def __init__(self):
|
377
|
+
self.lock = Lock()
|
378
|
+
self.pause = Lock()
|
379
|
+
self.code = 0L
|
380
|
+
self.success = False
|
381
|
+
self.finished = True
|
382
|
+
|
383
|
+
def reset(self):
|
384
|
+
self.success = False
|
385
|
+
self.finished = False
|
386
|
+
|
387
|
+
def set(self):
|
388
|
+
self.lock.acquire()
|
389
|
+
if not self.pause.locked():
|
390
|
+
self.pause.acquire()
|
391
|
+
self.first = True
|
392
|
+
self.code += 1L
|
393
|
+
self.lock.release()
|
394
|
+
return self.code
|
395
|
+
|
396
|
+
def trip(self, code, s = False):
|
397
|
+
self.lock.acquire()
|
398
|
+
try:
|
399
|
+
if code == self.code and not self.finished:
|
400
|
+
r = self.first
|
401
|
+
self.first = False
|
402
|
+
if s:
|
403
|
+
self.finished = True
|
404
|
+
self.success = True
|
405
|
+
return r
|
406
|
+
finally:
|
407
|
+
self.lock.release()
|
408
|
+
|
409
|
+
def give_up(self):
|
410
|
+
self.lock.acquire()
|
411
|
+
self.success = False
|
412
|
+
self.finished = True
|
413
|
+
self.lock.release()
|
414
|
+
|
415
|
+
def wait(self):
|
416
|
+
self.pause.acquire()
|
417
|
+
|
418
|
+
def unwait(self, code):
|
419
|
+
if code == self.code and self.pause.locked():
|
420
|
+
self.pause.release()
|
421
|
+
|
422
|
+
def isfinished(self):
|
423
|
+
self.lock.acquire()
|
424
|
+
x = self.finished
|
425
|
+
self.lock.release()
|
426
|
+
return x
|