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,100 @@
1
+ # Written by John Hoffman
2
+ # see LICENSE.txt for license information
3
+
4
+ from httplib import HTTPConnection, HTTPSConnection, HTTPException
5
+ from urlparse import urlparse
6
+ from bencode import bdecode
7
+ import socket
8
+ from gzip import GzipFile
9
+ from StringIO import StringIO
10
+ from urllib import quote, unquote
11
+ from __init__ import product_name, version_short
12
+
13
+ VERSION = product_name+'/'+version_short
14
+ MAX_REDIRECTS = 10
15
+
16
+
17
+ class btHTTPcon(HTTPConnection): # attempt to add automatic connection timeout
18
+ def connect(self):
19
+ HTTPConnection.connect(self)
20
+ try:
21
+ self.sock.settimeout(30)
22
+ except:
23
+ pass
24
+
25
+ class btHTTPScon(HTTPSConnection): # attempt to add automatic connection timeout
26
+ def connect(self):
27
+ HTTPSConnection.connect(self)
28
+ try:
29
+ self.sock.settimeout(30)
30
+ except:
31
+ pass
32
+
33
+ class urlopen:
34
+ def __init__(self, url):
35
+ self.tries = 0
36
+ self._open(url.strip())
37
+ self.error_return = None
38
+
39
+ def _open(self, url):
40
+ self.tries += 1
41
+ if self.tries > MAX_REDIRECTS:
42
+ raise IOError, ('http error', 500,
43
+ "Internal Server Error: Redirect Recursion")
44
+ (scheme, netloc, path, pars, query, fragment) = urlparse(url)
45
+ if scheme != 'http' and scheme != 'https':
46
+ raise IOError, ('url error', 'unknown url type', scheme, url)
47
+ url = path
48
+ if pars:
49
+ url += ';'+pars
50
+ if query:
51
+ url += '?'+query
52
+ # if fragment:
53
+ try:
54
+ if scheme == 'http':
55
+ self.connection = btHTTPcon(netloc)
56
+ else:
57
+ self.connection = btHTTPScon(netloc)
58
+ self.connection.request('GET', url, None,
59
+ { 'User-Agent': VERSION,
60
+ 'Accept-Encoding': 'gzip' } )
61
+ self.response = self.connection.getresponse()
62
+ except HTTPException, e:
63
+ raise IOError, ('http error', str(e))
64
+ status = self.response.status
65
+ if status in (301,302):
66
+ try:
67
+ self.connection.close()
68
+ except:
69
+ pass
70
+ self._open(self.response.getheader('Location'))
71
+ return
72
+ if status != 200:
73
+ try:
74
+ data = self._read()
75
+ d = bdecode(data)
76
+ if d.has_key('failure reason'):
77
+ self.error_return = data
78
+ return
79
+ except:
80
+ pass
81
+ raise IOError, ('http error', status, self.response.reason)
82
+
83
+ def read(self):
84
+ if self.error_return:
85
+ return self.error_return
86
+ return self._read()
87
+
88
+ def _read(self):
89
+ data = self.response.read()
90
+ if self.response.getheader('Content-Encoding','').find('gzip') >= 0:
91
+ try:
92
+ compressed = StringIO(data)
93
+ f = GzipFile(fileobj = compressed)
94
+ data = f.read()
95
+ except:
96
+ raise IOError, ('http error', 'got corrupt response')
97
+ return data
98
+
99
+ def close(self):
100
+ self.connection.close()
@@ -0,0 +1,291 @@
1
+ # Copyright 2010 Twitter, Inc.
2
+ # Copyright 2010 Larry Gadea <lg@twitter.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Usage: python murder_client.py peer/seed out.torrent OUT.OUT 127.0.0.1
17
+ # last parameter is the local ip address, normally 10.x.x.x
18
+
19
+ import warnings
20
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
21
+
22
+ from BitTornado import PSYCO
23
+ if PSYCO.psyco:
24
+ try:
25
+ import psyco
26
+ assert psyco.__version__ >= 0x010100f0
27
+ psyco.full()
28
+ except:
29
+ pass
30
+
31
+ from BitTornado.download_bt1 import BT1Download, defaults, parse_params, get_usage, get_response
32
+ from BitTornado.RawServer import RawServer, UPnP_ERROR
33
+ from random import seed
34
+ from socket import error as socketerror
35
+ from BitTornado.bencode import bencode
36
+ from BitTornado.natpunch import UPnP_test
37
+ from threading import Event
38
+ from os.path import abspath
39
+ from sys import argv, stdout
40
+ import sys
41
+ import os
42
+ import threading
43
+ from sha import sha
44
+ from time import strftime
45
+ from BitTornado.clock import clock
46
+ from BitTornado import createPeerID, version
47
+ from BitTornado.ConfigDir import ConfigDir
48
+
49
+ assert sys.version >= '2', "Install Python 2.0 or greater"
50
+ try:
51
+ True
52
+ except:
53
+ True = 1
54
+ False = 0
55
+
56
+ doneFlag = None
57
+ isPeer = False
58
+
59
+ def ok_close_now():
60
+ doneFlag.set()
61
+
62
+ def hours(n):
63
+ if n == 0:
64
+ return 'complete!'
65
+ try:
66
+ n = int(n)
67
+ assert n >= 0 and n < 5184000 # 60 days
68
+ except:
69
+ return '<unknown>'
70
+ m, s = divmod(n, 60)
71
+ h, m = divmod(m, 60)
72
+ if h > 0:
73
+ return '%d hour %02d min %02d sec' % (h, m, s)
74
+ else:
75
+ return '%d min %02d sec' % (m, s)
76
+
77
+ class HeadlessDisplayer:
78
+ def __init__(self):
79
+ self.done = False
80
+ self.file = ''
81
+ self.percentDone = ''
82
+ self.timeEst = ''
83
+ self.downloadTo = ''
84
+ self.downRate = ''
85
+ self.upRate = ''
86
+ self.shareRating = ''
87
+ self.seedStatus = ''
88
+ self.peerStatus = ''
89
+ self.errors = []
90
+ self.last_update_time = -1
91
+
92
+ def finished(self):
93
+ global doneFlag
94
+
95
+ self.done = True
96
+ self.percentDone = '100'
97
+ self.timeEst = 'Download Succeeded!'
98
+ self.downRate = ''
99
+ #self.display()
100
+
101
+ global isPeer
102
+
103
+ print "done and done"
104
+
105
+ if isPeer:
106
+ if os.fork():
107
+ os._exit(0)
108
+ return
109
+
110
+ os.setsid()
111
+ if os.fork():
112
+ os._exit(0)
113
+ return
114
+
115
+ os.close(0)
116
+ os.close(1)
117
+ os.close(2)
118
+
119
+ t = threading.Timer(30.0, ok_close_now)
120
+ t.start()
121
+
122
+ def failed(self):
123
+ self.done = True
124
+ self.percentDone = '0'
125
+ self.timeEst = 'Download Failed!'
126
+ self.downRate = ''
127
+ global doneFlag
128
+ doneFlag.set()
129
+ #self.display()
130
+
131
+ def error(self, errormsg):
132
+ #self.errors.append(errormsg)
133
+ self.display()
134
+ global doneFlag
135
+ print errormsg
136
+ doneFlag.set()
137
+
138
+ def display(self, dpflag = Event(), fractionDone = None, timeEst = None,
139
+ downRate = None, upRate = None, activity = None,
140
+ statistics = None, **kws):
141
+ if self.last_update_time + 0.1 > clock() and fractionDone not in (0.0, 1.0) and activity is not None:
142
+ return
143
+ self.last_update_time = clock()
144
+ if fractionDone is not None:
145
+ self.percentDone = str(float(int(fractionDone * 1000)) / 10)
146
+ if timeEst is not None:
147
+ self.timeEst = hours(timeEst)
148
+ if activity is not None and not self.done:
149
+ self.timeEst = activity
150
+ if downRate is not None:
151
+ self.downRate = '%.1f kB/s' % (float(downRate) / (1 << 10))
152
+ if upRate is not None:
153
+ self.upRate = '%.1f kB/s' % (float(upRate) / (1 << 10))
154
+ if statistics is not None:
155
+ if (statistics.shareRating < 0) or (statistics.shareRating > 100):
156
+ self.shareRating = 'oo (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
157
+ else:
158
+ self.shareRating = '%.3f (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
159
+ if not self.done:
160
+ self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies))
161
+ else:
162
+ self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
163
+ self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
164
+ #print '\n\n\n\n'
165
+ for err in self.errors:
166
+ print 'ERROR:\n' + err + '\n'
167
+ #print 'saving: ', self.file
168
+ #print 'percent done: ', self.percentDone
169
+ #print 'time left: ', self.timeEst
170
+ #print 'download to: ', self.downloadTo
171
+ #print 'download rate: ', self.downRate
172
+ #print 'upload rate: ', self.upRate
173
+ #print 'share rating: ', self.shareRating
174
+ #print 'seed status: ', self.seedStatus
175
+ #print 'peer status: ', self.peerStatus
176
+ #stdout.flush()
177
+ dpflag.set()
178
+
179
+ def chooseFile(self, default, size, saveas, dir):
180
+ self.file = '%s (%.1f MB)' % (default, float(size) / (1 << 20))
181
+ if saveas != '':
182
+ default = saveas
183
+ self.downloadTo = abspath(default)
184
+ return default
185
+
186
+ def newpath(self, path):
187
+ self.downloadTo = path
188
+
189
+ def run(params):
190
+ cols = 80
191
+
192
+ h = HeadlessDisplayer()
193
+ while 1:
194
+ configdir = ConfigDir('downloadheadless')
195
+ defaultsToIgnore = ['responsefile', 'url', 'priority']
196
+ configdir.setDefaults(defaults,defaultsToIgnore)
197
+ configdefaults = configdir.loadConfig()
198
+ defaults.append(('save_options',0,
199
+ "whether to save the current options as the new default configuration " +
200
+ "(only for btdownloadheadless.py)"))
201
+ try:
202
+ config = parse_params(params, configdefaults)
203
+ except ValueError, e:
204
+ print 'error: ' + str(e) + '\nrun with no args for parameter explanations'
205
+ break
206
+ if not config:
207
+ print get_usage(defaults, 80, configdefaults)
208
+ break
209
+ if config['save_options']:
210
+ configdir.saveConfig(config)
211
+ configdir.deleteOldCacheData(config['expire_cache_data'])
212
+
213
+ myid = createPeerID()
214
+ seed(myid)
215
+
216
+ global doneFlag
217
+ doneFlag = Event()
218
+ def disp_exception(text):
219
+ print text
220
+ rawserver = RawServer(doneFlag, config['timeout_check_interval'],
221
+ config['timeout'], ipv6_enable = config['ipv6_enabled'],
222
+ failfunc = h.failed, errorfunc = disp_exception)
223
+ upnp_type = UPnP_test(config['upnp_nat_access'])
224
+ while True:
225
+ try:
226
+ listen_port = rawserver.find_and_bind(config['minport'], config['maxport'],
227
+ config['bind'], ipv6_socket_style = config['ipv6_binds_v4'],
228
+ upnp = upnp_type, randomizer = config['random_port'])
229
+ break
230
+ except socketerror, e:
231
+ if upnp_type and e == UPnP_ERROR:
232
+ print 'WARNING: COULD NOT FORWARD VIA UPnP'
233
+ upnp_type = 0
234
+ continue
235
+ print "error: Couldn't listen - " + str(e)
236
+ h.failed()
237
+ return
238
+
239
+ response = get_response(config['responsefile'], config['url'], h.error)
240
+ if not response:
241
+ break
242
+
243
+ infohash = sha(bencode(response['info'])).digest()
244
+
245
+ dow = BT1Download(h.display, h.finished, h.error, disp_exception, doneFlag,
246
+ config, response, infohash, myid, rawserver, listen_port,
247
+ configdir)
248
+
249
+ if not dow.saveAs(h.chooseFile, h.newpath):
250
+ break
251
+
252
+ if not dow.initFiles(old_style = True):
253
+ break
254
+ if not dow.startEngine():
255
+ dow.shutdown()
256
+ break
257
+ dow.startRerequester()
258
+ dow.autoStats()
259
+
260
+ if not dow.am_I_finished():
261
+ h.display(activity = 'connecting to peers')
262
+ rawserver.listen_forever(dow.getPortHandler())
263
+ h.display(activity = 'shutting down')
264
+ dow.shutdown()
265
+ break
266
+ try:
267
+ rawserver.shutdown()
268
+ except:
269
+ pass
270
+ if not h.done:
271
+ h.failed()
272
+
273
+ if __name__ == '__main__':
274
+
275
+ if len(argv) != 5:
276
+ print "Incorrect number of arguments"
277
+ print
278
+ print """Usage:
279
+ python murder_client.py peer/seed out.torrent OUT.OUT 127.0.0.1
280
+
281
+ The last parameter is the local ip address, normally 10.x.x.x
282
+ """
283
+ sys.exit(1)
284
+
285
+ argv = ["--responsefile", sys.argv[2],
286
+ "--saveas", sys.argv[3],
287
+ "--ip", sys.argv[4]]
288
+
289
+ isPeer = sys.argv[1] == "peer"
290
+
291
+ run(argv[1:])
@@ -0,0 +1,46 @@
1
+ # Copyright 2010 Twitter, Inc.
2
+ # Copyright 2010 Larry Gadea <lg@twitter.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Usage: python murder_make_torrent.py <file> <trackerhost:port> <target>
17
+ # Usage: python murder_make_torrent.py deploy.tar.gz tracker.twitter.com:8998 deploy.torrent
18
+
19
+ import warnings
20
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
21
+
22
+ from sys import argv, version, exit
23
+ from os.path import split
24
+ assert version >= '2', "Install Python 2.0 or greater"
25
+ from BitTornado.BT1.makemetafile import make_meta_file
26
+
27
+ if __name__ == '__main__':
28
+
29
+ if len(argv) != 4:
30
+ print "Incorrect number of arguments"
31
+ print
32
+ print """Usage:
33
+ python murder_make_torrent.py <file> <trackerhost:port> <target>
34
+
35
+ For example:
36
+ python murder_make_torrent.py deploy.tar.gz tracker.twitter.com:8998 deploy.torrent
37
+ """
38
+ exit(1)
39
+
40
+ try:
41
+ params = {}
42
+ params["target"] = argv[3]
43
+ make_meta_file(argv[1], "http://" + argv[2] + "/announce", params)
44
+ except ValueError, e:
45
+ print str(e)
46
+ exit(1)
@@ -0,0 +1,28 @@
1
+ # Copyright 2010 Twitter, Inc.
2
+ # Copyright 2010 Larry Gadea <lg@twitter.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ # Usage: python murder_tracker.py <optionalparams>
17
+ # Usage: python murder_tracker.py
18
+
19
+ import warnings
20
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
21
+
22
+ from BitTornado.BT1.track import track
23
+ from sys import argv
24
+
25
+ if __name__ == '__main__':
26
+ args = ["--dfile", "data",
27
+ "--port", "8998"] + argv[1:]
28
+ track(args)