murder 0.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +1 -0
  2. data/LICENSE +17 -0
  3. data/README +224 -0
  4. data/Rakefile +52 -0
  5. data/VERSION +1 -0
  6. data/dist/BitTornado/BT1/Choker.py +128 -0
  7. data/dist/BitTornado/BT1/Connecter.py +288 -0
  8. data/dist/BitTornado/BT1/Downloader.py +594 -0
  9. data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
  10. data/dist/BitTornado/BT1/Encrypter.py +333 -0
  11. data/dist/BitTornado/BT1/FileSelector.py +245 -0
  12. data/dist/BitTornado/BT1/Filter.py +12 -0
  13. data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
  14. data/dist/BitTornado/BT1/NatCheck.py +95 -0
  15. data/dist/BitTornado/BT1/PiecePicker.py +320 -0
  16. data/dist/BitTornado/BT1/Rerequester.py +426 -0
  17. data/dist/BitTornado/BT1/Statistics.py +177 -0
  18. data/dist/BitTornado/BT1/Storage.py +584 -0
  19. data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
  20. data/dist/BitTornado/BT1/StreamCheck.py +135 -0
  21. data/dist/BitTornado/BT1/T2T.py +193 -0
  22. data/dist/BitTornado/BT1/Uploader.py +145 -0
  23. data/dist/BitTornado/BT1/__init__.py +1 -0
  24. data/dist/BitTornado/BT1/btformats.py +100 -0
  25. data/dist/BitTornado/BT1/fakeopen.py +89 -0
  26. data/dist/BitTornado/BT1/makemetafile.py +263 -0
  27. data/dist/BitTornado/BT1/track.py +1067 -0
  28. data/dist/BitTornado/ConfigDir.py +401 -0
  29. data/dist/BitTornado/ConfigReader.py +1068 -0
  30. data/dist/BitTornado/ConnChoice.py +31 -0
  31. data/dist/BitTornado/CreateIcons.py +105 -0
  32. data/dist/BitTornado/CurrentRateMeasure.py +37 -0
  33. data/dist/BitTornado/HTTPHandler.py +167 -0
  34. data/dist/BitTornado/PSYCO.py +5 -0
  35. data/dist/BitTornado/RateLimiter.py +153 -0
  36. data/dist/BitTornado/RateMeasure.py +75 -0
  37. data/dist/BitTornado/RawServer.py +195 -0
  38. data/dist/BitTornado/ServerPortHandler.py +188 -0
  39. data/dist/BitTornado/SocketHandler.py +375 -0
  40. data/dist/BitTornado/__init__.py +63 -0
  41. data/dist/BitTornado/bencode.py +319 -0
  42. data/dist/BitTornado/bitfield.py +162 -0
  43. data/dist/BitTornado/clock.py +27 -0
  44. data/dist/BitTornado/download_bt1.py +882 -0
  45. data/dist/BitTornado/inifile.py +169 -0
  46. data/dist/BitTornado/iprangeparse.py +194 -0
  47. data/dist/BitTornado/launchmanycore.py +381 -0
  48. data/dist/BitTornado/natpunch.py +254 -0
  49. data/dist/BitTornado/parseargs.py +137 -0
  50. data/dist/BitTornado/parsedir.py +150 -0
  51. data/dist/BitTornado/piecebuffer.py +86 -0
  52. data/dist/BitTornado/selectpoll.py +109 -0
  53. data/dist/BitTornado/subnetparse.py +218 -0
  54. data/dist/BitTornado/torrentlistparse.py +38 -0
  55. data/dist/BitTornado/zurllib.py +100 -0
  56. data/dist/murder_client.py +291 -0
  57. data/dist/murder_make_torrent.py +46 -0
  58. data/dist/murder_tracker.py +28 -0
  59. data/doc/examples/Capfile +28 -0
  60. data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
  61. data/lib/murder.rb +43 -0
  62. data/lib/murder/admin.rb +47 -0
  63. data/lib/murder/murder.rb +121 -0
  64. data/murder.gemspec +101 -0
  65. 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