diff options
| -rw-r--r-- | NEWS | 6 | ||||
| -rwxr-xr-x | transmission-remote-cli | 91 | 
2 files changed, 51 insertions, 46 deletions
| @@ -1,3 +1,9 @@ +1.4.1  2012-09-05 +    BUGFIXES: +        - Indices of sorted file lists are now pointing to the correct files +        - Fix crash in terminals without colors + +  1.4  2012-07-30      BUGFIXES:          - Fix crash upon opening help window for small terminals diff --git a/transmission-remote-cli b/transmission-remote-cli index 4bfe38d..8cd5dbf 100755 --- a/transmission-remote-cli +++ b/transmission-remote-cli @@ -16,7 +16,7 @@  # http://www.gnu.org/licenses/gpl-3.0.txt                              #  ######################################################################## -VERSION = '1.4' +VERSION = '1.4.1'  TRNSM_VERSION_MIN = '1.90'  TRNSM_VERSION_MAX = '2.61' @@ -138,8 +138,16 @@ config.set('Colors', 'file_prio_off',    'bg:blue,fg:black')  class ColorManager:      def __init__(self, config):          self.config = dict() +        self.term_has_colors = curses.has_colors() +        curses.start_color() +        if self.term_has_colors: +            curses.use_default_colors()          for name in config.keys():              self.config[name] = self._parse_color_pair(config[name]) +            if self.term_has_colors: +                curses.init_pair(self.config[name]['id'], +                                 self.config[name]['fg'], +                                 self.config[name]['bg'])      def _parse_color_pair(self, pair):          # BG and FG are intentionally switched here because colors are always @@ -151,10 +159,7 @@ class ColorManager:                   'bg': eval('curses.COLOR_' + bg_name),                   'fg': eval('curses.COLOR_' + fg_name) } -    def get_id(self, name): return self.config[name]['id'] -    def get_bg(self, name): return self.config[name]['bg'] -    def get_fg(self, name): return self.config[name]['fg'] -    def get_names(self): return self.config.keys() +    def id(self, name): return self.config[name]['id']  class Normalizer: @@ -803,6 +808,7 @@ class Interface:          self.details_category_focus = 0  # overview/files/peers/tracker in details          self.focus_detaillist       = -1 # same as focus but for details          self.selected_files         = [] # marked files in details +        self.file_index_map         = {} # Maps local torrent's file indices to server file indices          self.scrollpos_detaillist   = 0  # same as scrollpos but for details          self.compact_torrentlist    = False # draw only one line for each torrent in compact mode          self.exit_now               = False @@ -897,17 +903,7 @@ class Interface:          hide_cursor() -        # enable colors if available -        try: -            curses.start_color() -            curses.use_default_colors() -            self.colors = ColorManager(dict(config.items('Colors'))) -            for name in sorted(self.colors.get_names()): -                curses.init_pair(self.colors.get_id(name), -                                 self.colors.get_fg(name), -                                 self.colors.get_bg(name)) -        except: -            pass +        self.colors = ColorManager(dict(config.items('Colors')))          # http://bugs.python.org/issue2675          try: @@ -1398,10 +1394,10 @@ class Interface:                  if self.details_category_focus == 1 and \                          (self.selected_files or self.focus_detaillist > -1):                      if self.selected_files: -                        files = set(self.selected_files) +                        files = set([self.file_index_map[index] for index in self.selected_files])                          server.increase_file_priority(files)                      elif self.focus_detaillist > -1: -                        server.increase_file_priority([self.focus_detaillist]) +                        server.increase_file_priority([self.file_index_map[self.focus_detaillist]])                  else:                      self.scrollpos_detaillist = 0                      self.next_details() @@ -1409,10 +1405,10 @@ class Interface:                  if self.details_category_focus == 1 and \                          (self.selected_files or self.focus_detaillist > -1):                      if self.selected_files: -                        files = set(self.selected_files) +                        files = set([self.file_index_map[index] for index in self.selected_files])                          server.decrease_file_priority(files)                      elif self.focus_detaillist > -1: -                        server.decrease_file_priority([self.focus_detaillist]) +                        server.decrease_file_priority([self.file_index_map[self.focus_detaillist]])                  else:                      self.scrollpos_detaillist = 0                      self.prev_details() @@ -1594,24 +1590,24 @@ class Interface:          self.pad.addch(curses.ACS_DARROW, (0,curses.A_BOLD)[torrent['downloadLimited']])          rate = ('',scale_bytes(torrent['rateDownload']))[torrent['rateDownload']>0]          self.pad.addstr(rate.rjust(self.rateDownload_width), -                        curses.color_pair(self.colors.get_id('download_rate')) + curses.A_BOLD + curses.A_REVERSE) +                        curses.color_pair(self.colors.id('download_rate')) + curses.A_BOLD + curses.A_REVERSE)      def draw_uploadrate(self, torrent, ypos):          self.pad.move(ypos, self.width-self.rateUpload_width-1)          self.pad.addch(curses.ACS_UARROW, (0,curses.A_BOLD)[torrent['uploadLimited']])          rate = ('',scale_bytes(torrent['rateUpload']))[torrent['rateUpload']>0]          self.pad.addstr(rate.rjust(self.rateUpload_width), -                        curses.color_pair(self.colors.get_id('upload_rate')) + curses.A_BOLD + curses.A_REVERSE) +                        curses.color_pair(self.colors.id('upload_rate')) + curses.A_BOLD + curses.A_REVERSE)      def draw_ratio(self, torrent, ypos):          self.pad.addch(ypos+1, self.width-self.rateUpload_width-1, curses.ACS_DIAMOND,                         (0,curses.A_BOLD)[torrent['uploadRatio'] < 1 and torrent['uploadRatio'] >= 0])          self.pad.addstr(ypos+1, self.width-self.rateUpload_width,                          num2str(torrent['uploadRatio'], '%.02f').rjust(self.rateUpload_width), -                        curses.color_pair(self.colors.get_id('eta+ratio')) + curses.A_BOLD + curses.A_REVERSE) +                        curses.color_pair(self.colors.id('eta+ratio')) + curses.A_BOLD + curses.A_REVERSE)      def draw_eta(self, torrent, ypos):          self.pad.addch(ypos+1, self.width-self.rateDownload_width-self.rateUpload_width-3, curses.ACS_PLMINUS)          self.pad.addstr(ypos+1, self.width-self.rateDownload_width-self.rateUpload_width-2,                          scale_time(torrent['eta']).rjust(self.rateDownload_width), -                        curses.color_pair(self.colors.get_id('eta+ratio')) + curses.A_BOLD + curses.A_REVERSE) +                        curses.color_pair(self.colors.id('eta+ratio')) + curses.A_BOLD + curses.A_REVERSE)      def draw_torrentlist_title(self, torrent, focused, width, ypos): @@ -1632,18 +1628,18 @@ class Interface:          if torrent['status'] == Transmission.STATUS_SEED or \             torrent['status'] == Transmission.STATUS_SEED_WAIT: -            color = curses.color_pair(self.colors.get_id('title_seed')) +            color = curses.color_pair(self.colors.id('title_seed'))          elif torrent['status'] == Transmission.STATUS_STOPPED: -            color = curses.color_pair(self.colors.get_id('title_paused')) +            color = curses.color_pair(self.colors.id('title_paused'))          elif torrent['status'] == Transmission.STATUS_CHECK or \               torrent['status'] == Transmission.STATUS_CHECK_WAIT: -            color = curses.color_pair(self.colors.get_id('title_verify')) +            color = curses.color_pair(self.colors.id('title_verify'))          elif torrent['status'] == Transmission.STATUS_ISOLATED: -            color = curses.color_pair(self.colors.get_id('title_error')) +            color = curses.color_pair(self.colors.id('title_error'))          elif torrent['rateDownload'] == 0: -            color = curses.color_pair(self.colors.get_id('title_idle')) +            color = curses.color_pair(self.colors.id('title_idle'))          elif torrent['percentDone'] < 100: -            color = curses.color_pair(self.colors.get_id('title_download')) +            color = curses.color_pair(self.colors.id('title_download'))          else:              color = 0 @@ -1938,16 +1934,16 @@ class Interface:              for part in re.split('(high|normal|low|off)', line[0:30], 1):                  if part == 'high':                      self.pad.addstr(ypos, xpos, self.enc(part), -                                    curses_tags + curses.color_pair(self.colors.get_id('file_prio_high'))) +                                    curses_tags + curses.color_pair(self.colors.id('file_prio_high')))                  elif part == 'normal':                      self.pad.addstr(ypos, xpos, self.enc(part), -                                    curses_tags + curses.color_pair(self.colors.get_id('file_prio_normal'))) +                                    curses_tags + curses.color_pair(self.colors.id('file_prio_normal')))                  elif part == 'low':                      self.pad.addstr(ypos, xpos, self.enc(part), -                                    curses_tags + curses.color_pair(self.colors.get_id('file_prio_low'))) +                                    curses_tags + curses.color_pair(self.colors.id('file_prio_low')))                  elif part == 'off':                      self.pad.addstr(ypos, xpos, self.enc(part), -                                    curses_tags + curses.color_pair(self.colors.get_id('file_prio_off'))) +                                    curses_tags + curses.color_pair(self.colors.id('file_prio_off')))                  else:                      self.pad.addstr(ypos, xpos, self.enc(part), curses_tags)                  xpos += len(part) @@ -1958,13 +1954,16 @@ class Interface:      def create_filelist(self):          filelist = [] -        files = self.torrent_details['files'] +        files = sorted(self.torrent_details['files'], cmp=lambda x,y: cmp(x['name'], y['name']))          current_folder = []          current_depth = 0          index = 0          pos = 0          pos_before_focus = 0 +        # Build new mapping between sorted local files and transmission-daemon's unsorted files +        self.file_index_map = {}          for file in files: +            self.file_index_map[index] = self.torrent_details['files'].index(file)              f = file['name'].split('/')              f_len = len(f) - 1              if f[:f_len] != current_folder: @@ -2024,7 +2023,7 @@ class Interface:      def create_filelist_line(self, name, index, percent, length, current_depth):          line = "%s  %6.1f%%" % (str(index+1).rjust(3), percent) + \              '  '+scale_bytes(length).rjust(5) + \ -            '  '+server.get_file_priority(self.torrent_details['id'], index).center(8) + \ +            '  '+server.get_file_priority(self.torrent_details['id'], self.file_index_map[index]).center(8) + \              " %s| %s" % ('  '*current_depth, name[0:self.width-31-current_depth])          if index == self.focus_detaillist:              line = '_F' + line @@ -2330,7 +2329,7 @@ class Interface:              if self.filter_list:                  self.screen.addstr("Filter:", curses.A_REVERSE)                  self.screen.addstr("%s%s" % (('','not ')[self.filter_inverse], self.filter_list), -                                   curses.color_pair(self.colors.get_id('filter_status')) +                                   curses.color_pair(self.colors.id('filter_status'))                                     + curses.A_REVERSE)              # show last sort order (if terminal size permits it) @@ -2339,7 +2338,7 @@ class Interface:                  self.screen.addstr(" Sort by:", curses.A_REVERSE)                  name = [name[1] for name in self.sort_options if name[0] == self.sort_orders[-1]['name']][0]                  name = name.replace('_', '').lower() -                curses_tags = curses.color_pair(self.colors.get_id('filter_status')) + curses.A_REVERSE +                curses_tags = curses.color_pair(self.colors.id('filter_status')) + curses.A_REVERSE                  if self.sort_orders[-1]['reverse']:                      self.screen.addch(curses.ACS_DARROW, curses_tags)                  else: @@ -2370,14 +2369,14 @@ class Interface:          self.screen.move(self.height - 1, self.width - rates_width - limits_width)          self.screen.addch(curses.ACS_DARROW, curses.A_REVERSE)          self.screen.addstr(scale_bytes(self.stats['downloadSpeed']).rjust(self.rateDownload_width), -                           curses.color_pair(self.colors.get_id('download_rate')) +                           curses.color_pair(self.colors.id('download_rate'))                             + curses.A_REVERSE + curses.A_BOLD)          self.screen.addstr(limits['dn_limit'], curses.A_REVERSE)          self.screen.addch(' ', curses.A_REVERSE)          self.screen.addch(curses.ACS_UARROW, curses.A_REVERSE)          self.screen.insstr(limits['up_limit'], curses.A_REVERSE)          self.screen.insstr(scale_bytes(self.stats['uploadSpeed']).rjust(self.rateUpload_width), -                           curses.color_pair(self.colors.get_id('upload_rate')) +                           curses.color_pair(self.colors.id('upload_rate'))                             + curses.A_REVERSE + curses.A_BOLD) @@ -2533,10 +2532,10 @@ class Interface:          win.keypad(True)          if important: -            win.bkgd(' ', curses.color_pair(self.colors.get_id('dialog_important')) +            win.bkgd(' ', curses.color_pair(self.colors.id('dialog_important'))                            + curses.A_REVERSE) -        focus_tags   = curses.color_pair(self.colors.get_id('button_focused')) +        focus_tags   = curses.color_pair(self.colors.id('button_focused'))          unfocus_tags = 0          input = False @@ -2587,8 +2586,8 @@ class Interface:              displaytext = input[textwidth*page:textwidth*(page + 1)]              displayindex = index - textwidth*page -            color = (curses.color_pair(self.colors.get_id('dialog_important')) if self.highlight_dialog -                     else curses.color_pair(self.colors.get_id('dialog'))) +            color = (curses.color_pair(self.colors.id('dialog_important')) if self.highlight_dialog +                     else curses.color_pair(self.colors.id('dialog')))              win.addstr(height - 2, 2, displaytext.ljust(textwidth), color)              win.move(height - 2, displayindex + 2)              c = win.getch() @@ -2664,7 +2663,7 @@ class Interface:                  win.addstr(height-4, 2, "leave empty for default")          while True: -            win.addstr(height-2, 2, input.ljust(width-4), curses.color_pair(self.colors.get_id('dialog'))) +            win.addstr(height-2, 2, input.ljust(width-4), curses.color_pair(self.colors.id('dialog')))              win.move(height - 2, len(input) + 2)              c = win.getch()              if c == 27 or c == ord('q') or c == curses.KEY_BREAK: @@ -2752,7 +2751,7 @@ class Interface:          i = 1          for option in options:              title = option[1].split('_') -            if i == focus: tag = curses.color_pair(self.colors.get_id('dialog')) +            if i == focus: tag = curses.color_pair(self.colors.id('dialog'))              else:          tag = 0              win.addstr(i,2, title[0], tag)              win.addstr(title[1][0], tag + curses.A_UNDERLINE) | 
