(file) Return to btlaunchmanycurses.py CVS log (file) (dir) Up to [Development] / bittornado

  1 theshadow 1.1 #!/usr/bin/env python
  2               
  3 theshadow 1.4 # Written by John Hoffman
  4 theshadow 1.1 # see LICENSE.txt for license information
  5               
  6 theshadow 1.9 DOWNLOAD_SCROLL_RATE = 1
  7               
  8 theshadow 1.2 from BitTornado import PSYCO
  9 theshadow 1.1 if PSYCO.psyco:
 10                   try:
 11                       import psyco
 12                       assert psyco.__version__ >= 0x010100f0
 13                       psyco.full()
 14                   except:
 15                       pass
 16               
 17 theshadow 1.4 from BitTornado.launchmanycore import LaunchMany
 18               from BitTornado.download_bt1 import defaults, get_usage
 19 theshadow 1.5 from BitTornado.parseargs import parseargs
 20 theshadow 1.4 from threading import Event
 21               from sys import argv, exit
 22 theshadow 1.10 from time import time, localtime, strftime
 23 theshadow 1.4  import sys, os
 24 theshadow 1.18 from BitTornado import version, report_email
 25 theshadow 1.4  
 26                try:
 27                    import curses
 28                    import curses.panel
 29                    from curses.wrapper import wrapper as curses_wrapper
 30                    from signal import signal, SIGWINCH 
 31                except:
 32                    print 'Textmode GUI initialization failed, cannot proceed.'
 33                    print
 34                    print 'This download interface requires the standard Python module ' \
 35                       '"curses", which is unfortunately not available for the native ' \
 36                       'Windows port of Python. It is however available for the Cygwin ' \
 37                       'port of Python, running on all Win32 systems (www.cygwin.com).'
 38                    print
 39                    print 'You may still use "btdownloadheadless.py" to download.'
 40                    sys.exit(1)
 41                
 42                assert sys.version >= '2', "Install Python 2.0 or greater"
 43                try:
 44                    True
 45                except:
 46 theshadow 1.4      True = 1
 47                    False = 0
 48 theshadow 1.1  
 49 theshadow 1.15 Exceptions = []
 50 theshadow 1.6  
 51 theshadow 1.1  def fmttime(n):
 52 theshadow 1.6      if n <= 0:
 53                        return None
 54 theshadow 1.1      n = int(n)
 55                    m, s = divmod(n, 60)
 56                    h, m = divmod(m, 60)
 57                    if h > 1000000:
 58 theshadow 1.6          return 'connecting to peers'
 59 theshadow 1.11     return 'ETA in %d:%02d:%02d' % (h, m, s)
 60 theshadow 1.1  
 61                def fmtsize(n):
 62 theshadow 1.8      n = int(n)
 63 theshadow 1.1      unit = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
 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 = float(n) / (1 << 10)
 71                    if i > 0:
 72                        size = '%.1f' % n + '%s' % unit[i]
 73                    else:
 74                        size = '%.0f' % n + '%s' % unit[i]
 75                    return size
 76                
 77 theshadow 1.11 def ljust(s, size):
 78 theshadow 1.14     s = s[:size]
 79 theshadow 1.12     return s + (' '*(size-len(s)))
 80 theshadow 1.11 
 81                def rjust(s, size):
 82 theshadow 1.14     s = s[:size]
 83 theshadow 1.12     return (' '*(size-len(s)))+s
 84 theshadow 1.11 
 85 theshadow 1.1  
 86 theshadow 1.4  class CursesDisplayer:
 87                    def __init__(self, scrwin):
 88 theshadow 1.9          self.messages = []
 89 theshadow 1.12         self.scroll_pos = 0
 90                        self.scroll_time = 0
 91 theshadow 1.9          
 92 theshadow 1.4          self.scrwin = scrwin
 93                        signal(SIGWINCH, self.winch_handler)
 94                        self.changeflag = Event()
 95                        self._remake_window()
 96                
 97                    def winch_handler(self, signum, stackframe):
 98                        self.changeflag.set()
 99                        curses.endwin()
