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,375 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ import socket
5
+ from errno import EWOULDBLOCK, ECONNREFUSED, EHOSTUNREACH
6
+ try:
7
+ from select import poll, error, POLLIN, POLLOUT, POLLERR, POLLHUP
8
+ timemult = 1000
9
+ except ImportError:
10
+ from selectpoll import poll, error, POLLIN, POLLOUT, POLLERR, POLLHUP
11
+ timemult = 1
12
+ from time import sleep
13
+ from clock import clock
14
+ import sys
15
+ from random import shuffle, randrange
16
+ from natpunch import UPnP_open_port, UPnP_close_port
17
+ # from BT1.StreamCheck import StreamCheck
18
+ # import inspect
19
+ try:
20
+ True
21
+ except:
22
+ True = 1
23
+ False = 0
24
+
25
+ all = POLLIN | POLLOUT
26
+
27
+ UPnP_ERROR = "unable to forward port via UPnP"
28
+
29
+ class SingleSocket:
30
+ def __init__(self, socket_handler, sock, handler, ip = None):
31
+ self.socket_handler = socket_handler
32
+ self.socket = sock
33
+ self.handler = handler
34
+ self.buffer = []
35
+ self.last_hit = clock()
36
+ self.fileno = sock.fileno()
37
+ self.connected = False
38
+ self.skipped = 0
39
+ # self.check = StreamCheck()
40
+ try:
41
+ self.ip = self.socket.getpeername()[0]
42
+ except:
43
+ if ip is None:
44
+ self.ip = 'unknown'
45
+ else:
46
+ self.ip = ip
47
+
48
+ def get_ip(self, real=False):
49
+ if real:
50
+ try:
51
+ self.ip = self.socket.getpeername()[0]
52
+ except:
53
+ pass
54
+ return self.ip
55
+
56
+ def close(self):
57
+ '''
58
+ for x in xrange(5,0,-1):
59
+ try:
60
+ f = inspect.currentframe(x).f_code
61
+ print (f.co_filename,f.co_firstlineno,f.co_name)
62
+ del f
63
+ except:
64
+ pass
65
+ print ''
66
+ '''
67
+ assert self.socket
68
+ self.connected = False
69
+ sock = self.socket
70
+ self.socket = None
71
+ self.buffer = []
72
+ del self.socket_handler.single_sockets[self.fileno]
73
+ self.socket_handler.poll.unregister(sock)
74
+ sock.close()
75
+
76
+ def shutdown(self, val):
77
+ self.socket.shutdown(val)
78
+
79
+ def is_flushed(self):
80
+ return not self.buffer
81
+
82
+ def write(self, s):
83
+ # self.check.write(s)
84
+ assert self.socket is not None
85
+ self.buffer.append(s)
86
+ if len(self.buffer) == 1:
87
+ self.try_write()
88
+
89
+ def try_write(self):
90
+ if self.connected:
91
+ dead = False
92
+ try:
93
+ while self.buffer:
94
+ buf = self.buffer[0]
95
+ amount = self.socket.send(buf)
96
+ if amount == 0:
97
+ self.skipped += 1
98
+ break
99
+ self.skipped = 0
100
+ if amount != len(buf):
101
+ self.buffer[0] = buf[amount:]
102
+ break
103
+ del self.buffer[0]
104
+ except socket.error, e:
105
+ try:
106
+ dead = e[0] != EWOULDBLOCK
107
+ except:
108
+ dead = True
109
+ self.skipped += 1
110
+ if self.skipped >= 3:
111
+ dead = True
112
+ if dead:
113
+ self.socket_handler.dead_from_write.append(self)
114
+ return
115
+ if self.buffer:
116
+ self.socket_handler.poll.register(self.socket, all)
117
+ else:
118
+ self.socket_handler.poll.register(self.socket, POLLIN)
119
+
120
+ def set_handler(self, handler):
121
+ self.handler = handler
122
+
123
+ class SocketHandler:
124
+ def __init__(self, timeout, ipv6_enable, readsize = 100000):
125
+ self.timeout = timeout
126
+ self.ipv6_enable = ipv6_enable
127
+ self.readsize = readsize
128
+ self.poll = poll()
129
+ # {socket: SingleSocket}
130
+ self.single_sockets = {}
131
+ self.dead_from_write = []
132
+ self.max_connects = 1000
133
+ self.port_forwarded = None
134
+ self.servers = {}
135
+
136
+ def scan_for_timeouts(self):
137
+ t = clock() - self.timeout
138
+ tokill = []
139
+ for s in self.single_sockets.values():
140
+ if s.last_hit < t:
141
+ tokill.append(s)
142
+ for k in tokill:
143
+ if k.socket is not None:
144
+ self._close_socket(k)
145
+
146
+ def bind(self, port, bind = '', reuse = False, ipv6_socket_style = 1, upnp = 0):
147
+ port = int(port)
148
+ addrinfos = []
149
+ self.servers = {}
150
+ self.interfaces = []
151
+ # if bind != "" thread it as a comma seperated list and bind to all
152
+ # addresses (can be ips or hostnames) else bind to default ipv6 and
153
+ # ipv4 address
154
+ if bind:
155
+ if self.ipv6_enable:
156
+ socktype = socket.AF_UNSPEC
157
+ else:
158
+ socktype = socket.AF_INET
159
+ bind = bind.split(',')
160
+ for addr in bind:
161
+ if sys.version_info < (2,2):
162
+ addrinfos.append((socket.AF_INET, None, None, None, (addr, port)))
163
+ else:
164
+ addrinfos.extend(socket.getaddrinfo(addr, port,
165
+ socktype, socket.SOCK_STREAM))
166
+ else:
167
+ if self.ipv6_enable:
168
+ addrinfos.append([socket.AF_INET6, None, None, None, ('', port)])
169
+ if not addrinfos or ipv6_socket_style != 0:
170
+ addrinfos.append([socket.AF_INET, None, None, None, ('', port)])
171
+ for addrinfo in addrinfos:
172
+ try:
173
+ server = socket.socket(addrinfo[0], socket.SOCK_STREAM)
174
+ if reuse:
175
+ server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
176
+ server.setblocking(0)
177
+ server.bind(addrinfo[4])
178
+ self.servers[server.fileno()] = server
179
+ if bind:
180
+ self.interfaces.append(server.getsockname()[0])
181
+ server.listen(64)
182
+ self.poll.register(server, POLLIN)
183
+ except socket.error, e:
184
+ for server in self.servers.values():
185
+ try:
186
+ server.close()
187
+ except:
188
+ pass
189
+ if self.ipv6_enable and ipv6_socket_style == 0 and self.servers:
190
+ raise socket.error('blocked port (may require ipv6_binds_v4 to be set)')
191
+ raise socket.error(str(e))
192
+ if not self.servers:
193
+ raise socket.error('unable to open server port')
194
+ if upnp:
195
+ if not UPnP_open_port(port):
196
+ for server in self.servers.values():
197
+ try:
198
+ server.close()
199
+ except:
200
+ pass
201
+ self.servers = None
202
+ self.interfaces = None
203
+ raise socket.error(UPnP_ERROR)
204
+ self.port_forwarded = port
205
+ self.port = port
206
+
207
+ def find_and_bind(self, minport, maxport, bind = '', reuse = False,
208
+ ipv6_socket_style = 1, upnp = 0, randomizer = False):
209
+ e = 'maxport less than minport - no ports to check'
210
+ if maxport-minport < 50 or not randomizer:
211
+ portrange = range(minport, maxport+1)
212
+ if randomizer:
213
+ shuffle(portrange)
214
+ portrange = portrange[:20] # check a maximum of 20 ports
215
+ else:
216
+ portrange = []
217
+ while len(portrange) < 20:
218
+ listen_port = randrange(minport, maxport+1)
219
+ if not listen_port in portrange:
220
+ portrange.append(listen_port)
221
+ for listen_port in portrange:
222
+ try:
223
+ self.bind(listen_port, bind,
224
+ ipv6_socket_style = ipv6_socket_style, upnp = upnp)
225
+ return listen_port
226
+ except socket.error, e:
227
+ pass
228
+ raise socket.error(str(e))
229
+
230
+
231
+ def set_handler(self, handler):
232
+ self.handler = handler
233
+
234
+
235
+ def start_connection_raw(self, dns, socktype = socket.AF_INET, handler = None):
236
+ if handler is None:
237
+ handler = self.handler
238
+ sock = socket.socket(socktype, socket.SOCK_STREAM)
239
+ sock.setblocking(0)
240
+ try:
241
+ sock.connect_ex(dns)
242
+ except socket.error:
243
+ raise
244
+ except Exception, e:
245
+ raise socket.error(str(e))
246
+ self.poll.register(sock, POLLIN)
247
+ s = SingleSocket(self, sock, handler, dns[0])
248
+ self.single_sockets[sock.fileno()] = s
249
+ return s
250
+
251
+
252
+ def start_connection(self, dns, handler = None, randomize = False):
253
+ if handler is None:
254
+ handler = self.handler
255
+ if sys.version_info < (2,2):
256
+ s = self.start_connection_raw(dns,socket.AF_INET,handler)
257
+ else:
258
+ if self.ipv6_enable:
259
+ socktype = socket.AF_UNSPEC
260
+ else:
261
+ socktype = socket.AF_INET
262
+ try:
263
+ addrinfos = socket.getaddrinfo(dns[0], int(dns[1]),
264
+ socktype, socket.SOCK_STREAM)
265
+ except socket.error, e:
266
+ raise
267
+ except Exception, e:
268
+ raise socket.error(str(e))
269
+ if randomize:
270
+ shuffle(addrinfos)
271
+ for addrinfo in addrinfos:
272
+ try:
273
+ s = self.start_connection_raw(addrinfo[4],addrinfo[0],handler)
274
+ break
275
+ except:
276
+ pass
277
+ else:
278
+ raise socket.error('unable to connect')
279
+ return s
280
+
281
+
282
+ def _sleep(self):
283
+ sleep(1)
284
+
285
+ def handle_events(self, events):
286
+ for sock, event in events:
287
+ s = self.servers.get(sock)
288
+ if s:
289
+ if event & (POLLHUP | POLLERR) != 0:
290
+ self.poll.unregister(s)
291
+ s.close()
292
+ del self.servers[sock]
293
+ print "lost server socket"
294
+ elif len(self.single_sockets) < self.max_connects:
295
+ try:
296
+ newsock, addr = s.accept()
297
+ newsock.setblocking(0)
298
+ nss = SingleSocket(self, newsock, self.handler)
299
+ self.single_sockets[newsock.fileno()] = nss
300
+ self.poll.register(newsock, POLLIN)
301
+ self.handler.external_connection_made(nss)
302
+ except socket.error:
303
+ self._sleep()
304
+ else:
305
+ s = self.single_sockets.get(sock)
306
+ if not s:
307
+ continue
308
+ s.connected = True
309
+ if (event & (POLLHUP | POLLERR)):
310
+ self._close_socket(s)
311
+ continue
312
+ if (event & POLLIN):
313
+ try:
314
+ s.last_hit = clock()
315
+ data = s.socket.recv(100000)
316
+ if not data:
317
+ self._close_socket(s)
318
+ else:
319
+ s.handler.data_came_in(s, data)
320
+ except socket.error, e:
321
+ code, msg = e
322
+ if code != EWOULDBLOCK:
323
+ self._close_socket(s)
324
+ continue
325
+ if (event & POLLOUT) and s.socket and not s.is_flushed():
326
+ s.try_write()
327
+ if s.is_flushed():
328
+ s.handler.connection_flushed(s)
329
+
330
+ def close_dead(self):
331
+ while self.dead_from_write:
332
+ old = self.dead_from_write
333
+ self.dead_from_write = []
334
+ for s in old:
335
+ if s.socket:
336
+ self._close_socket(s)
337
+
338
+ def _close_socket(self, s):
339
+ s.close()
340
+ s.handler.connection_lost(s)
341
+
342
+ def do_poll(self, t):
343
+ r = self.poll.poll(t*timemult)
344
+ if r is None:
345
+ connects = len(self.single_sockets)
346
+ to_close = int(connects*0.05)+1 # close 5% of sockets
347
+ self.max_connects = connects-to_close
348
+ closelist = self.single_sockets.values()
349
+ shuffle(closelist)
350
+ closelist = closelist[:to_close]
351
+ for sock in closelist:
352
+ self._close_socket(sock)
353
+ return []
354
+ return r
355
+
356
+ def get_stats(self):
357
+ return { 'interfaces': self.interfaces,
358
+ 'port': self.port,
359
+ 'upnp': self.port_forwarded is not None }
360
+
361
+
362
+ def shutdown(self):
363
+ for ss in self.single_sockets.values():
364
+ try:
365
+ ss.close()
366
+ except:
367
+ pass
368
+ for server in self.servers.values():
369
+ try:
370
+ server.close()
371
+ except:
372
+ pass
373
+ if self.port_forwarded is not None:
374
+ UPnP_close_port(self.port_forwarded)
375
+
@@ -0,0 +1,63 @@
1
+ product_name = 'BitTornado'
2
+ version_short = 'T-0.3.17'
3
+
4
+ version = version_short+' ('+product_name+')'
5
+ report_email = version_short+'@degreez.net'
6
+
7
+ from types import StringType
8
+ from sha import sha
9
+ from time import time, clock
10
+ try:
11
+ from os import getpid
12
+ except ImportError:
13
+ def getpid():
14
+ return 1
15
+
16
+ mapbase64 = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-'
17
+
18
+ _idprefix = version_short[0]
19
+ for subver in version_short[2:].split('.'):
20
+ try:
21
+ subver = int(subver)
22
+ except:
23
+ subver = 0
24
+ _idprefix += mapbase64[subver]
25
+ _idprefix += ('-' * (6-len(_idprefix)))
26
+ _idrandom = [None]
27
+
28
+ def resetPeerIDs():
29
+ try:
30
+ f = open('/dev/urandom','rb')
31
+ x = f.read(20)
32
+ f.close()
33
+ except:
34
+ x = ''
35
+
36
+ l1 = 0
37
+ t = clock()
38
+ while t == clock():
39
+ l1 += 1
40
+ l2 = 0
41
+ t = long(time()*100)
42
+ while t == long(time()*100):
43
+ l2 += 1
44
+ l3 = 0
45
+ if l2 < 1000:
46
+ t = long(time()*10)
47
+ while t == long(clock()*10):
48
+ l3 += 1
49
+ x += ( repr(time()) + '/' + str(time()) + '/'
50
+ + str(l1) + '/' + str(l2) + '/' + str(l3) + '/'
51
+ + str(getpid()) )
52
+
53
+ s = ''
54
+ for i in sha(x).digest()[-11:]:
55
+ s += mapbase64[ord(i) & 0x3F]
56
+ _idrandom[0] = s
57
+
58
+ resetPeerIDs()
59
+
60
+ def createPeerID(ins = '---'):
61
+ assert type(ins) is StringType
62
+ assert len(ins) == 3
63
+ return _idprefix + ins + _idrandom[0]
@@ -0,0 +1,319 @@
1
+ # Written by Petru Paler, Uoti Urpala, Ross Cohen and John Hoffman
2
+ # see LICENSE.txt for license information
3
+
4
+ from types import IntType, LongType, StringType, ListType, TupleType, DictType
5
+ try:
6
+ from types import BooleanType
7
+ except ImportError:
8
+ BooleanType = None
9
+ try:
10
+ from types import UnicodeType
11
+ except ImportError:
12
+ UnicodeType = None
13
+ from cStringIO import StringIO
14
+
15
+ def decode_int(x, f):
16
+ f += 1
17
+ newf = x.index('e', f)
18
+ try:
19
+ n = int(x[f:newf])
20
+ except:
21
+ n = long(x[f:newf])
22
+ if x[f] == '-':
23
+ if x[f + 1] == '0':
24
+ raise ValueError
25
+ elif x[f] == '0' and newf != f+1:
26
+ raise ValueError
27
+ return (n, newf+1)
28
+
29
+ def decode_string(x, f):
30
+ colon = x.index(':', f)
31
+ try:
32
+ n = int(x[f:colon])
33
+ except (OverflowError, ValueError):
34
+ n = long(x[f:colon])
35
+ if x[f] == '0' and colon != f+1:
36
+ raise ValueError
37
+ colon += 1
38
+ return (x[colon:colon+n], colon+n)
39
+
40
+ def decode_unicode(x, f):
41
+ s, f = decode_string(x, f+1)
42
+ return (s.decode('UTF-8'),f)
43
+
44
+ def decode_list(x, f):
45
+ r, f = [], f+1
46
+ while x[f] != 'e':
47
+ v, f = decode_func[x[f]](x, f)
48
+ r.append(v)
49
+ return (r, f + 1)
50
+
51
+ def decode_dict(x, f):
52
+ r, f = {}, f+1
53
+ lastkey = None
54
+ while x[f] != 'e':
55
+ k, f = decode_string(x, f)
56
+ if lastkey >= k:
57
+ raise ValueError
58
+ lastkey = k
59
+ r[k], f = decode_func[x[f]](x, f)
60
+ return (r, f + 1)
61
+
62
+ decode_func = {}
63
+ decode_func['l'] = decode_list
64
+ decode_func['d'] = decode_dict
65
+ decode_func['i'] = decode_int
66
+ decode_func['0'] = decode_string
67
+ decode_func['1'] = decode_string
68
+ decode_func['2'] = decode_string
69
+ decode_func['3'] = decode_string
70
+ decode_func['4'] = decode_string
71
+ decode_func['5'] = decode_string
72
+ decode_func['6'] = decode_string
73
+ decode_func['7'] = decode_string
74
+ decode_func['8'] = decode_string
75
+ decode_func['9'] = decode_string
76
+ #decode_func['u'] = decode_unicode
77
+
78
+ def bdecode(x, sloppy = 0):
79
+ try:
80
+ r, l = decode_func[x[0]](x, 0)
81
+ # except (IndexError, KeyError):
82
+ except (IndexError, KeyError, ValueError):
83
+ raise ValueError, "bad bencoded data"
84
+ if not sloppy and l != len(x):
85
+ raise ValueError, "bad bencoded data"
86
+ return r
87
+
88
+ def test_bdecode():
89
+ try:
90
+ bdecode('0:0:')
91
+ assert 0
92
+ except ValueError:
93
+ pass
94
+ try:
95
+ bdecode('ie')
96
+ assert 0
97
+ except ValueError:
98
+ pass
99
+ try:
100
+ bdecode('i341foo382e')
101
+ assert 0
102
+ except ValueError:
103
+ pass
104
+ assert bdecode('i4e') == 4L
105
+ assert bdecode('i0e') == 0L
106
+ assert bdecode('i123456789e') == 123456789L
107
+ assert bdecode('i-10e') == -10L
108
+ try:
109
+ bdecode('i-0e')
110
+ assert 0
111
+ except ValueError:
112
+ pass
113
+ try:
114
+ bdecode('i123')
115
+ assert 0
116
+ except ValueError:
117
+ pass
118
+ try:
119
+ bdecode('')
120
+ assert 0
121
+ except ValueError:
122
+ pass
123
+ try:
124
+ bdecode('i6easd')
125
+ assert 0
126
+ except ValueError:
127
+ pass
128
+ try:
129
+ bdecode('35208734823ljdahflajhdf')
130
+ assert 0
131
+ except ValueError:
132
+ pass
133
+ try:
134
+ bdecode('2:abfdjslhfld')
135
+ assert 0
136
+ except ValueError:
137
+ pass
138
+ assert bdecode('0:') == ''
139
+ assert bdecode('3:abc') == 'abc'
140
+ assert bdecode('10:1234567890') == '1234567890'
141
+ try:
142
+ bdecode('02:xy')
143
+ assert 0
144
+ except ValueError:
145
+ pass
146
+ try:
147
+ bdecode('l')
148
+ assert 0
149
+ except ValueError:
150
+ pass
151
+ assert bdecode('le') == []
152
+ try:
153
+ bdecode('leanfdldjfh')
154
+ assert 0
155
+ except ValueError:
156
+ pass
157
+ assert bdecode('l0:0:0:e') == ['', '', '']
158
+ try:
159
+ bdecode('relwjhrlewjh')
160
+ assert 0
161
+ except ValueError:
162
+ pass
163
+ assert bdecode('li1ei2ei3ee') == [1, 2, 3]
164
+ assert bdecode('l3:asd2:xye') == ['asd', 'xy']
165
+ assert bdecode('ll5:Alice3:Bobeli2ei3eee') == [['Alice', 'Bob'], [2, 3]]
166
+ try:
167
+ bdecode('d')
168
+ assert 0
169
+ except ValueError:
170
+ pass
171
+ try:
172
+ bdecode('defoobar')
173
+ assert 0
174
+ except ValueError:
175
+ pass
176
+ assert bdecode('de') == {}
177
+ assert bdecode('d3:agei25e4:eyes4:bluee') == {'age': 25, 'eyes': 'blue'}
178
+ assert bdecode('d8:spam.mp3d6:author5:Alice6:lengthi100000eee') == {'spam.mp3': {'author': 'Alice', 'length': 100000}}
179
+ try:
180
+ bdecode('d3:fooe')
181
+ assert 0
182
+ except ValueError:
183
+ pass
184
+ try:
185
+ bdecode('di1e0:e')
186
+ assert 0
187
+ except ValueError:
188
+ pass
189
+ try:
190
+ bdecode('d1:b0:1:a0:e')
191
+ assert 0
192
+ except ValueError:
193
+ pass
194
+ try:
195
+ bdecode('d1:a0:1:a0:e')
196
+ assert 0
197
+ except ValueError:
198
+ pass
199
+ try:
200
+ bdecode('i03e')
201
+ assert 0
202
+ except ValueError:
203
+ pass
204
+ try:
205
+ bdecode('l01:ae')
206
+ assert 0
207
+ except ValueError:
208
+ pass
209
+ try:
210
+ bdecode('9999:x')
211
+ assert 0
212
+ except ValueError:
213
+ pass
214
+ try:
215
+ bdecode('l0:')
216
+ assert 0
217
+ except ValueError:
218
+ pass
219
+ try:
220
+ bdecode('d0:0:')
221
+ assert 0
222
+ except ValueError:
223
+ pass
224
+ try:
225
+ bdecode('d0:')
226
+ assert 0
227
+ except ValueError:
228
+ pass
229
+
230
+ bencached_marker = []
231
+
232
+ class Bencached:
233
+ def __init__(self, s):
234
+ self.marker = bencached_marker
235
+ self.bencoded = s
236
+
237
+ BencachedType = type(Bencached('')) # insufficient, but good as a filter
238
+
239
+ def encode_bencached(x,r):
240
+ assert x.marker == bencached_marker
241
+ r.append(x.bencoded)
242
+
243
+ def encode_int(x,r):
244
+ r.extend(('i',str(x),'e'))
245
+
246
+ def encode_bool(x,r):
247
+ encode_int(int(x),r)
248
+
249
+ def encode_string(x,r):
250
+ r.extend((str(len(x)),':',x))
251
+
252
+ def encode_unicode(x,r):
253
+ #r.append('u')
254
+ encode_string(x.encode('UTF-8'),r)
255
+
256
+ def encode_list(x,r):
257
+ r.append('l')
258
+ for e in x:
259
+ encode_func[type(e)](e, r)
260
+ r.append('e')
261
+
262
+ def encode_dict(x,r):
263
+ r.append('d')
264
+ ilist = x.items()
265
+ ilist.sort()
266
+ for k,v in ilist:
267
+ r.extend((str(len(k)),':',k))
268
+ encode_func[type(v)](v, r)
269
+ r.append('e')
270
+
271
+ encode_func = {}
272
+ encode_func[BencachedType] = encode_bencached
273
+ encode_func[IntType] = encode_int
274
+ encode_func[LongType] = encode_int
275
+ encode_func[StringType] = encode_string
276
+ encode_func[ListType] = encode_list
277
+ encode_func[TupleType] = encode_list
278
+ encode_func[DictType] = encode_dict
279
+ if BooleanType:
280
+ encode_func[BooleanType] = encode_bool
281
+ if UnicodeType:
282
+ encode_func[UnicodeType] = encode_unicode
283
+
284
+ def bencode(x):
285
+ r = []
286
+ try:
287
+ encode_func[type(x)](x, r)
288
+ except:
289
+ print "*** error *** could not encode type %s (value: %s)" % (type(x), x)
290
+ assert 0
291
+ return ''.join(r)
292
+
293
+ def test_bencode():
294
+ assert bencode(4) == 'i4e'
295
+ assert bencode(0) == 'i0e'
296
+ assert bencode(-10) == 'i-10e'
297
+ assert bencode(12345678901234567890L) == 'i12345678901234567890e'
298
+ assert bencode('') == '0:'
299
+ assert bencode('abc') == '3:abc'
300
+ assert bencode('1234567890') == '10:1234567890'
301
+ assert bencode([]) == 'le'
302
+ assert bencode([1, 2, 3]) == 'li1ei2ei3ee'
303
+ assert bencode([['Alice', 'Bob'], [2, 3]]) == 'll5:Alice3:Bobeli2ei3eee'
304
+ assert bencode({}) == 'de'
305
+ assert bencode({'age': 25, 'eyes': 'blue'}) == 'd3:agei25e4:eyes4:bluee'
306
+ assert bencode({'spam.mp3': {'author': 'Alice', 'length': 100000}}) == 'd8:spam.mp3d6:author5:Alice6:lengthi100000eee'
307
+ try:
308
+ bencode({1: 'foo'})
309
+ assert 0
310
+ except AssertionError:
311
+ pass
312
+
313
+
314
+ try:
315
+ import psyco
316
+ psyco.bind(bdecode)
317
+ psyco.bind(bencode)
318
+ except ImportError:
319
+ pass