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.
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