100                        self.scrwin.refresh()
101                        self.scrwin = curses.newwin(0, 0, 0, 0)
102 theshadow 1.22         self._remake_window()
103 theshadow 1.10         self._display_messages()
104 theshadow 1.4  
105 theshadow 1.22     def _remake_window(self):
106 theshadow 1.4          self.scrh, self.scrw = self.scrwin.getmaxyx()
107                        self.scrpan = curses.panel.new_panel(self.scrwin)
108 theshadow 1.9          self.mainwinh = int(2*(self.scrh)/3)
109 theshadow 1.4          self.mainwinw = self.scrw - 4  # - 2 (bars) - 2 (spaces)
110                        self.mainwiny = 2         # + 1 (bar) + 1 (titles)
111                        self.mainwinx = 2         # + 1 (bar) + 1 (space)
112                        # + 1 to all windows so we can write at mainwinw
113                
114                        self.mainwin = curses.newwin(self.mainwinh, self.mainwinw+1,
115                                                     self.mainwiny, self.mainwinx)
116                        self.mainpan = curses.panel.new_panel(self.mainwin)
117                        self.mainwin.scrollok(0)
118                
119                        self.headerwin = curses.newwin(1, self.mainwinw+1,
120                                                       1, self.mainwinx)
121                        self.headerpan = curses.panel.new_panel(self.headerwin)
122                        self.headerwin.scrollok(0)
123                
124                        self.totalwin = curses.newwin(1, self.mainwinw+1,
125 theshadow 1.9                                        self.mainwinh+1, self.mainwinx)
126 theshadow 1.4          self.totalpan = curses.panel.new_panel(self.totalwin)
127                        self.totalwin.scrollok(0)
128                
129 theshadow 1.14         self.statuswinh = self.scrh-4-self.mainwinh
130 theshadow 1.11         self.statuswin = curses.newwin(self.statuswinh, self.mainwinw+1,
131 theshadow 1.9                                         self.mainwinh+3, self.mainwinx)
132 theshadow 1.6          self.statuspan = curses.panel.new_panel(self.statuswin)
133 theshadow 1.4          self.statuswin.scrollok(0)
134                
135                        self.scrwin.border(ord('|'),ord('|'),ord('-'),ord('-'),ord(' '),ord(' '),ord(' '),ord(' '))
136 theshadow 1.9          self.headerwin.addnstr(0, 2, '#', self.mainwinw - 25, curses.A_BOLD)
137                        self.headerwin.addnstr(0, 4, 'Filename', self.mainwinw - 25, curses.A_BOLD)
138                        self.headerwin.addnstr(0, self.mainwinw - 24, 'Size', 4, curses.A_BOLD)
139                        self.headerwin.addnstr(0, self.mainwinw - 18, 'Download', 8, curses.A_BOLD)
140                        self.headerwin.addnstr(0, self.mainwinw -  6, 'Upload', 6, curses.A_BOLD)
141                        self.totalwin.addnstr(0, self.mainwinw - 27, 'Totals:', 7, curses.A_BOLD)
142 theshadow 1.22 
143 theshadow 1.4          curses.panel.update_panels()
144                        curses.doupdate()
145 theshadow 1.5          self.changeflag.clear()
146 theshadow 1.4  
147                
148 theshadow 1.11     def _display_line(self, s, bold = False):
149                        if self.disp_end:
150                            return True
151                        line = self.disp_line
152                        self.disp_line += 1
153                        if line < 0:
154                            return False
155                        if bold:
156                            self.mainwin.addnstr(line, 0, s, self.mainwinw, curses.A_BOLD)
157                        else:
158                            self.mainwin.addnstr(line, 0, s, self.mainwinw)
159 theshadow 1.14         if self.disp_line >= self.mainwinh:
160 theshadow 1.11             self.disp_end = True
161                        return self.disp_end
162                
163 theshadow 1.17     def _display_data(self, data):    
164 theshadow 1.12         if 3*len(data) <= self.mainwinh:
165 theshadow 1.11             self.scroll_pos = 0
166                            self.scrolling = False
167                        elif self.scroll_time + DOWNLOAD_SCROLL_RATE < time():
168                            self.scroll_time = time()
169                            self.scroll_pos += 1
170                            self.scrolling = True
171 theshadow 1.14             if self.scroll_pos >= 3*len(data)+2:
172 theshadow 1.11                 self.scroll_pos = 0
173                
174                        i = int(self.scroll_pos/3)
175                        self.disp_line = (3*i)-self.scroll_pos
176                        self.disp_end = False
177                
178                        while not self.disp_end:
179                            ii = i % len(data)
180                            if i and not ii:
181                                if not self.scrolling:
182                                    break
183                                self._display_line('')
184                                if self._display_line(''):
185                                    break
186 theshadow 1.16             ( name, status, progress, peers, seeds, seedsmsg, dist,
187 theshadow 1.11               uprate, dnrate, upamt, dnamt, size, t, msg ) = data[ii]
188 theshadow 1.6              t = fmttime(t)
189                            if t:
190                                status = t
191 theshadow 1.11             name = ljust(name,self.mainwinw-32)
192                            size = rjust(fmtsize(size),8)
193                            uprate = rjust('%s/s' % fmtsize(uprate),10)
194                            dnrate = rjust('%s/s' % fmtsize(dnrate),10)
195                            line = "%3d %s%s%s%s" % (ii+1, name, size, dnrate, uprate)
196 theshadow 1.17             self._display_line(line, True)
197 theshadow 1.9              if peers + seeds:
198 theshadow 1.16                 datastr = '    (%s) %s - %s peers %s seeds %.3f dist copies - %s up %s dn' % (
199                                                progress, status, peers, seeds, dist,
200 theshadow 1.15                                 fmtsize(upamt), fmtsize(dnamt) )
201 theshadow 1.9              else:
202 theshadow 1.11                 datastr = '    '+status+' ('+progress+')'
203 theshadow 1.17             self._display_line(datastr)
204 theshadow 1.11             self._display_line('    '+msg)
205                            i += 1
206 theshadow 1.17 
207                    def display(self, data):
208                        if self.changeflag.isSet():
209                            return
210                
211 theshadow 1.22         inchar = self.mainwin.getch()
212                        if inchar == 12: # ^L
213                            self._remake_window()
214                
215 theshadow 1.17         self.mainwin.erase()
216                        if data:
217                            self._display_data(data)
218                        else:
219                            self.mainwin.addnstr( 1, int(self.mainwinw/2)-5,
220                                                  'no torrents', 12, curses.A_BOLD )
221                        totalup = 0
222                        totaldn = 0
223                        for ( name, status, progress, peers, seeds, seedsmsg, dist,
224                              uprate, dnrate, upamt, dnamt, size, t, msg ) in data:
225                            totalup += uprate
226                            totaldn += dnrate
227                        
228 theshadow 1.4          totalup = '%s/s' % fmtsize(totalup)
229                        totaldn = '%s/s' % fmtsize(totaldn)
230 theshadow 1.1          
231 theshadow 1.4          self.totalwin.erase()
232 theshadow 1.9          self.totalwin.addnstr(0, self.mainwinw-27, 'Totals:', 7, curses.A_BOLD)
233 theshadow 1.4          self.totalwin.addnstr(0, self.mainwinw-20 + (10-len(totaldn)),
234 theshadow 1.11                               totaldn, 10, curses.A_BOLD)
235 theshadow 1.4          self.totalwin.addnstr(0, self.mainwinw-10 + (10-len(totalup)),
236 theshadow 1.11                               totalup, 10, curses.A_BOLD)
237 theshadow 1.17 
238 theshadow 1.1          curses.panel.update_panels()
239                        curses.doupdate()
240 theshadow 1.22 
241                        return chr(inchar) in 'qQ'
242 theshadow 1.1  
243 theshadow 1.4      def message(self, s):
244 theshadow 1.10         self.messages.append(strftime('%x %X - ',localtime(time()))+s)
245                        self._display_messages()
246                
247                    def _display_messages(self):
248 theshadow 1.15         self.statuswin.erase()
249 theshadow 1.9          winpos = 0
250 theshadow 1.12         for s in self.messages[-self.statuswinh:]:
251 theshadow 1.14             self.statuswin.addnstr(winpos, 0, s, self.mainwinw)
252 theshadow 1.9              winpos += 1
253 theshadow 1.10         curses.panel.update_panels()
254                        curses.doupdate()
255 theshadow 1.15 
256                    def exception(self, s):
257                        Exceptions.append(s)
258 theshadow 1.17         self.message('SYSTEM ERROR - EXCEPTION GENERATED')
259 theshadow 1.15 
260 theshadow 1.4  
261                
262                def LaunchManyWrapper(scrwin, config):
263 theshadow 1.15     LaunchMany(config, CursesDisplayer(scrwin))
264 theshadow 1.4  
265 theshadow 1.1  
266                if __name__ == '__main__':
267                    if argv[1:] == ['--version']:
268                        print version
269                        exit(0)
270 theshadow 1.20     defaults.extend( [
271 theshadow 1.19         ( 'parse_dir_interval', 60,
272                          "how often to rescan the torrent directory, in seconds" ),
273                        ( 'saveas_style', 2,
274                          "How to name torrent downloads (1 = rename to torrent name, " +
275 theshadow 1.21           "2 = save under name in torrent, 3 = save in directory under torrent name)" ),
276 theshadow 1.19         ( 'display_path', 0,
277 theshadow 1.21           "whether to display the full path or the torrent contents for each torrent" ),
278 theshadow 1.20     ] )
279 theshadow 1.1      try:
280 theshadow 1.7          if len(argv) <= 1:
281                            print "Usage: btlaunchmany.py <directory> <global options>\n"
282                            print "<directory> - directory to look for .torrent files (semi-recursive)"
283                            print get_usage(defaults)
284                            exit(1)
285 theshadow 1.4          config, args = parseargs(argv[1:], defaults, 1, 1)
286                        if not os.path.isdir(args[0]):
287                            raise ValueError("Warning: "+args[0]+" is not a directory")
288                        config['torrent_dir'] = args[0]
289                    except ValueError, e:
290                        print 'error: ' + str(e) + '\nrun with no args for parameter explanations'
291                        exit(1)
292 theshadow 1.1  
293 theshadow 1.15     curses_wrapper(LaunchManyWrapper, config)
294                    if Exceptions:
295                        print '\nEXCEPTION:'
296                        print Exceptions[0]
297 theshadow 1.18         print 'please report this to '+report_email

No CVS admin address has been configured
Powered by
ViewCVS 0.9.3