murder 0.0.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|