1 theshadow 1.1 #!/usr/bin/env python
2
3 # Written by Henry 'Pi' James
4 # see LICENSE.txt for license information
5
|
6 theshadow 1.7 SPEW_SCROLL_RATE = 3
7
|
8 theshadow 1.3 import PSYCO
9 if PSYCO.psyco:
10 try:
11 import psyco
12 assert psyco.__version__ >= 0x010100f0
13 psyco.full()
14 except:
15 pass
16
|
17 theshadow 1.7 from BitTorrent.download import Download
|
18 theshadow 1.1 from threading import Event
19 from os.path import abspath
20 from signal import signal, SIGWINCH
21 from sys import argv, version, stdout
|
22 theshadow 1.7 from time import time, strftime
|
23 theshadow 1.1 assert version >= '2', "Install Python 2.0 or greater"
24
25 def fmttime(n):
26 if n == -1:
27 return 'download not progressing (file not being uploaded by others?)'
28 if n == 0:
29 return 'download complete!'
30 n = int(n)
31 m, s = divmod(n, 60)
32 h, m = divmod(m, 60)
33 if h > 1000000:
34 return 'n/a'
35 return 'finishing in %d:%02d:%02d' % (h, m, s)
36
37 def fmtsize(n):
38 s = str(n)
39 size = s[-3:]
40 while len(s) > 3:
41 s = s[:-3]
42 size = '%s,%s' % (s[-3:], size)
43 if n > 999:
44 theshadow 1.1 unit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
45 i = 1
46 while i + 1 < len(unit) and (n >> 10) >= 999:
47 i += 1
48 n >>= 10
49 n = float(n) / (1 << 10)
50 size = '%s (%.0f %s)' % (size, n, unit[i])
51 return size
52
53 def winch_handler(signum, stackframe):
|
54 theshadow 1.7 global scrwin, scrpan, labelwin, labelpan
55 global fieldh, fieldw, fieldy, fieldx, fieldwin, fieldpan
56 global spewwin, spewpan, spewh, speww, spewy, spewx
|
57 theshadow 1.1 # SIGWINCH. Remake the frames!
58 ## Curses Trickery
|
59 theshadow 1.7 try:
60 curses.endwin()
61 except:
62 pass
|
63 theshadow 1.1 # delete scrwin somehow?
64 scrwin.refresh()
65 scrwin = curses.newwin(0, 0, 0, 0)
66 scrh, scrw = scrwin.getmaxyx()
67 scrpan = curses.panel.new_panel(scrwin)
|
68 theshadow 1.7 labelh, labelw, labely, labelx = 11, 9, 1, 2
|
69 theshadow 1.1 labelwin = curses.newwin(labelh, labelw, labely, labelx)
70 labelpan = curses.panel.new_panel(labelwin)
|
71 theshadow 1.7 fieldh, fieldw, fieldy, fieldx = labelh, scrw - 2 - labelw - 3, 1, labelw + 3
|
72 theshadow 1.1 fieldwin = curses.newwin(fieldh, fieldw, fieldy, fieldx)
73 fieldpan = curses.panel.new_panel(fieldwin)
|
74 theshadow 1.7 spewh, speww, spewy, spewx = scrh - labelh - 2, scrw - 3, 1 + labelh, 2
75 spewwin = curses.newwin(spewh, speww, spewy, spewx)
76 spewpan = curses.panel.new_panel(spewwin)
|
77 theshadow 1.1 prepare_display()
78
79
80 class CursesDisplayer:
81 def __init__(self, mainerrlist):
82 self.done = 0
83 self.file = ''
84 self.fileSize = ''
85 self.activity = ''
86 self.status = ''
87 self.progress = ''
88 self.downloadTo = ''
89 self.downRate = '---'
90 self.upRate = '---'
|
91 theshadow 1.2 self.shareRating = ''
92 self.seedStatus = ''
93 self.peerStatus = ''
|
94 theshadow 1.1 self.errors = []
95 self.globalerrlist = mainerrlist
|
96 theshadow 1.6 self.last_update_time = 0
|
97 theshadow 1.7 self.spew_scroll_time = 0
98 self.spew_scroll_pos = 0
|
99 theshadow 1.1
100 def finished(self):
101 self.done = 1
102 self.activity = 'download succeeded!'
103 self.downRate = '---'
104 self.display(fractionDone = 1)
105
106 def failed(self):
107 self.done = 1
108 self.activity = 'download failed!'
109 self.downRate = '---'
110 self.display()
111
112 def error(self, errormsg):
|
113 theshadow 1.7 newerrmsg = strftime('[%H:%M:%S] ') + errormsg
|
114 theshadow 1.4 self.errors.append(newerrmsg)
115 self.globalerrlist.append(newerrmsg)
|
116 theshadow 1.1 self.display()
117
118 def display(self, fractionDone = None, timeEst = None,
|
119 theshadow 1.2 downRate = None, upRate = None, activity = None,
|
120 theshadow 1.7 statistics = None, spew = None, **kws):
|
121 theshadow 1.6 if self.last_update_time + 0.1 > time() and fractionDone not in (0.0, 1.0) and activity is not None:
122 return
123 self.last_update_time = time()
|
124 theshadow 1.1 if activity is not None and not self.done:
125 self.activity = activity
126 elif timeEst is not None:
127 self.activity = fmttime(timeEst)
128 if fractionDone is not None:
129 blocknum = int(fieldw * fractionDone)
130 self.progress = blocknum * '#' + (fieldw - blocknum) * '_'
131 self.status = '%s (%.1f%%)' % (self.activity, fractionDone * 100)
132 else:
133 self.status = self.activity
134 if downRate is not None:
135 self.downRate = '%.1f KB/s' % (float(downRate) / (1 << 10))
136 if upRate is not None:
137 self.upRate = '%.1f KB/s' % (float(upRate) / (1 << 10))
|
138 theshadow 1.2 if statistics is not None:
139 if (statistics.shareRating < 0) or (statistics.shareRating > 100):
140 self.shareRating = 'oo (%.1f MB up / %.1f MB down)' % (float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
141 else:
142 self.shareRating = '%.3f (%.1f MB up / %.1f MB down)' % (statistics.shareRating, float(statistics.upTotal) / (1<<20), float(statistics.downTotal) / (1<<20))
143 if not self.done:
144 self.seedStatus = '%d seen now, plus %.3f distributed copies' % (statistics.numSeeds,0.001*int(1000*statistics.numCopies))
145 else:
146 self.seedStatus = '%d seen recently, plus %.3f distributed copies' % (statistics.numOldSeeds,0.001*int(1000*statistics.numCopies))
147 self.peerStatus = '%d seen now, %.1f%% done at %.1f kB/s' % (statistics.numPeers,statistics.percentDone,float(statistics.torrentRate) / (1 << 10))
|
148 theshadow 1.1
149 fieldwin.erase()
150 fieldwin.addnstr(0, 0, self.file, fieldw, curses.A_BOLD)
151 fieldwin.addnstr(1, 0, self.fileSize, fieldw)
152 fieldwin.addnstr(2, 0, self.downloadTo, fieldw)
153 if self.progress:
154 fieldwin.addnstr(3, 0, self.progress, fieldw, curses.A_BOLD)
155 fieldwin.addnstr(4, 0, self.status, fieldw)
156 fieldwin.addnstr(5, 0, self.downRate, fieldw)
157 fieldwin.addnstr(6, 0, self.upRate, fieldw)
|
158 theshadow 1.2 fieldwin.addnstr(7, 0, self.shareRating, fieldw)
159 fieldwin.addnstr(8, 0, self.seedStatus, fieldw)
160 fieldwin.addnstr(9, 0, self.peerStatus, fieldw)
|
161 theshadow 1.1
|
162 theshadow 1.7 spewwin.erase()
163
164 if spew is None:
165 errsize = spewh
166 spewwin.addnstr(0, 0, "error(s):", speww, curses.A_BOLD)
167 if self.errors:
168 errsize = len(self.errors)
169 displaysize = min(errsize, spewh)
170 displaytop = errsize - displaysize
171 for i in range(displaysize):
172 spewwin.addnstr(0, labelw, self.errors[displaytop + i],
173 speww-labelw-1, curses.A_BOLD)
|
174 theshadow 1.1 else:
|
175 theshadow 1.7 spewwin.addnstr(0, 0, "error:", speww, curses.A_BOLD)
176 if self.errors:
177 spewwin.addnstr(0, labelw, self.errors[-1],
178 speww-labelw-1, curses.A_BOLD)
179 spewwin.addnstr(2, 0, " # IP Upload Download Completed Speed", speww, curses.A_BOLD)
180
181
182 if self.spew_scroll_time + SPEW_SCROLL_RATE < time():
183 self.spew_scroll_time = time()
184 if len(spew) > spewh-5 or self.spew_scroll_pos > 0:
185 self.spew_scroll_pos += 1
186 if self.spew_scroll_pos > len(spew)-1:
187 self.spew_scroll_pos = 0
188
189 for i in range(len(spew)):
190 spew[i]['lineno'] = i+1
191 spew = spew[self.spew_scroll_pos:] + spew[:self.spew_scroll_pos]
192
193 for i in range(min(spewh - 5, len(spew))):
194 spewwin.addnstr(i+3, 0, '%3d' % spew[i]['lineno'], 3)
195 spewwin.addnstr(i+3, 4, spew[i]['ip'], 15)
196 theshadow 1.7 if spew[i]['uprate'] > 100:
197 spewwin.addnstr(i+3, 20, '%6.0f KB/s' % (float(spew[i]['uprate']) / 1000), 11)
198 spewwin.addnstr(i+3, 32, '-----', 5)
199 if spew[i]['uinterested'] == 1:
200 spewwin.addnstr(i+3, 33, 'I', 1)
201 if spew[i]['uchoked'] == 1:
202 spewwin.addnstr(i+3, 35, 'C', 1)
203 if spew[i]['downrate'] > 100:
204 spewwin.addnstr(i+3, 38, '%6.0f KB/s' % (float(spew[i]['downrate']) / 1000), 11)
205 spewwin.addnstr(i+3, 50, '-------', 7)
206 if spew[i]['dinterested'] == 1:
207 spewwin.addnstr(i+3, 51, 'I', 1)
208 if spew[i]['dchoked'] == 1:
209 spewwin.addnstr(i+3, 53, 'C', 1)
210 if spew[i]['snubbed'] == 1:
211 spewwin.addnstr(i+3, 55, 'S', 1)
212 spewwin.addnstr(i+3, 58, '%5.1f%%' % (float(int(spew[i]['completed']*1000))/10), 6)
213 if spew[i]['speed'] is not None:
214 spewwin.addnstr(i+3, 64, '%5.0f KB/s' % (float(spew[i]['speed'])/1000), 10)
215
216 if statistics is not None:
217 theshadow 1.7 spewwin.addnstr(spewh-1, 0,
218 'downloading %d pieces, have %d fragments, %d of %d pieces completed'
219 % ( statistics.storage_active, statistics.storage_dirty,
220 statistics.storage_numcomplete,
221 statistics.storage_totalpieces ), speww-1 )
|
222 theshadow 1.1
223 curses.panel.update_panels()
224 curses.doupdate()
225
226 def chooseFile(self, default, size, saveas, dir):
227 self.file = default
228 self.fileSize = fmtsize(size)
229 if saveas == '':
230 saveas = default
231 self.downloadTo = abspath(saveas)
232 return saveas
233
234 def run(mainerrlist, params):
235 d = CursesDisplayer(mainerrlist)
|
236 theshadow 1.7 dow = Download()
237 d.dow = dow
|
238 theshadow 1.1 try:
|
239 theshadow 1.7 dow.download(params, d.chooseFile, d.display, d.finished, d.error, Event(), fieldw)
|
240 theshadow 1.1 except KeyboardInterrupt:
241 # ^C to exit..
242 pass
243 if not d.done:
244 d.failed()
245
246 def prepare_display():
247 scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
248 labelwin.addstr(0, 0, 'file:')
249 labelwin.addstr(1, 0, 'size:')
250 labelwin.addstr(2, 0, 'dest:')
251 labelwin.addstr(3, 0, 'progress:')
252 labelwin.addstr(4, 0, 'status:')
253 labelwin.addstr(5, 0, 'dl speed:')
254 labelwin.addstr(6, 0, 'ul speed:')
|
255 theshadow 1.2 labelwin.addstr(7, 0, 'sharing:')
256 labelwin.addstr(8, 0, 'seeds:')
257 labelwin.addstr(9, 0, 'peers:')
|
258 theshadow 1.7 # labelwin.addstr(10, 0, '')
259 # labelwin.addstr(11, 0, 'error(s):')
|
260 theshadow 1.1 curses.panel.update_panels()
261 curses.doupdate()
262
263 try:
264 import curses
265 import curses.panel
266
267 scrwin = curses.initscr()
268 curses.noecho()
269 curses.cbreak()
270
271 except:
272 print 'Textmode GUI initialization failed, cannot proceed.'
273 print
274 print 'This download interface requires the standard Python module ' \
275 '"curses", which is unfortunately not available for the native ' \
276 'Windows port of Python. It is however available for the Cygwin ' \
277 'port of Python, running on all Win32 systems (www.cygwin.com).'
278 print
279 print 'You may still use "btdownloadheadless.py" to download.'
280
281 theshadow 1.1 scrh, scrw = scrwin.getmaxyx()
282 scrpan = curses.panel.new_panel(scrwin)
|
283 theshadow 1.7 labelh, labelw, labely, labelx = 13, 9, 1, 2
|
284 theshadow 1.1 labelwin = curses.newwin(labelh, labelw, labely, labelx)
285 labelpan = curses.panel.new_panel(labelwin)
|
286 theshadow 1.7 fieldh, fieldw, fieldy, fieldx = labelh, scrw - 2 - labelw - 3, 1, labelw + 3
|
287 theshadow 1.1 fieldwin = curses.newwin(fieldh, fieldw, fieldy, fieldx)
288 fieldpan = curses.panel.new_panel(fieldwin)
|
289 theshadow 1.7 spewh, speww, spewy, spewx = scrh - labelh - 2, scrw - 3, 1 + labelh, 2
290 spewwin = curses.newwin(spewh, speww, spewy, spewx)
291 spewpan = curses.panel.new_panel(spewwin)
|
292 theshadow 1.1 prepare_display()
293
294 signal(SIGWINCH, winch_handler)
295
296 if __name__ == '__main__':
297 mainerrlist = []
298 try:
299 run(mainerrlist, argv[1:])
300 finally:
|
301 theshadow 1.7 try:
302 curses.nocbreak()
303 curses.echo()
304 except:
305 pass
306 try:
307 curses.endwin()
308 except:
309 pass
|
310 theshadow 1.1 if len(mainerrlist) != 0:
311 print "These errors occurred during execution:"
312 for error in mainerrlist:
313 print error
|