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