summaryrefslogtreecommitdiffstats
path: root/launchmany-curses.py
diff options
context:
space:
mode:
Diffstat (limited to 'launchmany-curses.py')
-rwxr-xr-xlaunchmany-curses.py302
1 files changed, 302 insertions, 0 deletions
diff --git a/launchmany-curses.py b/launchmany-curses.py
new file mode 100755
index 0000000..86f8329
--- /dev/null
+++ b/launchmany-curses.py
@@ -0,0 +1,302 @@
+#!/usr/bin/env python
+
+# The contents of this file are subject to the BitTorrent Open Source License
+# Version 1.1 (the License). You may not copy or use this file, in either
+# source code or executable form, except in compliance with the License. You
+# may obtain a copy of the License at http://www.bittorrent.com/license/.
+#
+# Software distributed under the License is distributed on an AS IS basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+
+# Written by John Hoffman
+
+from __future__ import division
+
+from BitTorrent.platform import install_translation
+install_translation()
+
+DOWNLOAD_SCROLL_RATE = 1
+
+import sys, os
+from threading import Event
+from time import time, localtime, strftime
+
+from BitTorrent.obsoletepythonsupport import *
+from BitTorrent.launchmanycore import LaunchMany
+from BitTorrent.defaultargs import get_defaults
+from BitTorrent.parseargs import parseargs, printHelp
+from BitTorrent import configfile
+from BitTorrent import version
+from BitTorrent import BTFailure
+
+try:
+ curses = import_curses()
+ import curses.panel
+ from curses.wrapper import wrapper as curses_wrapper
+ from signal import signal, SIGWINCH
+except:
+ print _("Textmode GUI initialization failed, cannot proceed.")
+ print
+ 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).")
+ print
+ print _("You may still use \"btdownloadheadless.py\" to download.")
+ sys.exit(1)
+
+exceptions = []
+
+def fmttime(n):
+ if n <= 0:
+ return None
+ n = int(n)
+ m, s = divmod(n, 60)
+ h, m = divmod(m, 60)
+ if h > 1000000:
+ return _("connecting to peers")
+ return _("ETA in %d:%02d:%02d") % (h, m, s)
+
+def fmtsize(n):
+ n = long(n)
+ unit = [' B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
+ i = 0
+ if (n > 999):
+ i = 1
+ while i + 1 < len(unit) and (n >> 10) >= 999:
+ i += 1
+ n >>= 10
+ n /= 1024
+ if i > 0:
+ size = '%.1f' % n + '%s' % unit[i]
+ else:
+ size = '%.0f' % n + '%s' % unit[i]
+ return size
+
+def ljust(s, size):
+ s = s[:size]
+ return s + (' '*(size-len(s)))
+
+def rjust(s, size):
+ s = s[:size]
+ return (' '*(size-len(s)))+s
+
+
+class CursesDisplayer(object):
+
+ def __init__(self, scrwin):
+ self.messages = []
+ self.scroll_pos = 0
+ self.scroll_time = 0
+
+ self.scrwin = scrwin
+ signal(SIGWINCH, self.winch_handler)
+ self.changeflag = Event()
+ self._remake_window()
+ curses.use_default_colors()
+
+ def winch_handler(self, signum, stackframe):
+ self.changeflag.set()
+ curses.endwin()
+ self.scrwin.refresh()
+ self.scrwin = curses.newwin(0, 0, 0, 0)
+ self._remake_window()
+ self._display_messages()
+
+ def _remake_window(self):
+ self.scrh, self.scrw = self.scrwin.getmaxyx()
+ self.scrpan = curses.panel.new_panel(self.scrwin)
+ self.mainwinh = (2*self.scrh)//3
+ self.mainwinw = self.scrw - 4 # - 2 (bars) - 2 (spaces)
+ self.mainwiny = 2 # + 1 (bar) + 1 (titles)
+ self.mainwinx = 2 # + 1 (bar) + 1 (space)
+ # + 1 to all windows so we can write at mainwinw
+
+ self.mainwin = curses.newwin(self.mainwinh, self.mainwinw+1,
+ self.mainwiny, self.mainwinx)
+ self.mainpan = curses.panel.new_panel(self.mainwin)
+ self.mainwin.scrollok(0)
+ self.mainwin.nodelay(1)
+
+ self.headerwin = curses.newwin(1, self.mainwinw+1,
+ 1, self.mainwinx)
+ self.headerpan = curses.panel.new_panel(self.headerwin)
+ self.headerwin.scrollok(0)
+
+ self.totalwin = curses.newwin(1, self.mainwinw+1,
+ self.mainwinh+1, self.mainwinx)
+ self.totalpan = curses.panel.new_panel(self.totalwin)
+ self.totalwin.scrollok(0)
+
+ self.statuswinh = self.scrh-4-self.mainwinh
+ self.statuswin = curses.newwin(self.statuswinh, self.mainwinw+1,
+ self.mainwinh+3, self.mainwinx)
+ self.statuspan = curses.panel.new_panel(self.statuswin)
+ self.statuswin.scrollok(0)
+
+ try:
+ self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
+ except:
+ pass
+ rcols = (_("Size"),_("Download"),_("Upload"))
+ rwids = (8, 10, 10)
+ rwid = sum(rwids)
+ start = self.mainwinw - rwid
+ self.headerwin.addnstr(0, 2, '#', start, curses.A_BOLD)
+ self.headerwin.addnstr(0, 4, _("Filename"), start, curses.A_BOLD)
+
+ for s,w in zip(rcols, rwids):
+ st = start + max(w - len(s), 0)
+ self.headerwin.addnstr(0, st, s[:w], len(s[:w]), curses.A_BOLD)
+ start += w
+
+ self.totalwin.addnstr(0, self.mainwinw - 27, _("Totals:"), 7, curses.A_BOLD)
+
+ self._display_messages()
+
+ curses.panel.update_panels()
+ curses.doupdate()
+ self.changeflag.clear()
+
+
+ def _display_line(self, s, bold = False):
+ if self.disp_end:
+ return True
+ line = self.disp_line
+ self.disp_line += 1
+ if line < 0:
+ return False
+ if bold:
+ self.mainwin.addnstr(line, 0, s, self.mainwinw, curses.A_BOLD)
+ else:
+ self.mainwin.addnstr(line, 0, s, self.mainwinw)
+ if self.disp_line >= self.mainwinh:
+ self.disp_end = True
+ return self.disp_end
+
+ def _display_data(self, data):
+ if 3*len(data) <= self.mainwinh:
+ self.scroll_pos = 0
+ self.scrolling = False
+ elif self.scroll_time + DOWNLOAD_SCROLL_RATE < time():
+ self.scroll_time = time()
+ self.scroll_pos += 1
+ self.scrolling = True
+ if self.scroll_pos >= 3*len(data)+2:
+ self.scroll_pos = 0
+
+ i = self.scroll_pos//3
+ self.disp_line = (3*i)-self.scroll_pos
+ self.disp_end = False
+
+ while not self.disp_end:
+ ii = i % len(data)
+ if i and not ii:
+ if not self.scrolling:
+ break
+ self._display_line('')
+ if self._display_line(''):
+ break
+ ( name, status, progress, peers, seeds, seedsmsg, dist,
+ uprate, dnrate, upamt, dnamt, size, t, msg ) = data[ii]
+ t = fmttime(t)
+ if t:
+ status = t
+ name = ljust(name,self.mainwinw-32)
+ size = rjust(fmtsize(size),8)
+ uprate = rjust('%s/s' % fmtsize(uprate),10)
+ dnrate = rjust('%s/s' % fmtsize(dnrate),10)
+ line = "%3d %s%s%s%s" % (ii+1, name, size, dnrate, uprate)
+ self._display_line(line, True)
+ if peers + seeds:
+ datastr = _(" (%s) %s - %s peers %s seeds %s dist copies - %s dn %s up") % (
+ progress, status, peers, seeds, dist,
+ fmtsize(dnamt), fmtsize(upamt) )
+ else:
+ datastr = ' '+status+' ('+progress+')'
+ self._display_line(datastr)
+ self._display_line(' '+ljust(msg,self.mainwinw-4))
+ i += 1
+
+ def display(self, data):
+ if self.changeflag.isSet():
+ return
+
+ inchar = self.mainwin.getch()
+ if inchar == 12: # ^L
+ self._remake_window()
+
+ self.mainwin.erase()
+ if data:
+ self._display_data(data)
+ else:
+ self.mainwin.addnstr( 1, self.mainwinw//2-5,
+ _("no torrents"), 12, curses.A_BOLD )
+ totalup = 0
+ totaldn = 0
+ for ( name, status, progress, peers, seeds, seedsmsg, dist,
+ uprate, dnrate, upamt, dnamt, size, t, msg ) in data:
+ totalup += uprate
+ totaldn += dnrate
+
+ totalup = '%s/s' % fmtsize(totalup)
+ totaldn = '%s/s' % fmtsize(totaldn)
+
+ self.totalwin.erase()
+ self.totalwin.addnstr(0, self.mainwinw-27, _("Totals:"), 7, curses.A_BOLD)
+ self.totalwin.addnstr(0, self.mainwinw-20 + (10-len(totaldn)),
+ totaldn, 10, curses.A_BOLD)
+ self.totalwin.addnstr(0, self.mainwinw-10 + (10-len(totalup)),
+ totalup, 10, curses.A_BOLD)
+
+ curses.panel.update_panels()
+ curses.doupdate()
+
+ return inchar in (ord('q'),ord('Q'))
+
+ def message(self, s):
+ self.messages.append(strftime('%x %X - ',localtime(time()))+s)
+ self._display_messages()
+
+ def _display_messages(self):
+ self.statuswin.erase()
+ winpos = 0
+ for s in self.messages[-self.statuswinh:]:
+ self.statuswin.addnstr(winpos, 0, s, self.mainwinw)
+ winpos += 1
+ curses.panel.update_panels()
+ curses.doupdate()
+
+ def exception(self, s):
+ exceptions.append(s)
+ self.message(_("SYSTEM ERROR - EXCEPTION GENERATED"))
+
+
+
+def LaunchManyWrapper(scrwin, config):
+ LaunchMany(config, CursesDisplayer(scrwin), 'launchmany-curses')
+
+
+if __name__ == '__main__':
+ uiname = 'launchmany-curses'
+ defaults = get_defaults(uiname)
+ try:
+ if len(sys.argv) < 2:
+ printHelp(uiname, defaults)
+ sys.exit(1)
+ config, args = configfile.parse_configuration_and_args(defaults,
+ uiname, sys.argv[1:], 0, 1)
+ if args:
+ config['torrent_dir'] = args[0]
+ if not os.path.isdir(config['torrent_dir']):
+ raise BTFailure(_("Warning: ")+args[0]+_(" is not a directory"))
+ except BTFailure, e:
+ print _("error: ") + str(e) + _("\nrun with no args for parameter explanations")
+ sys.exit(1)
+
+ curses_wrapper(LaunchManyWrapper, config)
+ if exceptions:
+ print _("\nEXCEPTION:")
+ print exceptions[0]