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,135 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from cStringIO import StringIO
5
+ from binascii import b2a_hex
6
+ from socket import error as socketerror
7
+ from urllib import quote
8
+ from traceback import print_exc
9
+ import Connecter
10
+ try:
11
+ True
12
+ except:
13
+ True = 1
14
+ False = 0
15
+
16
+ DEBUG = False
17
+
18
+
19
+ protocol_name = 'BitTorrent protocol'
20
+ option_pattern = chr(0)*8
21
+
22
+ def toint(s):
23
+ return long(b2a_hex(s), 16)
24
+
25
+ def tobinary(i):
26
+ return (chr(i >> 24) + chr((i >> 16) & 0xFF) +
27
+ chr((i >> 8) & 0xFF) + chr(i & 0xFF))
28
+
29
+ hexchars = '0123456789ABCDEF'
30
+ hexmap = []
31
+ for i in xrange(256):
32
+ hexmap.append(hexchars[(i&0xF0)/16]+hexchars[i&0x0F])
33
+
34
+ def tohex(s):
35
+ r = []
36
+ for c in s:
37
+ r.append(hexmap[ord(c)])
38
+ return ''.join(r)
39
+
40
+ def make_readable(s):
41
+ if not s:
42
+ return ''
43
+ if quote(s).find('%') >= 0:
44
+ return tohex(s)
45
+ return '"'+s+'"'
46
+
47
+ def toint(s):
48
+ return long(b2a_hex(s), 16)
49
+
50
+ # header, reserved, download id, my id, [length, message]
51
+
52
+ streamno = 0
53
+
54
+
55
+ class StreamCheck:
56
+ def __init__(self):
57
+ global streamno
58
+ self.no = streamno
59
+ streamno += 1
60
+ self.buffer = StringIO()
61
+ self.next_len, self.next_func = 1, self.read_header_len
62
+
63
+ def read_header_len(self, s):
64
+ if ord(s) != len(protocol_name):
65
+ print self.no, 'BAD HEADER LENGTH'
66
+ return len(protocol_name), self.read_header
67
+
68
+ def read_header(self, s):
69
+ if s != protocol_name:
70
+ print self.no, 'BAD HEADER'
71
+ return 8, self.read_reserved
72
+
73
+ def read_reserved(self, s):
74
+ return 20, self.read_download_id
75
+
76
+ def read_download_id(self, s):
77
+ if DEBUG:
78
+ print self.no, 'download ID ' + tohex(s)
79
+ return 20, self.read_peer_id
80
+
81
+ def read_peer_id(self, s):
82
+ if DEBUG:
83
+ print self.no, 'peer ID' + make_readable(s)
84
+ return 4, self.read_len
85
+
86
+ def read_len(self, s):
87
+ l = toint(s)
88
+ if l > 2 ** 23:
89
+ print self.no, 'BAD LENGTH: '+str(l)+' ('+s+')'
90
+ return l, self.read_message
91
+
92
+ def read_message(self, s):
93
+ if not s:
94
+ return 4, self.read_len
95
+ m = s[0]
96
+ if ord(m) > 8:
97
+ print self.no, 'BAD MESSAGE: '+str(ord(m))
98
+ if m == Connecter.REQUEST:
99
+ if len(s) != 13:
100
+ print self.no, 'BAD REQUEST SIZE: '+str(len(s))
101
+ return 4, self.read_len
102
+ index = toint(s[1:5])
103
+ begin = toint(s[5:9])
104
+ length = toint(s[9:])
105
+ print self.no, 'Request: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
106
+ elif m == Connecter.CANCEL:
107
+ if len(s) != 13:
108
+ print self.no, 'BAD CANCEL SIZE: '+str(len(s))
109
+ return 4, self.read_len
110
+ index = toint(s[1:5])
111
+ begin = toint(s[5:9])
112
+ length = toint(s[9:])
113
+ print self.no, 'Cancel: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
114
+ elif m == Connecter.PIECE:
115
+ index = toint(s[1:5])
116
+ begin = toint(s[5:9])
117
+ length = len(s)-9
118
+ print self.no, 'Piece: '+str(index)+': '+str(begin)+'-'+str(begin)+'+'+str(length)
119
+ else:
120
+ print self.no, 'Message '+str(ord(m))+' (length '+str(len(s))+')'
121
+ return 4, self.read_len
122
+
123
+ def write(self, s):
124
+ while True:
125
+ i = self.next_len - self.buffer.tell()
126
+ if i > len(s):
127
+ self.buffer.write(s)
128
+ return
129
+ self.buffer.write(s[:i])
130
+ s = s[i:]
131
+ m = self.buffer.getvalue()
132
+ self.buffer.reset()
133
+ self.buffer.truncate()
134
+ x = self.next_func(m)
135
+ self.next_len, self.next_func = x
@@ -0,0 +1,193 @@
1
+ # Written by John Hoffman
2
+ # see LICENSE.txt for license information
3
+
4
+ from Rerequester import Rerequester
5
+ from urllib import quote
6
+ from threading import Event
7
+ from random import randrange
8
+ from string import lower
9
+ import sys
10
+ import __init__
11
+ try:
12
+ True
13
+ except:
14
+ True = 1
15
+ False = 0
16
+
17
+ DEBUG = True
18
+
19
+
20
+ def excfunc(x):
21
+ print x
22
+
23
+ class T2TConnection:
24
+ def __init__(self, myid, tracker, hash, interval, peers, timeout,
25
+ rawserver, disallow, isdisallowed):
26
+ self.tracker = tracker
27
+ self.interval = interval
28
+ self.hash = hash
29
+ self.operatinginterval = interval
30
+ self.peers = peers
31
+ self.rawserver = rawserver
32
+ self.disallow = disallow
33
+ self.isdisallowed = isdisallowed
34
+ self.active = True
35
+ self.busy = False
36
+ self.errors = 0
37
+ self.rejected = 0
38
+ self.trackererror = False
39
+ self.peerlists = []
40
+
41
+ self.rerequester = Rerequester([[tracker]], interval,
42
+ rawserver.add_task, lambda: 0, peers, self.addtolist,
43
+ rawserver.add_task, lambda: 1, 0, 0, 0, '',
44
+ myid, hash, timeout, self.errorfunc, excfunc, peers, Event(),
45
+ lambda: 0, lambda: 0)
46
+
47
+ if self.isactive():
48
+ rawserver.add_task(self.refresh, randrange(int(self.interval/10), self.interval))
49
+ # stagger announces
50
+
51
+ def isactive(self):
52
+ if self.isdisallowed(self.tracker): # whoops!
53
+ self.deactivate()
54
+ return self.active
55
+
56
+ def deactivate(self):
57
+ self.active = False
58
+
59
+ def refresh(self):
60
+ if not self.isactive():
61
+ return
62
+ self.lastsuccessful = True
63
+ self.newpeerdata = []
64
+ if DEBUG:
65
+ print 'contacting %s for info_hash=%s' % (self.tracker, quote(self.hash))
66
+ self.rerequester.snoop(self.peers, self.callback)
67
+
68
+ def callback(self):
69
+ self.busy = False
70
+ if self.lastsuccessful:
71
+ self.errors = 0
72
+ self.rejected = 0
73
+ if self.rerequester.announce_interval > (3*self.interval):
74
+ # I think I'm stripping from a regular tracker; boost the number of peers requested
75
+ self.peers = int(self.peers * (self.rerequester.announce_interval / self.interval))
76
+ self.operatinginterval = self.rerequester.announce_interval
77
+ if DEBUG:
78
+ print ("%s with info_hash=%s returned %d peers" %
79
+ (self.tracker, quote(self.hash), len(self.newpeerdata)))
80
+ self.peerlists.append(self.newpeerdata)
81
+ self.peerlists = self.peerlists[-10:] # keep up to the last 10 announces
82
+ if self.isactive():
83
+ self.rawserver.add_task(self.refresh, self.operatinginterval)
84
+
85
+ def addtolist(self, peers):
86
+ for peer in peers:
87
+ self.newpeerdata.append((peer[1],peer[0][0],peer[0][1]))
88
+
89
+ def errorfunc(self, r):
90
+ self.lastsuccessful = False
91
+ if DEBUG:
92
+ print "%s with info_hash=%s gives error: '%s'" % (self.tracker, quote(self.hash), r)
93
+ if r == self.rerequester.rejectedmessage + 'disallowed': # whoops!
94
+ if DEBUG:
95
+ print ' -- disallowed - deactivating'
96
+ self.deactivate()
97
+ self.disallow(self.tracker) # signal other torrents on this tracker
98
+ return
99
+ if lower(r[:8]) == 'rejected': # tracker rejected this particular torrent
100
+ self.rejected += 1
101
+ if self.rejected == 3: # rejected 3 times
102
+ if DEBUG:
103
+ print ' -- rejected 3 times - deactivating'
104
+ self.deactivate()
105
+ return
106
+ self.errors += 1
107
+ if self.errors >= 3: # three or more errors in a row
108
+ self.operatinginterval += self.interval # lengthen the interval
109
+ if DEBUG:
110
+ print ' -- lengthening interval to '+str(self.operatinginterval)+' seconds'
111
+
112
+ def harvest(self):
113
+ x = []
114
+ for list in self.peerlists:
115
+ x += list
116
+ self.peerlists = []
117
+ return x
118
+
119
+
120
+ class T2TList:
121
+ def __init__(self, enabled, trackerid, interval, maxpeers, timeout, rawserver):
122
+ self.enabled = enabled
123
+ self.trackerid = trackerid
124
+ self.interval = interval
125
+ self.maxpeers = maxpeers
126
+ self.timeout = timeout
127
+ self.rawserver = rawserver
128
+ self.list = {}
129
+ self.torrents = {}
130
+ self.disallowed = {}
131
+ self.oldtorrents = []
132
+
133
+ def parse(self, allowed_list):
134
+ if not self.enabled:
135
+ return
136
+
137
+ # step 1: Create a new list with all tracker/torrent combinations in allowed_dir
138
+ newlist = {}
139
+ for hash, data in allowed_list.items():
140
+ if data.has_key('announce-list'):
141
+ for tier in data['announce-list']:
142
+ for tracker in tier:
143
+ self.disallowed.setdefault(tracker, False)
144
+ newlist.setdefault(tracker, {})
145
+ newlist[tracker][hash] = None # placeholder
146
+
147
+ # step 2: Go through and copy old data to the new list.
148
+ # if the new list has no place for it, then it's old, so deactivate it
149
+ for tracker, hashdata in self.list.items():
150
+ for hash, t2t in hashdata.items():
151
+ if not newlist.has_key(tracker) or not newlist[tracker].has_key(hash):
152
+ t2t.deactivate() # this connection is no longer current
153
+ self.oldtorrents += [t2t]
154
+ # keep it referenced in case a thread comes along and tries to access.
155
+ else:
156
+ newlist[tracker][hash] = t2t
157
+ if not newlist.has_key(tracker):
158
+ self.disallowed[tracker] = False # reset when no torrents on it left
159
+
160
+ self.list = newlist
161
+ newtorrents = {}
162
+
163
+ # step 3: If there are any entries that haven't been initialized yet, do so.
164
+ # At the same time, copy all entries onto the by-torrent list.
165
+ for tracker, hashdata in newlist.items():
166
+ for hash, t2t in hashdata.items():
167
+ if t2t is None:
168
+ hashdata[hash] = T2TConnection(self.trackerid, tracker, hash,
169
+ self.interval, self.maxpeers, self.timeout,
170
+ self.rawserver, self._disallow, self._isdisallowed)
171
+ newtorrents.setdefault(hash,[])
172
+ newtorrents[hash] += [hashdata[hash]]
173
+
174
+ self.torrents = newtorrents
175
+
176
+ # structures:
177
+ # list = {tracker: {hash: T2TConnection, ...}, ...}
178
+ # torrents = {hash: [T2TConnection, ...]}
179
+ # disallowed = {tracker: flag, ...}
180
+ # oldtorrents = [T2TConnection, ...]
181
+
182
+ def _disallow(self,tracker):
183
+ self.disallowed[tracker] = True
184
+
185
+ def _isdisallowed(self,tracker):
186
+ return self.disallowed[tracker]
187
+
188
+ def harvest(self,hash):
189
+ harvest = []
190
+ if self.enabled:
191
+ for t2t in self.torrents[hash]:
192
+ harvest += t2t.harvest()
193
+ return harvest
@@ -0,0 +1,145 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from BitTornado.CurrentRateMeasure import Measure
5
+
6
+ try:
7
+ True
8
+ except:
9
+ True = 1
10
+ False = 0
11
+
12
+ class Upload:
13
+ def __init__(self, connection, ratelimiter, totalup, choker, storage,
14
+ picker, config):
15
+ self.connection = connection
16
+ self.ratelimiter = ratelimiter
17
+ self.totalup = totalup
18
+ self.choker = choker
19
+ self.storage = storage
20
+ self.picker = picker
21
+ self.config = config
22
+ self.max_slice_length = config['max_slice_length']
23
+ self.choked = True
24
+ self.cleared = True
25
+ self.interested = False
26
+ self.super_seeding = False
27
+ self.buffer = []
28
+ self.measure = Measure(config['max_rate_period'], config['upload_rate_fudge'])
29
+ self.was_ever_interested = False
30
+ if storage.get_amount_left() == 0:
31
+ if choker.super_seed:
32
+ self.super_seeding = True # flag, and don't send bitfield
33
+ self.seed_have_list = [] # set from piecepicker
34
+ self.skipped_count = 0
35
+ else:
36
+ if config['breakup_seed_bitfield']:
37
+ bitfield, msgs = storage.get_have_list_cloaked()
38
+ connection.send_bitfield(bitfield)
39
+ for have in msgs:
40
+ connection.send_have(have)
41
+ else:
42
+ connection.send_bitfield(storage.get_have_list())
43
+ else:
44
+ if storage.do_I_have_anything():
45
+ connection.send_bitfield(storage.get_have_list())
46
+ self.piecedl = None
47
+ self.piecebuf = None
48
+
49
+ def got_not_interested(self):
50
+ if self.interested:
51
+ self.interested = False
52
+ del self.buffer[:]
53
+ self.piecedl = None
54
+ if self.piecebuf:
55
+ self.piecebuf.release()
56
+ self.piecebuf = None
57
+ self.choker.not_interested(self.connection)
58
+
59
+ def got_interested(self):
60
+ if not self.interested:
61
+ self.interested = True
62
+ self.was_ever_interested = True
63
+ self.choker.interested(self.connection)
64
+
65
+ def get_upload_chunk(self):
66
+ if self.choked or not self.buffer:
67
+ return None
68
+ index, begin, length = self.buffer.pop(0)
69
+ if self.config['buffer_reads']:
70
+ if index != self.piecedl:
71
+ if self.piecebuf:
72
+ self.piecebuf.release()
73
+ self.piecedl = index
74
+ self.piecebuf = self.storage.get_piece(index, 0, -1)
75
+ try:
76
+ piece = self.piecebuf[begin:begin+length]
77
+ assert len(piece) == length
78
+ except: # fails if storage.get_piece returns None or if out of range
79
+ self.connection.close()
80
+ return None
81
+ else:
82
+ if self.piecebuf:
83
+ self.piecebuf.release()
84
+ self.piecedl = None
85
+ piece = self.storage.get_piece(index, begin, length)
86
+ if piece is None:
87
+ self.connection.close()
88
+ return None
89
+ self.measure.update_rate(len(piece))
90
+ self.totalup.update_rate(len(piece))
91
+ return (index, begin, piece)
92
+
93
+ def got_request(self, index, begin, length):
94
+ if ( (self.super_seeding and not index in self.seed_have_list)
95
+ or not self.interested or length > self.max_slice_length ):
96
+ self.connection.close()
97
+ return
98
+ if not self.cleared:
99
+ self.buffer.append((index, begin, length))
100
+ if not self.choked and self.connection.next_upload is None:
101
+ self.ratelimiter.queue(self.connection)
102
+
103
+
104
+ def got_cancel(self, index, begin, length):
105
+ try:
106
+ self.buffer.remove((index, begin, length))
107
+ except ValueError:
108
+ pass
109
+
110
+ def choke(self):
111
+ if not self.choked:
112
+ self.choked = True
113
+ self.connection.send_choke()
114
+ self.piecedl = None
115
+ if self.piecebuf:
116
+ self.piecebuf.release()
117
+ self.piecebuf = None
118
+
119
+ def choke_sent(self):
120
+ del self.buffer[:]
121
+ self.cleared = True
122
+
123
+ def unchoke(self):
124
+ if self.choked:
125
+ self.choked = False
126
+ self.cleared = False
127
+ self.connection.send_unchoke()
128
+
129
+ def disconnected(self):
130
+ if self.piecebuf:
131
+ self.piecebuf.release()
132
+ self.piecebuf = None
133
+
134
+ def is_choked(self):
135
+ return self.choked
136
+
137
+ def is_interested(self):
138
+ return self.interested
139
+
140
+ def has_queries(self):
141
+ return not self.choked and len(self.buffer) > 0
142
+
143
+ def get_rate(self):
144
+ return self.measure.get_rate()
145
+