murder 0.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/LICENSE +17 -0
- data/README +224 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/dist/BitTornado/BT1/Choker.py +128 -0
- data/dist/BitTornado/BT1/Connecter.py +288 -0
- data/dist/BitTornado/BT1/Downloader.py +594 -0
- data/dist/BitTornado/BT1/DownloaderFeedback.py +155 -0
- data/dist/BitTornado/BT1/Encrypter.py +333 -0
- data/dist/BitTornado/BT1/FileSelector.py +245 -0
- data/dist/BitTornado/BT1/Filter.py +12 -0
- data/dist/BitTornado/BT1/HTTPDownloader.py +251 -0
- data/dist/BitTornado/BT1/NatCheck.py +95 -0
- data/dist/BitTornado/BT1/PiecePicker.py +320 -0
- data/dist/BitTornado/BT1/Rerequester.py +426 -0
- data/dist/BitTornado/BT1/Statistics.py +177 -0
- data/dist/BitTornado/BT1/Storage.py +584 -0
- data/dist/BitTornado/BT1/StorageWrapper.py +1045 -0
- data/dist/BitTornado/BT1/StreamCheck.py +135 -0
- data/dist/BitTornado/BT1/T2T.py +193 -0
- data/dist/BitTornado/BT1/Uploader.py +145 -0
- data/dist/BitTornado/BT1/__init__.py +1 -0
- data/dist/BitTornado/BT1/btformats.py +100 -0
- data/dist/BitTornado/BT1/fakeopen.py +89 -0
- data/dist/BitTornado/BT1/makemetafile.py +263 -0
- data/dist/BitTornado/BT1/track.py +1067 -0
- data/dist/BitTornado/ConfigDir.py +401 -0
- data/dist/BitTornado/ConfigReader.py +1068 -0
- data/dist/BitTornado/ConnChoice.py +31 -0
- data/dist/BitTornado/CreateIcons.py +105 -0
- data/dist/BitTornado/CurrentRateMeasure.py +37 -0
- data/dist/BitTornado/HTTPHandler.py +167 -0
- data/dist/BitTornado/PSYCO.py +5 -0
- data/dist/BitTornado/RateLimiter.py +153 -0
- data/dist/BitTornado/RateMeasure.py +75 -0
- data/dist/BitTornado/RawServer.py +195 -0
- data/dist/BitTornado/ServerPortHandler.py +188 -0
- data/dist/BitTornado/SocketHandler.py +375 -0
- data/dist/BitTornado/__init__.py +63 -0
- data/dist/BitTornado/bencode.py +319 -0
- data/dist/BitTornado/bitfield.py +162 -0
- data/dist/BitTornado/clock.py +27 -0
- data/dist/BitTornado/download_bt1.py +882 -0
- data/dist/BitTornado/inifile.py +169 -0
- data/dist/BitTornado/iprangeparse.py +194 -0
- data/dist/BitTornado/launchmanycore.py +381 -0
- data/dist/BitTornado/natpunch.py +254 -0
- data/dist/BitTornado/parseargs.py +137 -0
- data/dist/BitTornado/parsedir.py +150 -0
- data/dist/BitTornado/piecebuffer.py +86 -0
- data/dist/BitTornado/selectpoll.py +109 -0
- data/dist/BitTornado/subnetparse.py +218 -0
- data/dist/BitTornado/torrentlistparse.py +38 -0
- data/dist/BitTornado/zurllib.py +100 -0
- data/dist/murder_client.py +291 -0
- data/dist/murder_make_torrent.py +46 -0
- data/dist/murder_tracker.py +28 -0
- data/doc/examples/Capfile +28 -0
- data/lib/capistrano/recipes/deploy/strategy/murder.rb +52 -0
- data/lib/murder.rb +43 -0
- data/lib/murder/admin.rb +47 -0
- data/lib/murder/murder.rb +121 -0
- data/murder.gemspec +101 -0
- metadata +129 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
# Written by John Hoffman
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
'''
|
5
|
+
reads/writes a Windows-style INI file
|
6
|
+
format:
|
7
|
+
|
8
|
+
aa = "bb"
|
9
|
+
cc = 11
|
10
|
+
|
11
|
+
[eee]
|
12
|
+
ff = "gg"
|
13
|
+
|
14
|
+
decodes to:
|
15
|
+
d = { '': {'aa':'bb','cc':'11'}, 'eee': {'ff':'gg'} }
|
16
|
+
|
17
|
+
the encoder can also take this as input:
|
18
|
+
|
19
|
+
d = { 'aa': 'bb, 'cc': 11, 'eee': {'ff':'gg'} }
|
20
|
+
|
21
|
+
though it will only decode in the above format. Keywords must be strings.
|
22
|
+
Values that are strings are written surrounded by quotes, and the decoding
|
23
|
+
routine automatically strips any.
|
24
|
+
Booleans are written as integers. Anything else aside from string/int/float
|
25
|
+
may have unpredictable results.
|
26
|
+
'''
|
27
|
+
|
28
|
+
from cStringIO import StringIO
|
29
|
+
from traceback import print_exc
|
30
|
+
from types import DictType, StringType
|
31
|
+
try:
|
32
|
+
from types import BooleanType
|
33
|
+
except ImportError:
|
34
|
+
BooleanType = None
|
35
|
+
|
36
|
+
try:
|
37
|
+
True
|
38
|
+
except:
|
39
|
+
True = 1
|
40
|
+
False = 0
|
41
|
+
|
42
|
+
DEBUG = False
|
43
|
+
|
44
|
+
def ini_write(f, d, comment=''):
|
45
|
+
try:
|
46
|
+
a = {'':{}}
|
47
|
+
for k,v in d.items():
|
48
|
+
assert type(k) == StringType
|
49
|
+
k = k.lower()
|
50
|
+
if type(v) == DictType:
|
51
|
+
if DEBUG:
|
52
|
+
print 'new section:' +k
|
53
|
+
if k:
|
54
|
+
assert not a.has_key(k)
|
55
|
+
a[k] = {}
|
56
|
+
aa = a[k]
|
57
|
+
for kk,vv in v:
|
58
|
+
assert type(kk) == StringType
|
59
|
+
kk = kk.lower()
|
60
|
+
assert not aa.has_key(kk)
|
61
|
+
if type(vv) == BooleanType:
|
62
|
+
vv = int(vv)
|
63
|
+
if type(vv) == StringType:
|
64
|
+
vv = '"'+vv+'"'
|
65
|
+
aa[kk] = str(vv)
|
66
|
+
if DEBUG:
|
67
|
+
print 'a['+k+']['+kk+'] = '+str(vv)
|
68
|
+
else:
|
69
|
+
aa = a['']
|
70
|
+
assert not aa.has_key(k)
|
71
|
+
if type(v) == BooleanType:
|
72
|
+
v = int(v)
|
73
|
+
if type(v) == StringType:
|
74
|
+
v = '"'+v+'"'
|
75
|
+
aa[k] = str(v)
|
76
|
+
if DEBUG:
|
77
|
+
print 'a[\'\']['+k+'] = '+str(v)
|
78
|
+
r = open(f,'w')
|
79
|
+
if comment:
|
80
|
+
for c in comment.split('\n'):
|
81
|
+
r.write('# '+c+'\n')
|
82
|
+
r.write('\n')
|
83
|
+
l = a.keys()
|
84
|
+
l.sort()
|
85
|
+
for k in l:
|
86
|
+
if k:
|
87
|
+
r.write('\n['+k+']\n')
|
88
|
+
aa = a[k]
|
89
|
+
ll = aa.keys()
|
90
|
+
ll.sort()
|
91
|
+
for kk in ll:
|
92
|
+
r.write(kk+' = '+aa[kk]+'\n')
|
93
|
+
success = True
|
94
|
+
except:
|
95
|
+
if DEBUG:
|
96
|
+
print_exc()
|
97
|
+
success = False
|
98
|
+
try:
|
99
|
+
r.close()
|
100
|
+
except:
|
101
|
+
pass
|
102
|
+
return success
|
103
|
+
|
104
|
+
|
105
|
+
if DEBUG:
|
106
|
+
def errfunc(lineno, line, err):
|
107
|
+
print '('+str(lineno)+') '+err+': '+line
|
108
|
+
else:
|
109
|
+
errfunc = lambda lineno, line, err: None
|
110
|
+
|
111
|
+
def ini_read(f, errfunc = errfunc):
|
112
|
+
try:
|
113
|
+
r = open(f,'r')
|
114
|
+
ll = r.readlines()
|
115
|
+
d = {}
|
116
|
+
dd = {'':d}
|
117
|
+
for i in xrange(len(ll)):
|
118
|
+
l = ll[i]
|
119
|
+
l = l.strip()
|
120
|
+
if not l:
|
121
|
+
continue
|
122
|
+
if l[0] == '#':
|
123
|
+
continue
|
124
|
+
if l[0] == '[':
|
125
|
+
if l[-1] != ']':
|
126
|
+
errfunc(i,l,'syntax error')
|
127
|
+
continue
|
128
|
+
l1 = l[1:-1].strip().lower()
|
129
|
+
if not l1:
|
130
|
+
errfunc(i,l,'syntax error')
|
131
|
+
continue
|
132
|
+
if dd.has_key(l1):
|
133
|
+
errfunc(i,l,'duplicate section')
|
134
|
+
d = dd[l1]
|
135
|
+
continue
|
136
|
+
d = {}
|
137
|
+
dd[l1] = d
|
138
|
+
continue
|
139
|
+
try:
|
140
|
+
k,v = l.split('=',1)
|
141
|
+
except:
|
142
|
+
try:
|
143
|
+
k,v = l.split(':',1)
|
144
|
+
except:
|
145
|
+
errfunc(i,l,'syntax error')
|
146
|
+
continue
|
147
|
+
k = k.strip().lower()
|
148
|
+
v = v.strip()
|
149
|
+
if len(v) > 1 and ( (v[0] == '"' and v[-1] == '"') or
|
150
|
+
(v[0] == "'" and v[-1] == "'") ):
|
151
|
+
v = v[1:-1]
|
152
|
+
if not k:
|
153
|
+
errfunc(i,l,'syntax error')
|
154
|
+
continue
|
155
|
+
if d.has_key(k):
|
156
|
+
errfunc(i,l,'duplicate entry')
|
157
|
+
continue
|
158
|
+
d[k] = v
|
159
|
+
if DEBUG:
|
160
|
+
print dd
|
161
|
+
except:
|
162
|
+
if DEBUG:
|
163
|
+
print_exc()
|
164
|
+
dd = None
|
165
|
+
try:
|
166
|
+
r.close()
|
167
|
+
except:
|
168
|
+
pass
|
169
|
+
return dd
|
@@ -0,0 +1,194 @@
|
|
1
|
+
# Written by John Hoffman
|
2
|
+
# see LICENSE.txt for license information
|
3
|
+
|
4
|
+
from bisect import bisect, insort
|
5
|
+
|
6
|
+
try:
|
7
|
+
True
|
8
|
+
except:
|
9
|
+
True = 1
|
10
|
+
False = 0
|
11
|
+
bool = lambda x: not not x
|
12
|
+
|
13
|
+
|
14
|
+
def to_long_ipv4(ip):
|
15
|
+
ip = ip.split('.')
|
16
|
+
if len(ip) != 4:
|
17
|
+
raise ValueError, "bad address"
|
18
|
+
b = 0L
|
19
|
+
for n in ip:
|
20
|
+
b *= 256
|
21
|
+
b += int(n)
|
22
|
+
return b
|
23
|
+
|
24
|
+
|
25
|
+
def to_long_ipv6(ip):
|
26
|
+
if ip == '':
|
27
|
+
raise ValueError, "bad address"
|
28
|
+
if ip == '::': # boundary handling
|
29
|
+
ip = ''
|
30
|
+
elif ip[:2] == '::':
|
31
|
+
ip = ip[1:]
|
32
|
+
elif ip[0] == ':':
|
33
|
+
raise ValueError, "bad address"
|
34
|
+
elif ip[-2:] == '::':
|
35
|
+
ip = ip[:-1]
|
36
|
+
elif ip[-1] == ':':
|
37
|
+
raise ValueError, "bad address"
|
38
|
+
|
39
|
+
b = []
|
40
|
+
doublecolon = False
|
41
|
+
for n in ip.split(':'):
|
42
|
+
if n == '': # double-colon
|
43
|
+
if doublecolon:
|
44
|
+
raise ValueError, "bad address"
|
45
|
+
doublecolon = True
|
46
|
+
b.append(None)
|
47
|
+
continue
|
48
|
+
if n.find('.') >= 0: # IPv4
|
49
|
+
n = n.split('.')
|
50
|
+
if len(n) != 4:
|
51
|
+
raise ValueError, "bad address"
|
52
|
+
for i in n:
|
53
|
+
b.append(int(i))
|
54
|
+
continue
|
55
|
+
n = ('0'*(4-len(n))) + n
|
56
|
+
b.append(int(n[:2],16))
|
57
|
+
b.append(int(n[2:],16))
|
58
|
+
bb = 0L
|
59
|
+
for n in b:
|
60
|
+
if n is None:
|
61
|
+
for i in xrange(17-len(b)):
|
62
|
+
bb *= 256
|
63
|
+
continue
|
64
|
+
bb *= 256
|
65
|
+
bb += n
|
66
|
+
return bb
|
67
|
+
|
68
|
+
ipv4addrmask = 65535L*256*256*256*256
|
69
|
+
|
70
|
+
class IP_List:
|
71
|
+
def __init__(self):
|
72
|
+
self.ipv4list = [] # starts of ranges
|
73
|
+
self.ipv4dict = {} # start: end of ranges
|
74
|
+
self.ipv6list = [] # "
|
75
|
+
self.ipv6dict = {} # "
|
76
|
+
|
77
|
+
def __nonzero__(self):
|
78
|
+
return bool(self.ipv4list or self.ipv6list)
|
79
|
+
|
80
|
+
|
81
|
+
def append(self, ip_beg, ip_end = None):
|
82
|
+
if ip_end is None:
|
83
|
+
ip_end = ip_beg
|
84
|
+
else:
|
85
|
+
assert ip_beg <= ip_end
|
86
|
+
if ip_beg.find(':') < 0: # IPv4
|
87
|
+
ip_beg = to_long_ipv4(ip_beg)
|
88
|
+
ip_end = to_long_ipv4(ip_end)
|
89
|
+
l = self.ipv4list
|
90
|
+
d = self.ipv4dict
|
91
|
+
else:
|
92
|
+
ip_beg = to_long_ipv6(ip_beg)
|
93
|
+
ip_end = to_long_ipv6(ip_end)
|
94
|
+
bb = ip_beg % (256*256*256*256)
|
95
|
+
if bb == ipv4addrmask:
|
96
|
+
ip_beg -= bb
|
97
|
+
ip_end -= bb
|
98
|
+
l = self.ipv4list
|
99
|
+
d = self.ipv4dict
|
100
|
+
else:
|
101
|
+
l = self.ipv6list
|
102
|
+
d = self.ipv6dict
|
103
|
+
|
104
|
+
pos = bisect(l,ip_beg)-1
|
105
|
+
done = pos < 0
|
106
|
+
while not done:
|
107
|
+
p = pos
|
108
|
+
while p < len(l):
|
109
|
+
range_beg = l[p]
|
110
|
+
if range_beg > ip_end+1:
|
111
|
+
done = True
|
112
|
+
break
|
113
|
+
range_end = d[range_beg]
|
114
|
+
if range_end < ip_beg-1:
|
115
|
+
p += 1
|
116
|
+
if p == len(l):
|
117
|
+
done = True
|
118
|
+
break
|
119
|
+
continue
|
120
|
+
# if neither of the above conditions is true, the ranges overlap
|
121
|
+
ip_beg = min(ip_beg, range_beg)
|
122
|
+
ip_end = max(ip_end, range_end)
|
123
|
+
del l[p]
|
124
|
+
del d[range_beg]
|
125
|
+
break
|
126
|
+
|
127
|
+
insort(l,ip_beg)
|
128
|
+
d[ip_beg] = ip_end
|
129
|
+
|
130
|
+
|
131
|
+
def includes(self, ip):
|
132
|
+
if not (self.ipv4list or self.ipv6list):
|
133
|
+
return False
|
134
|
+
if ip.find(':') < 0: # IPv4
|
135
|
+
ip = to_long_ipv4(ip)
|
136
|
+
l = self.ipv4list
|
137
|
+
d = self.ipv4dict
|
138
|
+
else:
|
139
|
+
ip = to_long_ipv6(ip)
|
140
|
+
bb = ip % (256*256*256*256)
|
141
|
+
if bb == ipv4addrmask:
|
142
|
+
ip -= bb
|
143
|
+
l = self.ipv4list
|
144
|
+
d = self.ipv4dict
|
145
|
+
else:
|
146
|
+
l = self.ipv6list
|
147
|
+
d = self.ipv6dict
|
148
|
+
for ip_beg in l[bisect(l,ip)-1:]:
|
149
|
+
if ip == ip_beg:
|
150
|
+
return True
|
151
|
+
ip_end = d[ip_beg]
|
152
|
+
if ip > ip_beg and ip <= ip_end:
|
153
|
+
return True
|
154
|
+
return False
|
155
|
+
|
156
|
+
|
157
|
+
# reads a list from a file in the format 'whatever:whatever:ip-ip'
|
158
|
+
# (not IPv6 compatible at all)
|
159
|
+
def read_rangelist(self, file):
|
160
|
+
f = open(file, 'r')
|
161
|
+
while True:
|
162
|
+
line = f.readline()
|
163
|
+
if not line:
|
164
|
+
break
|
165
|
+
line = line.strip()
|
166
|
+
if not line or line[0] == '#':
|
167
|
+
continue
|
168
|
+
line = line.split(':')[-1]
|
169
|
+
try:
|
170
|
+
ip1,ip2 = line.split('-')
|
171
|
+
except:
|
172
|
+
ip1 = line
|
173
|
+
ip2 = line
|
174
|
+
try:
|
175
|
+
self.append(ip1.strip(),ip2.strip())
|
176
|
+
except:
|
177
|
+
print '*** WARNING *** could not parse IP range: '+line
|
178
|
+
f.close()
|
179
|
+
|
180
|
+
def is_ipv4(ip):
|
181
|
+
return ip.find(':') < 0
|
182
|
+
|
183
|
+
def is_valid_ip(ip):
|
184
|
+
try:
|
185
|
+
if is_ipv4(ip):
|
186
|
+
a = ip.split('.')
|
187
|
+
assert len(a) == 4
|
188
|
+
for i in a:
|
189
|
+
chr(int(i))
|
190
|
+
return True
|
191
|
+
to_long_ipv6(ip)
|
192
|
+
return True
|
193
|
+
except:
|
194
|
+
return False
|
@@ -0,0 +1,381 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
# Written by John Hoffman
|
4
|
+
# see LICENSE.txt for license information
|
5
|
+
|
6
|
+
from BitTornado import PSYCO
|
7
|
+
if PSYCO.psyco:
|
8
|
+
try:
|
9
|
+
import psyco
|
10
|
+
assert psyco.__version__ >= 0x010100f0
|
11
|
+
psyco.full()
|
12
|
+
except:
|
13
|
+
pass
|
14
|
+
|
15
|
+
from download_bt1 import BT1Download
|
16
|
+
from RawServer import RawServer, UPnP_ERROR
|
17
|
+
from RateLimiter import RateLimiter
|
18
|
+
from ServerPortHandler import MultiHandler
|
19
|
+
from parsedir import parsedir
|
20
|
+
from natpunch import UPnP_test
|
21
|
+
from random import seed
|
22
|
+
from socket import error as socketerror
|
23
|
+
from threading import Event
|
24
|
+
from sys import argv, exit
|
25
|
+
import sys, os
|
26
|
+
from clock import clock
|
27
|
+
from __init__ import createPeerID, mapbase64, version
|
28
|
+
from cStringIO import StringIO
|
29
|
+
from traceback import print_exc
|
30
|
+
|
31
|
+
try:
|
32
|
+
True
|
33
|
+
except:
|
34
|
+
True = 1
|
35
|
+
False = 0
|
36
|
+
|
37
|
+
|
38
|
+
def fmttime(n):
|
39
|
+
try:
|
40
|
+
n = int(n) # n may be None or too large
|
41
|
+
assert n < 5184000 # 60 days
|
42
|
+
except:
|
43
|
+
return 'downloading'
|
44
|
+
m, s = divmod(n, 60)
|
45
|
+
h, m = divmod(m, 60)
|
46
|
+
return '%d:%02d:%02d' % (h, m, s)
|
47
|
+
|
48
|
+
class SingleDownload:
|
49
|
+
def __init__(self, controller, hash, response, config, myid):
|
50
|
+
self.controller = controller
|
51
|
+
self.hash = hash
|
52
|
+
self.response = response
|
53
|
+
self.config = config
|
54
|
+
|
55
|
+
self.doneflag = Event()
|
56
|
+
self.waiting = True
|
57
|
+
self.checking = False
|
58
|
+
self.working = False
|
59
|
+
self.seed = False
|
60
|
+
self.closed = False
|
61
|
+
|
62
|
+
self.status_msg = ''
|
63
|
+
self.status_err = ['']
|
64
|
+
self.status_errtime = 0
|
65
|
+
self.status_done = 0.0
|
66
|
+
|
67
|
+
self.rawserver = controller.handler.newRawServer(hash, self.doneflag)
|
68
|
+
|
69
|
+
d = BT1Download(self.display, self.finished, self.error,
|
70
|
+
controller.exchandler, self.doneflag, config, response,
|
71
|
+
hash, myid, self.rawserver, controller.listen_port)
|
72
|
+
self.d = d
|
73
|
+
|
74
|
+
def start(self):
|
75
|
+
if not self.d.saveAs(self.saveAs):
|
76
|
+
self._shutdown()
|
77
|
+
return
|
78
|
+
self._hashcheckfunc = self.d.initFiles()
|
79
|
+
if not self._hashcheckfunc:
|
80
|
+
self._shutdown()
|
81
|
+
return
|
82
|
+
self.controller.hashchecksched(self.hash)
|
83
|
+
|
84
|
+
|
85
|
+
def saveAs(self, name, length, saveas, isdir):
|
86
|
+
return self.controller.saveAs(self.hash, name, saveas, isdir)
|
87
|
+
|
88
|
+
def hashcheck_start(self, donefunc):
|
89
|
+
if self.is_dead():
|
90
|
+
self._shutdown()
|
91
|
+
return
|
92
|
+
self.waiting = False
|
93
|
+
self.checking = True
|
94
|
+
self._hashcheckfunc(donefunc)
|
95
|
+
|
96
|
+
def hashcheck_callback(self):
|
97
|
+
self.checking = False
|
98
|
+
if self.is_dead():
|
99
|
+
self._shutdown()
|
100
|
+
return
|
101
|
+
if not self.d.startEngine(ratelimiter = self.controller.ratelimiter):
|
102
|
+
self._shutdown()
|
103
|
+
return
|
104
|
+
self.d.startRerequester()
|
105
|
+
self.statsfunc = self.d.startStats()
|
106
|
+
self.rawserver.start_listening(self.d.getPortHandler())
|
107
|
+
self.working = True
|
108
|
+
|
109
|
+
def is_dead(self):
|
110
|
+
return self.doneflag.isSet()
|
111
|
+
|
112
|
+
def _shutdown(self):
|
113
|
+
self.shutdown(False)
|
114
|
+
|
115
|
+
def shutdown(self, quiet=True):
|
116
|
+
if self.closed:
|
117
|
+
return
|
118
|
+
self.doneflag.set()
|
119
|
+
self.rawserver.shutdown()
|
120
|
+
if self.checking or self.working:
|
121
|
+
self.d.shutdown()
|
122
|
+
self.waiting = False
|
123
|
+
self.checking = False
|
124
|
+
self.working = False
|
125
|
+
self.closed = True
|
126
|
+
self.controller.was_stopped(self.hash)
|
127
|
+
if not quiet:
|
128
|
+
self.controller.died(self.hash)
|
129
|
+
|
130
|
+
|
131
|
+
def display(self, activity = None, fractionDone = None):
|
132
|
+
# really only used by StorageWrapper now
|
133
|
+
if activity:
|
134
|
+
self.status_msg = activity
|
135
|
+
if fractionDone is not None:
|
136
|
+
self.status_done = float(fractionDone)
|
137
|
+
|
138
|
+
def finished(self):
|
139
|
+
self.seed = True
|
140
|
+
|
141
|
+
def error(self, msg):
|
142
|
+
if self.doneflag.isSet():
|
143
|
+
self._shutdown()
|
144
|
+
self.status_err.append(msg)
|
145
|
+
self.status_errtime = clock()
|
146
|
+
|
147
|
+
|
148
|
+
class LaunchMany:
|
149
|
+
def __init__(self, config, Output):
|
150
|
+
try:
|
151
|
+
self.config = config
|
152
|
+
self.Output = Output
|
153
|
+
|
154
|
+
self.torrent_dir = config['torrent_dir']
|
155
|
+
self.torrent_cache = {}
|
156
|
+
self.file_cache = {}
|
157
|
+
self.blocked_files = {}
|
158
|
+
self.scan_period = config['parse_dir_interval']
|
159
|
+
self.stats_period = config['display_interval']
|
160
|
+
|
161
|
+
self.torrent_list = []
|
162
|
+
self.downloads = {}
|
163
|
+
self.counter = 0
|
164
|
+
self.doneflag = Event()
|
165
|
+
|
166
|
+
self.hashcheck_queue = []
|
167
|
+
self.hashcheck_current = None
|
168
|
+
|
169
|
+
self.rawserver = RawServer(self.doneflag, config['timeout_check_interval'],
|
170
|
+
config['timeout'], ipv6_enable = config['ipv6_enabled'],
|
171
|
+
failfunc = self.failed, errorfunc = self.exchandler)
|
172
|
+
upnp_type = UPnP_test(config['upnp_nat_access'])
|
173
|
+
while True:
|
174
|
+
try:
|
175
|
+
self.listen_port = self.rawserver.find_and_bind(
|
176
|
+
config['minport'], config['maxport'], config['bind'],
|
177
|
+
ipv6_socket_style = config['ipv6_binds_v4'],
|
178
|
+
upnp = upnp_type, randomizer = config['random_port'])
|
179
|
+
break
|
180
|
+
except socketerror, e:
|
181
|
+
if upnp_type and e == UPnP_ERROR:
|
182
|
+
self.Output.message('WARNING: COULD NOT FORWARD VIA UPnP')
|
183
|
+
upnp_type = 0
|
184
|
+
continue
|
185
|
+
self.failed("Couldn't listen - " + str(e))
|
186
|
+
return
|
187
|
+
|
188
|
+
self.ratelimiter = RateLimiter(self.rawserver.add_task,
|
189
|
+
config['upload_unit_size'])
|
190
|
+
self.ratelimiter.set_upload_rate(config['max_upload_rate'])
|
191
|
+
|
192
|
+
self.handler = MultiHandler(self.rawserver, self.doneflag)
|
193
|
+
seed(createPeerID())
|
194
|
+
self.rawserver.add_task(self.scan, 0)
|
195
|
+
self.rawserver.add_task(self.stats, 0)
|
196
|
+
|
197
|
+
self.handler.listen_forever()
|
198
|
+
|
199
|
+
self.Output.message('shutting down')
|
200
|
+
self.hashcheck_queue = []
|
201
|
+
for hash in self.torrent_list:
|
202
|
+
self.Output.message('dropped "'+self.torrent_cache[hash]['path']+'"')
|
203
|
+
self.downloads[hash].shutdown()
|
204
|
+
self.rawserver.shutdown()
|
205
|
+
|
206
|
+
except:
|
207
|
+
data = StringIO()
|
208
|
+
print_exc(file = data)
|
209
|
+
Output.exception(data.getvalue())
|
210
|
+
|
211
|
+
|
212
|
+
def scan(self):
|
213
|
+
self.rawserver.add_task(self.scan, self.scan_period)
|
214
|
+
|
215
|
+
r = parsedir(self.torrent_dir, self.torrent_cache,
|
216
|
+
self.file_cache, self.blocked_files,
|
217
|
+
return_metainfo = True, errfunc = self.Output.message)
|
218
|
+
|
219
|
+
( self.torrent_cache, self.file_cache, self.blocked_files,
|
220
|
+
added, removed ) = r
|
221
|
+
|
222
|
+
for hash, data in removed.items():
|
223
|
+
self.Output.message('dropped "'+data['path']+'"')
|
224
|
+
self.remove(hash)
|
225
|
+
for hash, data in added.items():
|
226
|
+
self.Output.message('added "'+data['path']+'"')
|
227
|
+
self.add(hash, data)
|
228
|
+
|
229
|
+
def stats(self):
|
230
|
+
self.rawserver.add_task(self.stats, self.stats_period)
|
231
|
+
data = []
|
232
|
+
for hash in self.torrent_list:
|
233
|
+
cache = self.torrent_cache[hash]
|
234
|
+
if self.config['display_path']:
|
235
|
+
name = cache['path']
|
236
|
+
else:
|
237
|
+
name = cache['name']
|
238
|
+
size = cache['length']
|
239
|
+
d = self.downloads[hash]
|
240
|
+
progress = '0.0%'
|
241
|
+
peers = 0
|
242
|
+
seeds = 0
|
243
|
+
seedsmsg = "S"
|
244
|
+
dist = 0.0
|
245
|
+
uprate = 0.0
|
246
|
+
dnrate = 0.0
|
247
|
+
upamt = 0
|
248
|
+
dnamt = 0
|
249
|
+
t = 0
|
250
|
+
if d.is_dead():
|
251
|
+
status = 'stopped'
|
252
|
+
elif d.waiting:
|
253
|
+
status = 'waiting for hash check'
|
254
|
+
elif d.checking:
|
255
|
+
status = d.status_msg
|
256
|
+
progress = '%.1f%%' % (d.status_done*100)
|
257
|
+
else:
|
258
|
+
stats = d.statsfunc()
|
259
|
+
s = stats['stats']
|
260
|
+
if d.seed:
|
261
|
+
status = 'seeding'
|
262
|
+
progress = '100.0%'
|
263
|
+
seeds = s.numOldSeeds
|
264
|
+
seedsmsg = "s"
|
265
|
+
dist = s.numCopies
|
266
|
+
else:
|
267
|
+
if s.numSeeds + s.numPeers:
|
268
|
+
t = stats['time']
|
269
|
+
if t == 0: # unlikely
|
270
|
+
t = 0.01
|
271
|
+
status = fmttime(t)
|
272
|
+
else:
|
273
|
+
t = -1
|
274
|
+
status = 'connecting to peers'
|
275
|
+
progress = '%.1f%%' % (int(stats['frac']*1000)/10.0)
|
276
|
+
seeds = s.numSeeds
|
277
|
+
dist = s.numCopies2
|
278
|
+
dnrate = stats['down']
|
279
|
+
peers = s.numPeers
|
280
|
+
uprate = stats['up']
|
281
|
+
upamt = s.upTotal
|
282
|
+
dnamt = s.downTotal
|
283
|
+
|
284
|
+
if d.is_dead() or d.status_errtime+300 > clock():
|
285
|
+
msg = d.status_err[-1]
|
286
|
+
else:
|
287
|
+
msg = ''
|
288
|
+
|
289
|
+
data.append(( name, status, progress, peers, seeds, seedsmsg, dist,
|
290
|
+
uprate, dnrate, upamt, dnamt, size, t, msg ))
|
291
|
+
stop = self.Output.display(data)
|
292
|
+
if stop:
|
293
|
+
self.doneflag.set()
|
294
|
+
|
295
|
+
def remove(self, hash):
|
296
|
+
self.torrent_list.remove(hash)
|
297
|
+
self.downloads[hash].shutdown()
|
298
|
+
del self.downloads[hash]
|
299
|
+
|
300
|
+
def add(self, hash, data):
|
301
|
+
c = self.counter
|
302
|
+
self.counter += 1
|
303
|
+
x = ''
|
304
|
+
for i in xrange(3):
|
305
|
+
x = mapbase64[c & 0x3F]+x
|
306
|
+
c >>= 6
|
307
|
+
peer_id = createPeerID(x)
|
308
|
+
d = SingleDownload(self, hash, data['metainfo'], self.config, peer_id)
|
309
|
+
self.torrent_list.append(hash)
|
310
|
+
self.downloads[hash] = d
|
311
|
+
d.start()
|
312
|
+
|
313
|
+
|
314
|
+
def saveAs(self, hash, name, saveas, isdir):
|
315
|
+
x = self.torrent_cache[hash]
|
316
|
+
style = self.config['saveas_style']
|
317
|
+
if style == 1 or style == 3:
|
318
|
+
if saveas:
|
319
|
+
saveas = os.path.join(saveas,x['file'][:-1-len(x['type'])])
|
320
|
+
else:
|
321
|
+
saveas = x['path'][:-1-len(x['type'])]
|
322
|
+
if style == 3:
|
323
|
+
if not os.path.isdir(saveas):
|
324
|
+
try:
|
325
|
+
os.mkdir(saveas)
|
326
|
+
except:
|
327
|
+
raise OSError("couldn't create directory for "+x['path']
|
328
|
+
+" ("+saveas+")")
|
329
|
+
if not isdir:
|
330
|
+
saveas = os.path.join(saveas, name)
|
331
|
+
else:
|
332
|
+
if saveas:
|
333
|
+
saveas = os.path.join(saveas, name)
|
334
|
+
else:
|
335
|
+
saveas = os.path.join(os.path.split(x['path'])[0], name)
|
336
|
+
|
337
|
+
if isdir and not os.path.isdir(saveas):
|
338
|
+
try:
|
339
|
+
os.mkdir(saveas)
|
340
|
+
except:
|
341
|
+
raise OSError("couldn't create directory for "+x['path']
|
342
|
+
+" ("+saveas+")")
|
343
|
+
return saveas
|
344
|
+
|
345
|
+
|
346
|
+
def hashchecksched(self, hash = None):
|
347
|
+
if hash:
|
348
|
+
self.hashcheck_queue.append(hash)
|
349
|
+
if not self.hashcheck_current:
|
350
|
+
self._hashcheck_start()
|
351
|
+
|
352
|
+
def _hashcheck_start(self):
|
353
|
+
self.hashcheck_current = self.hashcheck_queue.pop(0)
|
354
|
+
self.downloads[self.hashcheck_current].hashcheck_start(self.hashcheck_callback)
|
355
|
+
|
356
|
+
def hashcheck_callback(self):
|
357
|
+
self.downloads[self.hashcheck_current].hashcheck_callback()
|
358
|
+
if self.hashcheck_queue:
|
359
|
+
self._hashcheck_start()
|
360
|
+
else:
|
361
|
+
self.hashcheck_current = None
|
362
|
+
|
363
|
+
def died(self, hash):
|
364
|
+
if self.torrent_cache.has_key(hash):
|
365
|
+
self.Output.message('DIED: "'+self.torrent_cache[hash]['path']+'"')
|
366
|
+
|
367
|
+
def was_stopped(self, hash):
|
368
|
+
try:
|
369
|
+
self.hashcheck_queue.remove(hash)
|
370
|
+
except:
|
371
|
+
pass
|
372
|
+
if self.hashcheck_current == hash:
|
373
|
+
self.hashcheck_current = None
|
374
|
+
if self.hashcheck_queue:
|
375
|
+
self._hashcheck_start()
|
376
|
+
|
377
|
+
def failed(self, s):
|
378
|
+
self.Output.message('FAILURE: '+s)
|
379
|
+
|
380
|
+
def exchandler(self, s):
|
381
|
+
self.Output.exception(s)
|