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.
- 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)
|