0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018from __future__ import division
0019
0020DOWNLOAD_SCROLL_RATE = 1
0021
0022import sys, os
0023from threading import Event
0024from time import time, localtime, strftime, sleep
0025from xmlrpclib import ServerProxy
0026
0027try:
0028 import curses
0029 import curses.panel
0030 from curses.wrapper import wrapper as curses_wrapper
0031 from signal import signal, SIGWINCH
0032except:
0033 print 'Textmode GUI initialization failed, cannot proceed.'
0034 print
0035 print 'This download interface requires the standard Python module ' '"curses", which is unfortunately not available for the native ' 'Windows port of Python. It is however available for the Cygwin ' 'port of Python, running on all Win32 systems (www.cygwin.com).'
0039 print
0040 print 'You may still use "btdownloadheadless.py" to download.'
0041 sys.exit(1)
0042
0043exceptions = []
0044
0045def fmttime(n):
0046 if n <= 0:
0047 return None
0048 n = int(n)
0049 m, s = divmod(n, 60)
0050 h, m = divmod(m, 60)
0051 if h > 1000000:
0052 return 'connecting to peers'
0053 return 'ETA in %d:%02d:%02d' % (h, m, s)
0054
0055def fmtsize(n):
0056 n = long(n)
0057 unit = [' B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
0058 i = 0
0059 if (n > 999):
0060 i = 1
0061 while i + 1 < len(unit) and (n >> 10) >= 999:
0062 i += 1
0063 n >>= 10
0064 n /= 1024
0065 if i > 0:
0066 size = '%.1f' % n + '%s' % unit[i]
0067 else:
0068 size = '%.0f' % n + '%s' % unit[i]
0069 return size
0070
0071def ljust(s, size):
0072 s = s[:size]
0073 return s + (' '*(size-len(s)))
0074
0075def rjust(s, size):
0076 s = s[:size]
0077 return (' '*(size-len(s)))+s
0078
0079
0080class CursesDisplayer(object):
0081
0082 def __init__(self, scrwin):
0083 self.messages = []
0084 self.scroll_pos = 0
0085 self.scroll_time = 0
0086
0087 self.scrwin = scrwin
0088 signal(SIGWINCH, self.winch_handler)
0089 self.changeflag = Event()
0090 self._remake_window()
0091
0092 def winch_handler(self, signum, stackframe):
0093 self.changeflag.set()
0094 curses.endwin()
0095 self.scrwin.refresh()
0096 self.scrwin = curses.newwin(0, 0, 0, 0)
0097 self._remake_window()
0098 self._display_messages()
0099
0100 def _remake_window(self):
0101 self.scrh, self.scrw = self.scrwin.getmaxyx()
0102 self.scrpan = curses.panel.new_panel(self.scrwin)
0103 self.mainwinh = (2*self.scrh)//3
0104 self.mainwinw = self.scrw - 4
0105 self.mainwiny = 2
0106 self.mainwinx = 2
0107
0108
0109 self.mainwin = curses.newwin(self.mainwinh, self.mainwinw+1,
0110 self.mainwiny, self.mainwinx)
0111 self.mainpan = curses.panel.new_panel(self.mainwin)
0112 self.mainwin.scrollok(0)
0113 self.mainwin.nodelay(1)
0114
0115 self.headerwin = curses.newwin(1, self.mainwinw+1,
0116 1, self.mainwinx)
0117 self.headerpan = curses.panel.new_panel(self.headerwin)
0118 self.headerwin.scrollok(0)
0119
0120 self.totalwin = curses.newwin(1, self.mainwinw+1,
0121 self.mainwinh+1, self.mainwinx)
0122 self.totalpan = curses.panel.new_panel(self.totalwin)
0123 self.totalwin.scrollok(0)
0124
0125 self.statuswinh = self.scrh-4-self.mainwinh
0126 self.statuswin = curses.newwin(self.statuswinh, self.mainwinw+1,
0127 self.mainwinh+3, self.mainwinx)
0128 self.statuspan = curses.panel.new_panel(self.statuswin)
0129 self.statuswin.scrollok(0)
0130
0131 try:
0132 self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
0133 except:
0134 pass
0135 self.headerwin.addnstr(0, 2, '#', self.mainwinw - 25, curses.A_BOLD)
0136 self.headerwin.addnstr(0, 4, 'Filename', self.mainwinw - 25, curses.A_BOLD)
0137 self.headerwin.addnstr(0, self.mainwinw - 24, 'Size', 4, curses.A_BOLD)
0138 self.headerwin.addnstr(0, self.mainwinw - 18, 'Download', 8, curses.A_BOLD)
0139 self.headerwin.addnstr(0, self.mainwinw - 6, 'Upload', 6, curses.A_BOLD)
0140 self.totalwin.addnstr(0, self.mainwinw - 27, 'Totals:', 7, curses.A_BOLD)
0141
0142 self._display_messages()
0143
0144 curses.panel.update_panels()
0145 curses.doupdate()
0146 self.changeflag.clear()
0147
0148
0149 def _display_line(self, s, bold = False):
0150 if self.disp_end:
0151 return True
0152 line = self.disp_line
0153 self.disp_line += 1
0154 if line < 0:
0155 return False
0156 if bold:
0157 self.mainwin.addnstr(line, 0, s, self.mainwinw, curses.A_BOLD)
0158 else:
0159 self.mainwin.addnstr(line, 0, s, self.mainwinw)
0160 if self.disp_line >= self.mainwinh:
0161 self.disp_end = True
0162 return self.disp_end
0163
0164 def _display_data(self, data):
0165 if 3*len(data) <= self.mainwinh:
0166 self.scroll_pos = 0
0167 self.scrolling = False
0168 elif self.scroll_time + DOWNLOAD_SCROLL_RATE < time():
0169 self.scroll_time = time()
0170 self.scroll_pos += 1
0171 self.scrolling = True
0172 if self.scroll_pos >= 3*len(data)+2:
0173 self.scroll_pos = 0
0174
0175 i = self.scroll_pos//3
0176 self.disp_line = (3*i)-self.scroll_pos
0177 self.disp_end = False
0178
0179 while not self.disp_end:
0180 ii = i % len(data)
0181 if i and not ii:
0182 if not self.scrolling:
0183 break
0184 self._display_line('')
0185 if self._display_line(''):
0186 break
0187 ( name, status, progress, peers, seeds, seedsmsg, dist,
0188 uprate, dnrate, upamt, dnamt, size, t, msg ) = data[ii]
0189 t = fmttime(t)
0190 if t:
0191 status = t
0192 name = ljust(name,self.mainwinw-32)
0193 size = rjust(fmtsize(size),8)
0194 uprate = rjust('%s/s' % fmtsize(uprate),10)
0195 dnrate = rjust('%s/s' % fmtsize(dnrate),10)
0196 line = "%3d %s%s%s%s" % (ii+1, name, size, dnrate, uprate)
0197 self._display_line(line, True)
0198 if peers + seeds:
0199 datastr = ' (%s) %s - %s peers %s seeds %s dist copies - %s up %s dn' % (
0200 progress, status, peers, seeds, dist,
0201 fmtsize(upamt), fmtsize(dnamt) )
0202 else:
0203 datastr = ' '+status+' ('+progress+')'
0204 self._display_line(datastr)
0205 self._display_line(' '+ljust(msg,self.mainwinw-4))
0206 i += 1
0207
0208 def display(self, name, data):
0209 if self.changeflag.isSet():
0210 return
0211
0212 inchar = self.mainwin.getch()
0213 if inchar == 12:
0214 self._remake_window()
0215
0216 self.mainwin.erase()
0217 if data:
0218 self._display_data(data)
0219 else:
0220 self.mainwin.addnstr( 1, self.mainwinw//2-5,
0221 'no torrents', 12, curses.A_BOLD )
0222 totalup = 0
0223 totaldn = 0
0224 for ( name, status, progress, peers, seeds, seedsmsg, dist,
0225 uprate, dnrate, upamt, dnamt, size, t, msg ) in data:
0226 totalup += uprate
0227 totaldn += dnrate
0228
0229 totalup = '%s/s' % fmtsize(totalup)
0230 totaldn = '%s/s' % fmtsize(totaldn)
0231
0232 self.totalwin.erase()
0233 self.totalwin.addnstr(0, self.mainwinw-27, 'Totals:', 7, curses.A_BOLD)
0234 self.totalwin.addnstr(0, self.mainwinw-20 + (10-len(totaldn)),
0235 totaldn, 10, curses.A_BOLD)
0236 self.totalwin.addnstr(0, self.mainwinw-10 + (10-len(totalup)),
0237 totalup, 10, curses.A_BOLD)
0238
0239 curses.panel.update_panels()
0240 curses.doupdate()
0241
0242 return inchar in (ord('q'),ord('Q'))
0243
0244 def message(self, s):
0245 self.messages.append(strftime('%x %X - ',localtime(time()))+s)
0246 self._display_messages()
0247
0248 def _display_messages(self):
0249 self.statuswin.erase()
0250 winpos = 0
0251 for s in self.messages[-self.statuswinh:]:
0252 self.statuswin.addnstr(winpos, 0, s, self.mainwinw)
0253 winpos += 1
0254 curses.panel.update_panels()
0255 curses.doupdate()
0256
0257 def exception(self, s):
0258 exceptions.append(s)
0259 self.message('SYSTEM ERROR - EXCEPTION GENERATED')
0260
0261
0262def FmTorrentWrapper(scrwin, url):
0263 server = ServerProxy(url)
0264 disp = CursesDisplayer(scrwin)
0265 while 1:
0266 status = server.getStatus()
0267 data = [ (name, d['status'], d['progress'], d['peers'], d['seeds'],
0268 d['seedsmsg'], d['dist'], d['uprate'], d['dnrate'], d['upamt'],
0269 d['dnamt'], d['size'], d['t'], d['msg'])
0270 for name, d in status.iteritems() ]
0271 disp.display("", data)
0272 sleep(1.0)
0273
0274if __name__ == '__main__':
0275 if len(sys.argv) < 2:
0276 print "usage: %s master_url" % sys.argv[0]
0277 else:
0278 url = sys.argv[-1]
0279 try:
0280 curses_wrapper(FmTorrentWrapper, url)
0281 except KeyboardInterrupt:
0282 pass