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,31 @@
1
+ connChoices=(
2
+ {'name':'automatic',
3
+ 'rate':{'min':0, 'max':5000, 'def': 0},
4
+ 'conn':{'min':0, 'max':100, 'def': 0},
5
+ 'automatic':1},
6
+ {'name':'unlimited',
7
+ 'rate':{'min':0, 'max':5000, 'def': 0, 'div': 50},
8
+ 'conn':{'min':4, 'max':100, 'def': 4}},
9
+ {'name':'dialup/isdn',
10
+ 'rate':{'min':3, 'max': 8, 'def': 5},
11
+ 'conn':{'min':2, 'max': 3, 'def': 2},
12
+ 'initiate': 12},
13
+ {'name':'dsl/cable slow',
14
+ 'rate':{'min':10, 'max': 48, 'def': 13},
15
+ 'conn':{'min':4, 'max': 20, 'def': 4}},
16
+ {'name':'dsl/cable fast',
17
+ 'rate':{'min':20, 'max': 100, 'def': 40},
18
+ 'conn':{'min':4, 'max': 30, 'def': 6}},
19
+ {'name':'T1',
20
+ 'rate':{'min':100, 'max': 300, 'def':150},
21
+ 'conn':{'min':4, 'max': 40, 'def':10}},
22
+ {'name':'T3+',
23
+ 'rate':{'min':400, 'max':2000, 'def':500},
24
+ 'conn':{'min':4, 'max':100, 'def':20}},
25
+ {'name':'seeder',
26
+ 'rate':{'min':0, 'max':5000, 'def':0, 'div': 50},
27
+ 'conn':{'min':1, 'max':100, 'def':1}},
28
+ {'name':'SUPER-SEED', 'super-seed':1}
29
+ )
30
+
31
+ connChoiceList = map(lambda x:x['name'], connChoices)
@@ -0,0 +1,105 @@
1
+ # Generated from bt_MakeCreateIcons - 05/10/04 22:15:33
2
+ # T-0.3.0 (BitTornado)
3
+
4
+ from binascii import a2b_base64
5
+ from zlib import decompress
6
+ from os.path import join
7
+
8
+ icons = {
9
+ "icon_bt.ico":
10
+ "eJyt1K+OFEEQx/FaQTh5GDRZhSQpiUHwCrxCBYXFrjyJLXeXEARPsZqUPMm+" +
11
+ "AlmP+PGtngoLDji69zMz2zt/qqtr1mxHv7621d4+MnvK/jl66Bl2drV+e7Wz" +
12
+ "S/v12A7rY4fDtuvOwfF4tOPXo52/fLLz+WwpWd6nqRXHKXux39sTrtnjNd7g" +
13
+ "PW7wGSd860f880kffjvJ2QYS1Zcw4AjcoaA5yRFIFDQXOgKJguZmjkCioB4T" +
14
+ "Y2CqxpTXA7sHEgVNEC8RSBQ0gfk7xtknCupgk3EEEgXlNgFHIFHQTMoRSBQ0" +
15
+ "E+1ouicKmsk7AomCJiGOQKKgSZIjkChoEucIJAqaZDoCiYImwb4iydULmqQ7" +
16
+ "AomC1kLcEQ/jSBQ0i+MIJAqaBXMEElVdi9siOgKJgmZhfWWlVjTddXW/FtsR" +
17
+ "SBQ0BeAIJAqaonAEEgVNoTgCiYKmeByBREHaqiVWRtSRrAJzBBIFTdE5AomC" +
18
+ "phBPpxPP57dVkDfrTl063nUVnWe383fZx9tb3uN+o7U+BLDtuvcQm8d/27Y/" +
19
+ "jO3o5/ay+YPv/+f6y30e1OyB7QcsGWFj",
20
+ "icon_done.ico":
21
+ "eJyt1K2OVEEQhuEaQbJyMWgyCklSEoPgFvYWKigsduRKbLndhCC4itGk5Erm" +
22
+ "Fsh4xMdbfSoMOGDpnuf89Jyf6uqaMdvRr69ttbdPzJ6xf4Eeeo6dXa3vXu/s" +
23
+ "0n49tsP62OGw7bpzcDwe7fj1aOcvn+x8PltKlg9pasVxyl7u9/aUe/Z4gxu8" +
24
+ "xy0+44Rv/Yp/vujDbxc520Ci+hYGHIF7FDQXOQKJguZGRyBR0DzMEUgU1GNi" +
25
+ "DEzVmPJ6YfdAoqAJ4hUCiYImMH/HOPtEQR1sMo5AoqDcJuAIJAqaSTkCiYJm" +
26
+ "oh1N90RBM3lHIFHQJMQRSBQ0SXIEEgVN4hyBREGTTEcgUdAk2FckuXpBk3RH" +
27
+ "IFHQWoh74mEciYJmcRyBREGzYI5AoqprcVtERyBR0Cysr6zUiqa7rh7WYjsC" +
28
+ "iYKmAByBREFTFI5AoqApFEcgUdAUjyOQKEhbtcTKiDqSVWCOQKKgKTpHIFHQ" +
29
+ "FOLpdOL9fLcK8nY9qUvHu66i8+x2/i77eHfH77h/0VofAth23Xuoz/+2bX8Y" +
30
+ "29HP7WXzB+f/5/7Lcx7V7JHtB9dPG3I=",
31
+ "black.ico":
32
+ "eJzt1zsOgkAYReFLLCztjJ2UlpLY485kOS7DpbgESwqTcQZDghjxZwAfyfl0" +
33
+ "LIieGzUWSom/pan840rHnbSUtPHHX9Je9+tAh2ybNe8TZZ/vk8ajJ4zl6JVJ" +
34
+ "+xFx+0R03Djx1/2B8bcT9L/bt0+4Wq+4se8e/VTfMvGqb4n3nYiIGz+lvt9s" +
35
+ "9EpE2T4xJN4xNFYWU6t+JWXuXDFzTom7SodSyi/S+iwtwjlJ80KaNY/C34rW" +
36
+ "aT8nvK5uhF7ohn7Yqfb87kffLAAAAAAAAAAAAAAAAAAAGMUNy7dADg==",
37
+ "blue.ico":
38
+ "eJzt10EOwUAYhuGv6cLSTux06QD2dTM9jmM4iiNYdiEZ81cIFTWddtDkfbQW" +
39
+ "De8XogtS5h9FIf+81H4jLSSt/ekvaavrdaCDez4SZV+PpPHoicBy9ErSfkQ8" +
40
+ "fCI6Hjgx6f7A+McJ+r/t95i46xMP7bf8Uz9o4k0/XMT338voP5shK0MkjXcM" +
41
+ "YSqam6Qunatyf7Nk7iztaqk8SaujNLfzIM0qKX88ZX8rWmf7Nfa+W8N61rW+" +
42
+ "7TR7fverHxYAAAAAAAAAAAAAAAAAAIziApVZ444=",
43
+ "green.ico":
44
+ "eJzt1zEOgjAAheFHGBzdjJuMHsAdbybxNB7Do3gERwaT2mJIBCOWlqok/yc4" +
45
+ "EP1fNDIoZfZRFLLPa5120krS1p72kvZ6XAeGHLtHouzrkTQePOFZDl5J2g+I" +
46
+ "+08Exz0nZt2PjH+coP/bvveEaY2L+/VN13/1PSbe9v0FfP+jTP6ziVmJkTQ+" +
47
+ "MISZaO6SujSmyu3dkpmbdKil8iptLtLSnWdpUUn58yn3t6J39l/j3tc2XM91" +
48
+ "Xd/tNHt296sfFgAAAAAAAAAAAAAAAAAATOIOVLEoDg==",
49
+ "red.ico":
50
+ "eJzt10EOwUAYhuGv6cLSTux06QD2dTOO4xiO4giWXUjG/BVCRTuddtDkfbQW" +
51
+ "De8XogtS5h9FIf+81GEjLSSt/ekvaavbdaCVez0SZd+PpPHoicBy9ErSfkQ8" +
52
+ "fCI6Hjgx6f7AeOcE/d/2QyceesaD+g1/1u+e+NwPF/H99zL6z2bIyhBJ4y1D" +
53
+ "mIb6LqlK5/a5v1syd5F2lVSepdVJmtt5lGZ7KX8+ZX8rGmfzNfa+e8N61rW+" +
54
+ "7dR7fverHxYAAAAAAAAAAAAAAAAAAIziCpgs444=",
55
+ "white.ico":
56
+ "eJzt1zsOgkAYReFLKCztjJ2ULsAed6bLcRnuwYTaJVhSmIwzGBLEiD8D+EjO" +
57
+ "p2NB9NyosVBK/C3L5B+XOmykhaS1P/6StrpfBzoUp6J5nyj7fJ80Hj1hLEev" +
58
+ "TNqPiNsnouPGib/uD4y/naD/3b59wtV6xY199+in+paJV31LvO9ERNz4KfX9" +
59
+ "ZqNXIsr2iSHxjqGxspha9Sspc+f2qXNK3FXalVJ+kVZnaR7OUZrtpbR5FP5W" +
60
+ "tE77OeF1dSP0Qjf0w06153c/+mYBAAAAAAAAAAAAAAAAAMAobj//I7s=",
61
+ "yellow.ico":
62
+ "eJzt1zsOgkAYReFLKCztjJ2ULsAedybLcRkuxSVYUpiM82M0ihGHgVFJzidY" +
63
+ "ED03vgqlzN+KQv5+qf1GWkha+9Nf0lbX60AX556ORNnXI2k8eiKwHL2StB8R" +
64
+ "D5+IjgdOTLo/MP5xgv5v+8ETd/3iYf2W/+oHTLzth4t4/3sZ/WszZGWIpPGO" +
65
+ "IUxE8yupS+eq3H9smTtLu1oqT9LqKM3tPEizSsofT9nfitbZfow979awnnWt" +
66
+ "bzvNnt/96osFAAAAAAAAAAAAAAAAAACjuABhjmIs",
67
+ "black1.ico":
68
+ "eJzt0zEOgkAUANEhFpZSGTstTWzkVt5Cj8ZROAIHMNGPWBCFDYgxMZkHn2Iz" +
69
+ "G5YCyOLKc+K54XSANbCPiSV2tOt/qjgW3XtSnN41FH/Qv29Jx/P7qefp7W8P" +
70
+ "4z85HQ+9JRG/7BpTft31DPUKyiVcFjEZzQ/TTtdzrWnKmCr6evv780qSJEmS" +
71
+ "JEmSJEmSJEmSpPnunVFDcA==",
72
+ "green1.ico":
73
+ "eJzt0zEKwkAQRuEXLCyTSuy0DHgxb6F4shzFI+QAgpkkFoombowIwvt2Z4vh" +
74
+ "X5gtFrJYRUGca/Y7WAFlVLTY0vf/1elxTwqP3xoKf5B/vjIenp+fOs+r/LWT" +
75
+ "/uQ34aGpUqQnv+1ygDqHagnHRVRG+2H6unfrtZkq6hz5evP7eSVJkiRJkiRJ" +
76
+ "kiRJkiRJ0nwNoWQ+AA==",
77
+ "yellow1.ico":
78
+ "eJzt0zEKwkAQRuEXLCxNJXZaCl7MW8Sj5SgeIQcQ4oS1UDTJxkhAeN/ubDH8" +
79
+ "C7PFQhGrLIlzx/kEW+AYFS0OpP6/atuXPSk8fKsv/EX+/cpweH5+6jyf8kn+" +
80
+ "k0fCfVPlyE/+2q2CZgP1Gi6rqILuw6R69uh1mTrqGvlmv/y8kiRJkiRJkiRJ" +
81
+ "kiRJkiRpvjsp9L8k",
82
+ "alloc.gif":
83
+ "eJxz93SzsEw0YRBh+M4ABi0MS3ue///P8H8UjIIRBhR/sjAyMDAx6IAyAihP" +
84
+ "MHAcYWDlkPHYsOBgM4ewVsyJDQsPNzEoebF8CHjo0smjH3dmRsDjI33C7Dw3" +
85
+ "MiYuOtjNyDShRSNwyemJguJJKhaGS32nGka61Vg2NJyYKRd+bY+nwtMzjbqV" +
86
+ "Qh84gxMCJgnlL4vJuqJyaa5NfFLNLsNVV2a7syacfVWkHd4bv7RN1ltM7ejm" +
87
+ "tMtNZ19Oyb02p8C3aqr3dr2GbXl/7fZyOej5rW653WZ7MzzHZV+v7O2/EZM+" +
88
+ "Pt45kbX6ScWHNWfOilo3n5thucXv8org1XF3DRQYrAEWiVY3"
89
+ }
90
+
91
+ def GetIcons():
92
+ return icons.keys()
93
+
94
+ def CreateIcon(icon, savedir):
95
+ try:
96
+ f = open(join(savedir,icon),"wb")
97
+ f.write(decompress(a2b_base64(icons[icon])))
98
+ success = 1
99
+ except:
100
+ success = 0
101
+ try:
102
+ f.close()
103
+ except:
104
+ pass
105
+ return success
@@ -0,0 +1,37 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from clock import clock
5
+
6
+ class Measure:
7
+ def __init__(self, max_rate_period, fudge = 1):
8
+ self.max_rate_period = max_rate_period
9
+ self.ratesince = clock() - fudge
10
+ self.last = self.ratesince
11
+ self.rate = 0.0
12
+ self.total = 0l
13
+
14
+ def update_rate(self, amount):
15
+ self.total += amount
16
+ t = clock()
17
+ self.rate = (self.rate * (self.last - self.ratesince) +
18
+ amount) / (t - self.ratesince + 0.0001)
19
+ self.last = t
20
+ if self.ratesince < t - self.max_rate_period:
21
+ self.ratesince = t - self.max_rate_period
22
+
23
+ def get_rate(self):
24
+ self.update_rate(0)
25
+ return self.rate
26
+
27
+ def get_rate_noupdate(self):
28
+ return self.rate
29
+
30
+ def time_until_rate(self, newrate):
31
+ if self.rate <= newrate:
32
+ return 0
33
+ t = clock() - self.ratesince
34
+ return ((self.rate * t) / newrate) - t
35
+
36
+ def get_total(self):
37
+ return self.total
@@ -0,0 +1,167 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from cStringIO import StringIO
5
+ from sys import stdout
6
+ import time
7
+ from clock import clock
8
+ from gzip import GzipFile
9
+ try:
10
+ True
11
+ except:
12
+ True = 1
13
+ False = 0
14
+
15
+ DEBUG = False
16
+
17
+ weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
18
+
19
+ months = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
20
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
21
+
22
+ class HTTPConnection:
23
+ def __init__(self, handler, connection):
24
+ self.handler = handler
25
+ self.connection = connection
26
+ self.buf = ''
27
+ self.closed = False
28
+ self.done = False
29
+ self.donereading = False
30
+ self.next_func = self.read_type
31
+
32
+ def get_ip(self):
33
+ return self.connection.get_ip()
34
+
35
+ def data_came_in(self, data):
36
+ if self.donereading or self.next_func is None:
37
+ return True
38
+ self.buf += data
39
+ while True:
40
+ try:
41
+ i = self.buf.index('\n')
42
+ except ValueError:
43
+ return True
44
+ val = self.buf[:i]
45
+ self.buf = self.buf[i+1:]
46
+ self.next_func = self.next_func(val)
47
+ if self.donereading:
48
+ return True
49
+ if self.next_func is None or self.closed:
50
+ return False
51
+
52
+ def read_type(self, data):
53
+ self.header = data.strip()
54
+ words = data.split()
55
+ if len(words) == 3:
56
+ self.command, self.path, garbage = words
57
+ self.pre1 = False
58
+ elif len(words) == 2:
59
+ self.command, self.path = words
60
+ self.pre1 = True
61
+ if self.command != 'GET':
62
+ return None
63
+ else:
64
+ return None
65
+ if self.command not in ('HEAD', 'GET'):
66
+ return None
67
+ self.headers = {}
68
+ return self.read_header
69
+
70
+ def read_header(self, data):
71
+ data = data.strip()
72
+ if data == '':
73
+ self.donereading = True
74
+ if self.headers.get('accept-encoding','').find('gzip') > -1:
75
+ self.encoding = 'gzip'
76
+ else:
77
+ self.encoding = 'identity'
78
+ r = self.handler.getfunc(self, self.path, self.headers)
79
+ if r is not None:
80
+ self.answer(r)
81
+ return None
82
+ try:
83
+ i = data.index(':')
84
+ except ValueError:
85
+ return None
86
+ self.headers[data[:i].strip().lower()] = data[i+1:].strip()
87
+ if DEBUG:
88
+ print data[:i].strip() + ": " + data[i+1:].strip()
89
+ return self.read_header
90
+
91
+ def answer(self, (responsecode, responsestring, headers, data)):
92
+ if self.closed:
93
+ return
94
+ if self.encoding == 'gzip':
95
+ compressed = StringIO()
96
+ gz = GzipFile(fileobj = compressed, mode = 'wb', compresslevel = 9)
97
+ gz.write(data)
98
+ gz.close()
99
+ cdata = compressed.getvalue()
100
+ if len(cdata) >= len(data):
101
+ self.encoding = 'identity'
102
+ else:
103
+ if DEBUG:
104
+ print "Compressed: %i Uncompressed: %i\n" % (len(cdata),len(data))
105
+ data = cdata
106
+ headers['Content-Encoding'] = 'gzip'
107
+
108
+ # i'm abusing the identd field here, but this should be ok
109
+ if self.encoding == 'identity':
110
+ ident = '-'
111
+ else:
112
+ ident = self.encoding
113
+ self.handler.log( self.connection.get_ip(), ident, '-',
114
+ self.header, responsecode, len(data),
115
+ self.headers.get('referer','-'),
116
+ self.headers.get('user-agent','-') )
117
+ self.done = True
118
+ r = StringIO()
119
+ r.write('HTTP/1.0 ' + str(responsecode) + ' ' +
120
+ responsestring + '\r\n')
121
+ if not self.pre1:
122
+ headers['Content-Length'] = len(data)
123
+ for key, value in headers.items():
124
+ r.write(key + ': ' + str(value) + '\r\n')
125
+ r.write('\r\n')
126
+ if self.command != 'HEAD':
127
+ r.write(data)
128
+ self.connection.write(r.getvalue())
129
+ if self.connection.is_flushed():
130
+ self.connection.shutdown(1)
131
+
132
+ class HTTPHandler:
133
+ def __init__(self, getfunc, minflush):
134
+ self.connections = {}
135
+ self.getfunc = getfunc
136
+ self.minflush = minflush
137
+ self.lastflush = clock()
138
+
139
+ def external_connection_made(self, connection):
140
+ self.connections[connection] = HTTPConnection(self, connection)
141
+
142
+ def connection_flushed(self, connection):
143
+ if self.connections[connection].done:
144
+ connection.shutdown(1)
145
+
146
+ def connection_lost(self, connection):
147
+ ec = self.connections[connection]
148
+ ec.closed = True
149
+ del ec.connection
150
+ del ec.next_func
151
+ del self.connections[connection]
152
+
153
+ def data_came_in(self, connection, data):
154
+ c = self.connections[connection]
155
+ if not c.data_came_in(data) and not c.closed:
156
+ c.connection.shutdown(1)
157
+
158
+ def log(self, ip, ident, username, header,
159
+ responsecode, length, referrer, useragent):
160
+ year, month, day, hour, minute, second, a, b, c = time.localtime(time.time())
161
+ print '%s %s %s [%02d/%3s/%04d:%02d:%02d:%02d] "%s" %i %i "%s" "%s"' % (
162
+ ip, ident, username, day, months[month], year, hour,
163
+ minute, second, header, responsecode, length, referrer, useragent)
164
+ t = clock()
165
+ if t - self.lastflush > self.minflush:
166
+ self.lastflush = t
167
+ stdout.flush()
@@ -0,0 +1,5 @@
1
+ # edit this file to enable/disable Psyco
2
+ # psyco = 1 -- enabled
3
+ # psyco = 0 -- disabled
4
+
5
+ psyco = 0
@@ -0,0 +1,153 @@
1
+ # Written by Bram Cohen
2
+ # see LICENSE.txt for license information
3
+
4
+ from traceback import print_exc
5
+ from binascii import b2a_hex
6
+ from clock import clock
7
+ from CurrentRateMeasure import Measure
8
+ from cStringIO import StringIO
9
+ from math import sqrt
10
+
11
+ try:
12
+ True
13
+ except:
14
+ True = 1
15
+ False = 0
16
+ try:
17
+ sum([1])
18
+ except:
19
+ sum = lambda a: reduce(lambda x,y: x+y, a, 0)
20
+
21
+ DEBUG = False
22
+
23
+ MAX_RATE_PERIOD = 20.0
24
+ MAX_RATE = 10e10
25
+ PING_BOUNDARY = 1.2
26
+ PING_SAMPLES = 7
27
+ PING_DISCARDS = 1
28
+ PING_THRESHHOLD = 5
29
+ PING_DELAY = 5 # cycles 'til first upward adjustment
30
+ PING_DELAY_NEXT = 2 # 'til next
31
+ ADJUST_UP = 1.05
32
+ ADJUST_DOWN = 0.95
33
+ UP_DELAY_FIRST = 5
34
+ UP_DELAY_NEXT = 2
35
+ SLOTS_STARTING = 6
36
+ SLOTS_FACTOR = 1.66/1000
37
+
38
+ class RateLimiter:
39
+ def __init__(self, sched, unitsize, slotsfunc = lambda x: None):
40
+ self.sched = sched
41
+ self.last = None
42
+ self.unitsize = unitsize
43
+ self.slotsfunc = slotsfunc
44
+ self.measure = Measure(MAX_RATE_PERIOD)
45
+ self.autoadjust = False
46
+ self.upload_rate = MAX_RATE * 1000
47
+ self.slots = SLOTS_STARTING # garbage if not automatic
48
+
49
+ def set_upload_rate(self, rate):
50
+ # rate = -1 # test automatic
51
+ if rate < 0:
52
+ if self.autoadjust:
53
+ return
54
+ self.autoadjust = True
55
+ self.autoadjustup = 0
56
+ self.pings = []
57
+ rate = MAX_RATE
58
+ self.slots = SLOTS_STARTING
59
+ self.slotsfunc(self.slots)
60
+ else:
61
+ self.autoadjust = False
62
+ if not rate:
63
+ rate = MAX_RATE
64
+ self.upload_rate = rate * 1000
65
+ self.lasttime = clock()
66
+ self.bytes_sent = 0
67
+
68
+ def queue(self, conn):
69
+ assert conn.next_upload is None
70
+ if self.last is None:
71
+ self.last = conn
72
+ conn.next_upload = conn
73
+ self.try_send(True)
74
+ else:
75
+ conn.next_upload = self.last.next_upload
76
+ self.last.next_upload = conn
77
+ self.last = conn
78
+
79
+ def try_send(self, check_time = False):
80
+ t = clock()
81
+ self.bytes_sent -= (t - self.lasttime) * self.upload_rate
82
+ self.lasttime = t
83
+ if check_time:
84
+ self.bytes_sent = max(self.bytes_sent, 0)
85
+ cur = self.last.next_upload
86
+ while self.bytes_sent <= 0:
87
+ bytes = cur.send_partial(self.unitsize)
88
+ self.bytes_sent += bytes
89
+ self.measure.update_rate(bytes)
90
+ if bytes == 0 or cur.backlogged():
91
+ if self.last is cur:
92
+ self.last = None
93
+ cur.next_upload = None
94
+ break
95
+ else:
96
+ self.last.next_upload = cur.next_upload
97
+ cur.next_upload = None
98
+ cur = self.last.next_upload
99
+ else:
100
+ self.last = cur
101
+ cur = cur.next_upload
102
+ else:
103
+ self.sched(self.try_send, self.bytes_sent / self.upload_rate)
104
+
105
+ def adjust_sent(self, bytes):
106
+ self.bytes_sent = min(self.bytes_sent+bytes, self.upload_rate*3)
107
+ self.measure.update_rate(bytes)
108
+
109
+
110
+ def ping(self, delay):
111
+ if DEBUG:
112
+ print delay
113
+ if not self.autoadjust:
114
+ return
115
+ self.pings.append(delay > PING_BOUNDARY)
116
+ if len(self.pings) < PING_SAMPLES+PING_DISCARDS:
117
+ return
118
+ if DEBUG:
119
+ print 'cycle'
120
+ pings = sum(self.pings[PING_DISCARDS:])
121
+ del self.pings[:]
122
+ if pings >= PING_THRESHHOLD: # assume flooded
123
+ if self.upload_rate == MAX_RATE:
124
+ self.upload_rate = self.measure.get_rate()*ADJUST_DOWN
125
+ else:
126
+ self.upload_rate = min(self.upload_rate,
127
+ self.measure.get_rate()*1.1)
128
+ self.upload_rate = max(int(self.upload_rate*ADJUST_DOWN),2)
129
+ self.slots = int(sqrt(self.upload_rate*SLOTS_FACTOR))
130
+ self.slotsfunc(self.slots)
131
+ if DEBUG:
132
+ print 'adjust down to '+str(self.upload_rate)
133
+ self.lasttime = clock()
134
+ self.bytes_sent = 0
135
+ self.autoadjustup = UP_DELAY_FIRST
136
+ else: # not flooded
137
+ if self.upload_rate == MAX_RATE:
138
+ return
139
+ self.autoadjustup -= 1
140
+ if self.autoadjustup:
141
+ return
142
+ self.upload_rate = int(self.upload_rate*ADJUST_UP)
143
+ self.slots = int(sqrt(self.upload_rate*SLOTS_FACTOR))
144
+ self.slotsfunc(self.slots)
145
+ if DEBUG:
146
+ print 'adjust up to '+str(self.upload_rate)
147
+ self.lasttime = clock()
148
+ self.bytes_sent = 0
149
+ self.autoadjustup = UP_DELAY_NEXT
150
+
151
+
152
+
153
+