root/anonlaunchmanycurses.py

Revision 20846dfc6b1f0cce7809de82867e08f69a808100, 9.9 kB (checked in by John M. Schanck <john@…>, 2 weeks ago)

logging: Created init_logging for setting log options

No tracker logging to disk

  • Property mode set to 100755
Line 
1#!/usr/bin/env python
2
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16# Written by John Hoffman
17
18from __future__ import division
19
20DOWNLOAD_SCROLL_RATE = 1
21
22import sys, os
23from threading import Event
24from time import time, localtime, strftime
25
26from Anomos.launchmanycore import LaunchMany
27from Anomos.defaultargs import get_defaults
28from Anomos.parseargs import parseargs, printHelp
29from Anomos import configfile
30from Anomos import version
31from Anomos import BTFailure
32
33try:
34    import curses
35    import curses.panel
36    from curses.wrapper import wrapper as curses_wrapper
37    from signal import signal, SIGWINCH
38except:
39    print 'Textmode GUI initialization failed, cannot proceed.'
40    print
41    print 'This download interface requires the standard Python module ' \
42       '"curses", which is unfortunately not available for the native ' \
43       'Windows port of Python. It is however available for the Cygwin ' \
44       'port of Python, running on all Win32 systems (www.cygwin.com).'
45    print
46    print 'You may still use "anondownloadheadless.py" to download.'
47    sys.exit(1)
48
49exceptions = []
50
51def fmttime(n):
52    if n <= 0:
53        return None
54    n = int(n)
55    m, s = divmod(n, 60)
56    h, m = divmod(m, 60)
57    if h > 1000000:
58        return 'connecting to peers'
59    return 'ETA in %d:%02d:%02d' % (h, m, s)
60
61def fmtsize(n):
62    n = long(n)
63    unit = [' B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
64    i = 0
65    if (n > 999):
66        i = 1
67        while i + 1 < len(unit) and (n >> 10) >= 999:
68            i += 1
69            n >>= 10
70        n /= 1024
71    if i > 0:
72        size = '%.1f' % n + '%s' % unit[i]
73    else:
74        size = '%.0f' % n + '%s' % unit[i]
75    return size
76
77def ljust(s, size):
78    s = s[:size]
79    return s + (' '*(size-len(s)))
80
81def rjust(s, size):
82    s = s[:size]
83    return (' '*(size-len(s)))+s
84
85
86class CursesDisplayer(object):
87
88    def __init__(self, scrwin):
89        self.messages = []
90        self.scroll_pos = 0
91        self.scroll_time = 0
92
93        self.scrwin = scrwin
94        signal(SIGWINCH, self.winch_handler)
95        self.changeflag = Event()
96        self._remake_window()
97
98    def winch_handler(self, signum, stackframe):
99        self.changeflag.set()
100        curses.endwin()
101        self.scrwin.refresh()
102        self.scrwin = curses.newwin(0, 0, 0, 0)
103        self._remake_window()
104        self._display_messages()
105
106    def _remake_window(self):
107        self.scrh, self.scrw = self.scrwin.getmaxyx()
108        self.scrpan = curses.panel.new_panel(self.scrwin)
109        self.mainwinh = (2*self.scrh)//3
110        self.mainwinw = self.scrw - 4  # - 2 (bars) - 2 (spaces)
111        self.mainwiny = 2         # + 1 (bar) + 1 (titles)
112        self.mainwinx = 2         # + 1 (bar) + 1 (space)
113        # + 1 to all windows so we can write at mainwinw
114
115        self.mainwin = curses.newwin(self.mainwinh, self.mainwinw+1,
116                                     self.mainwiny, self.mainwinx)
117        self.mainpan = curses.panel.new_panel(self.mainwin)
118        self.mainwin.scrollok(0)
119        self.mainwin.nodelay(1)
120
121        self.headerwin = curses.newwin(1, self.mainwinw+1,
122                                       1, self.mainwinx)
123        self.headerpan = curses.panel.new_panel(self.headerwin)
124        self.headerwin.scrollok(0)
125
126        self.totalwin = curses.newwin(1, self.mainwinw+1,
127                                      self.mainwinh+1, self.mainwinx)
128        self.totalpan = curses.panel.new_panel(self.totalwin)
129        self.totalwin.scrollok(0)
130
131        self.statuswinh = self.scrh-4-self.mainwinh
132        self.statuswin = curses.newwin(self.statuswinh, self.mainwinw+1,
133                                       self.mainwinh+3, self.mainwinx)
134        self.statuspan = curses.panel.new_panel(self.statuswin)
135        self.statuswin.scrollok(0)
136
137        try:
138            self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
139        except:
140            pass
141        self.headerwin.addnstr(0, 2, '#', self.mainwinw - 25, curses.A_BOLD)
142        self.headerwin.addnstr(0, 4, 'Filename', self.mainwinw - 25, curses.A_BOLD)
143        self.headerwin.addnstr(0, self.mainwinw - 24, 'Size', 4, curses.A_BOLD)
144        self.headerwin.addnstr(0, self.mainwinw - 18, 'Download', 8, curses.A_BOLD)
145        self.headerwin.addnstr(0, self.mainwinw -  6, 'Upload', 6, curses.A_BOLD)
146        self.totalwin.addnstr(0, self.mainwinw - 27, 'Totals:', 7, curses.A_BOLD)
147
148        self._display_messages()
149
150        curses.panel.update_panels()
151        curses.doupdate()
152        self.changeflag.clear()
153
154
155    def _display_line(self, s, bold = False):
156        if self.disp_end:
157            return True
158        line = self.disp_line
159        self.disp_line += 1
160        if line < 0:
161            return False
162        if bold:
163            self.mainwin.addnstr(line, 0, s, self.mainwinw, curses.A_BOLD)
164        else:
165            self.mainwin.addnstr(line, 0, s, self.mainwinw)
166        if self.disp_line >= self.mainwinh:
167            self.disp_end = True
168        return self.disp_end
169
170    def _display_data(self, data):
171        if 3*len(data) <= self.mainwinh:
172            self.scroll_pos = 0
173            self.scrolling = False
174        elif self.scroll_time + DOWNLOAD_SCROLL_RATE < time():
175            self.scroll_time = time()
176            self.scroll_pos += 1
177            self.scrolling = True
178            if self.scroll_pos >= 3*len(data)+2:
179                self.scroll_pos = 0
180
181        i = self.scroll_pos//3
182        self.disp_line = (3*i)-self.scroll_pos
183        self.disp_end = False
184
185        while not self.disp_end:
186            ii = i % len(data)
187            if i and not ii:
188                if not self.scrolling:
189                    break
190                self._display_line('')
191                if self._display_line(''):
192                    break
193            ( name, status, progress, peers, seeds, seedsmsg, dist,
194              uprate, dnrate, upamt, dnamt, size, t, msg ) = data[ii]
195            t = fmttime(t)
196            if t:
197                status = t
198            name = ljust(name,self.mainwinw-32)
199            size = rjust(fmtsize(size),8)
200            uprate = rjust('%s/s' % fmtsize(uprate),10)
201            dnrate = rjust('%s/s' % fmtsize(dnrate),10)
202            line = "%3d %s%s%s%s" % (ii+1, name, size, dnrate, uprate)
203            self._display_line(line, True)
204            if peers + seeds:
205                datastr = '    (%s) %s - %s peers %s seeds %s dist copies - %s up %s dn' % (
206                                progress, status, peers, seeds, dist,
207                                fmtsize(upamt), fmtsize(dnamt) )
208            else:
209                datastr = '    '+status+' ('+progress+')'
210            self._display_line(datastr)
211            self._display_line('    '+ljust(msg,self.mainwinw-4))
212            i += 1
213
214    def display(self, data):
215        if self.changeflag.isSet():
216            return
217
218        inchar = self.mainwin.getch()
219        if inchar == 12: # ^L
220            self._remake_window()
221
222        self.mainwin.erase()
223        if data:
224            self._display_data(data)
225        else:
226            self.mainwin.addnstr( 1, self.mainwinw//2-5,
227                                  'no torrents', 12, curses.A_BOLD )
228        totalup = 0
229        totaldn = 0
230        for ( name, status, progress, peers, seeds, seedsmsg, dist,
231              uprate, dnrate, upamt, dnamt, size, t, msg ) in data:
232            totalup += uprate
233            totaldn += dnrate
234
235        totalup = '%s/s' % fmtsize(totalup)
236        totaldn = '%s/s' % fmtsize(totaldn)
237
238        self.totalwin.erase()
239        self.totalwin.addnstr(0, self.mainwinw-27, 'Totals:', 7, curses.A_BOLD)
240        self.totalwin.addnstr(0, self.mainwinw-20 + (10-len(totaldn)),
241                              totaldn, 10, curses.A_BOLD)
242        self.totalwin.addnstr(0, self.mainwinw-10 + (10-len(totalup)),
243                              totalup, 10, curses.A_BOLD)
244
245        curses.panel.update_panels()
246        curses.doupdate()
247
248        return inchar in (ord('q'),ord('Q'))
249
250    def message(self, s):
251        self.messages.append(strftime('%x %X - ',localtime(time()))+s)
252        self._display_messages()
253
254    def _display_messages(self):
255        self.statuswin.erase()
256        winpos = 0
257        for s in self.messages[-self.statuswinh:]:
258            self.statuswin.addnstr(winpos, 0, s, self.mainwinw)
259            winpos += 1
260        curses.panel.update_panels()
261        curses.doupdate()
262
263    def exception(self, s):
264        exceptions.append(s)
265        self.message('SYSTEM ERROR - EXCEPTION GENERATED')
266
267
268
269def LaunchManyWrapper(scrwin, config):
270    LaunchMany(config, CursesDisplayer(scrwin), 'anonlaunchmanycurses')
271
272
273if __name__ == '__main__':
274    import Anomos
275    Anomos.ensure_minimum_config()
276    Anomos.init_logging()
277
278    uiname = 'anonlaunchmanycurses'
279    defaults = get_defaults(uiname)
280    try:
281        if len(sys.argv) < 2:
282            printHelp(uiname, defaults)
283            sys.exit(1)
284        config, args = configfile.parse_configuration_and_args(defaults,
285                                      uiname, sys.argv[1:], 0, 1)
286        if args:
287            config['torrent_dir'] = args[0]
288        if not os.path.isdir(config['torrent_dir']):
289            raise BTFailure("Warning: "+args[0]+" is not a directory")
290    except BTFailure, e:
291        print 'error: ' + str(e) + '\nrun with no args for parameter explanations'
292        sys.exit(1)
293
294    curses_wrapper(LaunchManyWrapper, config)
295    if exceptions:
296        print '\nEXCEPTION:'
297        print exceptions[0]
Note: See TracBrowser for help on using the browser.