def _enter_dir(self): # Enter currently selected directory dirname = self.raw_rows[self.cursel][0] new_dir = self.path_stack_pos >= len(self.path_stack) new_dir = new_dir or (dirname != self.path_stack[self.path_stack_pos]) if new_dir: self.path_stack = self.path_stack[: self.path_stack_pos] self.path_stack.append(dirname) path = os.path.join(*self.path_stack[: self.path_stack_pos + 1]) if not os.access(path, os.R_OK): self.path_stack = self.path_stack[: self.path_stack_pos] self.popup = MessagePopup( self, 'Error', '{!error!}Access denied: %s' % path ) self.__refresh_listing() return self.path_stack_pos += 1 self.view_offset = 0 self.cursel = 0 self.last_mark = -1 self.marked = set() self.__refresh_listing()
def _enter_dir(self): # Enter currently selected directory dirname = self.raw_rows[self.cursel][0] new_dir = self.path_stack_pos >= len(self.path_stack) new_dir = new_dir or (dirname != self.path_stack[self.path_stack_pos]) if new_dir: self.path_stack = self.path_stack[:self.path_stack_pos] self.path_stack.append(dirname) path = os.path.join(*self.path_stack[:self.path_stack_pos + 1]) if not os.access(path, os.R_OK): self.path_stack = self.path_stack[:self.path_stack_pos] self.popup = MessagePopup(self, 'Error', '{!error!}Access denied: %s' % path) self.__refresh_listing() return self.path_stack_pos += 1 self.view_offset = 0 self.cursel = 0 self.last_mark = -1 self.marked = set() self.__refresh_listing()
def handle_read(self, c): if c == ord('h'): popup = MessagePopup(self.torrentlist, 'Help', COLUMN_VIEW_HELP_STR, width_req=70, border_off_west=1) self.torrentlist.push_popup(popup) return util.ReadState.READ return InputPopup.handle_read(self, c)
def start(self): self.torrentview.on_config_changed() self.toggle_sidebar() if self.config['first_run']: self.push_popup( MessagePopup(self, 'Welcome to Deluge', HELP_STR, width_req=0.65)) self.config['first_run'] = False self.config.save() if client.connected(): self.torrentview.update(refresh=False)
def handle_read(self, c): if c in [util.KEY_ESC, util.KEY_BELL]: # If Escape key or CTRL-g, we abort self.torrentslist.set_minor_mode(None) elif c == ord('h'): popup = MessagePopup(self.torrentslist, 'Help', QUEUE_MODE_HELP_STR, width_req=0.65, border_off_west=1) self.torrentslist.push_popup(popup, clear=True) elif c in [ curses.KEY_UP, curses.KEY_DOWN, curses.KEY_HOME, curses.KEY_END, curses.KEY_NPAGE, curses.KEY_PPAGE ]: action = key_to_action[c] self.do_queue(action)
def read_input(self): c = self.stdscr.getch() if self.popup: if self.popup.handle_read(c): self.pop_popup() self.refresh() return if util.is_printable_chr(c): char = chr(c) if char == 'Q': component.get('ConsoleUI').quit() elif char == 'h': self.push_popup( MessagePopup(self, 'Preferences Help', HELP_STR)) if self.sidebar.has_focus() and c == util.KEY_ESC: self.back_to_parent() return def update_active_zone(val): self.active_zone += val if self.active_zone == -1: self.active_zone = ZONE.length - 1 else: self.active_zone %= ZONE.length self.sidebar.set_focused(self.active_zone == ZONE.CATEGORIES) if c == util.KEY_TAB: update_active_zone(1) elif c == curses.KEY_BTAB: update_active_zone(-1) else: if self.active_zone == ZONE.CATEGORIES: self.sidebar.handle_read(c) elif self.active_zone == ZONE.PREFRENCES: self.panes[self.cur_cat].handle_read(c) elif self.active_zone == ZONE.ACTIONS: self._actions_read(c) self.refresh()
def refresh(self): if (not component.get('ConsoleUI').is_active_mode(self) or not self.config_loaded): return if self.popup is None and self.messages: title, msg = self.messages.popleft() self.push_popup(MessagePopup(self, title, msg)) self.stdscr.erase() self.draw_statusbars() self._draw_actions() # Necessary to force updating the stdscr self.stdscr.noutrefresh() self.sidebar.refresh() # do this last since it moves the cursor self._draw_preferences() if self.popup: self.popup.refresh() curses.doupdate()
class AddTorrents(BaseMode): def __init__(self, parent_mode, stdscr, console_config, encoding=None): self.console_config = console_config self.parent_mode = parent_mode self.popup = None self.view_offset = 0 self.cursel = 0 self.marked = set() self.last_mark = -1 path = os.path.expanduser(self.console_config['addtorrents']['last_path']) self.path_stack = ['/'] + path.strip('/').split('/') self.path_stack_pos = len(self.path_stack) self.listing_files = [] self.listing_dirs = [] self.raw_rows = [] self.raw_rows_files = [] self.raw_rows_dirs = [] self.formatted_rows = [] self.sort_column = self.console_config['addtorrents']['sort_column'] self.reverse_sort = self.console_config['addtorrents']['reverse_sort'] BaseMode.__init__(self, stdscr, encoding) self._listing_space = self.rows - 5 self.__refresh_listing() util.safe_curs_set(util.Curser.INVISIBLE) self.stdscr.notimeout(0) @overrides(component.Component) def start(self): pass @overrides(component.Component) def update(self): pass def __refresh_listing(self): path = os.path.join(*self.path_stack[:self.path_stack_pos]) listing = os.listdir(path) self.listing_files = [] self.listing_dirs = [] self.raw_rows = [] self.raw_rows_files = [] self.raw_rows_dirs = [] self.formatted_rows = [] for f in listing: if os.path.isdir(os.path.join(path, f)): if self.console_config['addtorrents']['show_hidden_folders']: self.listing_dirs.append(f) elif f[0] != '.': self.listing_dirs.append(f) elif os.path.isfile(os.path.join(path, f)): if self.console_config['addtorrents']['show_misc_files']: self.listing_files.append(f) elif f.endswith('.torrent'): self.listing_files.append(f) for dirname in self.listing_dirs: row = [] full_path = os.path.join(path, dirname) try: size = len(os.listdir(full_path)) except OSError: size = -1 time = os.stat(full_path).st_mtime row = [dirname, size, time, full_path, 1] self.raw_rows.append(row) self.raw_rows_dirs.append(row) # Highlight the directory we came from if self.path_stack_pos < len(self.path_stack): selected = self.path_stack[self.path_stack_pos] ld = sorted(self.listing_dirs, key=lambda n: n.lower()) c = ld.index(selected) self.cursel = c if (self.view_offset + self._listing_space) <= self.cursel: self.view_offset = self.cursel - self._listing_space for filename in self.listing_files: row = [] full_path = os.path.join(path, filename) size = os.stat(full_path).st_size time = os.stat(full_path).st_mtime row = [filename, size, time, full_path, 0] self.raw_rows.append(row) self.raw_rows_files.append(row) self.__sort_rows() def __sort_rows(self): self.console_config['addtorrents']['sort_column'] = self.sort_column self.console_config['addtorrents']['reverse_sort'] = self.reverse_sort self.console_config.save() self.raw_rows_dirs.sort(key=lambda r: r[0].lower()) if self.sort_column == 'name': self.raw_rows_files.sort(key=lambda r: r[0].lower(), reverse=self.reverse_sort) elif self.sort_column == 'date': self.raw_rows_files.sort(key=lambda r: r[2], reverse=self.reverse_sort) self.raw_rows = self.raw_rows_dirs + self.raw_rows_files self.__refresh_rows() def __refresh_rows(self): self.formatted_rows = [] for row in self.raw_rows: filename = deluge.common.decode_bytes(row[0]) size = row[1] time = row[2] if row[4]: if size != -1: size_str = '%i items' % size else: size_str = ' unknown' cols = [filename, size_str, deluge.common.fdate(time)] widths = [self.cols - 35, 12, 23] self.formatted_rows.append(format_utils.format_row(cols, widths)) else: # Size of .torrent file itself couldn't matter less so we'll leave it out cols = [filename, deluge.common.fdate(time)] widths = [self.cols - 23, 23] self.formatted_rows.append(format_utils.format_row(cols, widths)) def scroll_list_up(self, distance): self.cursel -= distance if self.cursel < 0: self.cursel = 0 if self.cursel < self.view_offset + 1: self.view_offset = max(self.cursel - 1, 0) def scroll_list_down(self, distance): self.cursel += distance if self.cursel >= len(self.formatted_rows): self.cursel = len(self.formatted_rows) - 1 if (self.view_offset + self._listing_space) <= self.cursel + 1: self.view_offset = self.cursel - self._listing_space + 1 def set_popup(self, pu): self.popup = pu self.refresh() @overrides(BaseMode) def on_resize(self, rows, cols): BaseMode.on_resize(self, rows, cols) if self.popup: self.popup.handle_resize() self._listing_space = self.rows - 5 self.refresh() def refresh(self, lines=None): if self.mode_paused(): return # Update the status bars self.stdscr.erase() self.draw_statusbars() off = 1 # Render breadcrumbs s = 'Location: ' for i, e in enumerate(self.path_stack): if e == '/': if i == self.path_stack_pos - 1: s += '{!black,red,bold!}root' else: s += '{!red,black,bold!}root' else: if i == self.path_stack_pos - 1: s += '{!black,white,bold!}%s' % e else: s += '{!white,black,bold!}%s' % e if e != len(self.path_stack) - 1: s += '{!white,black!}/' self.add_string(off, s) off += 1 # Render header cols = ['Name', 'Contents', 'Modification time'] widths = [self.cols - 35, 12, 23] s = '' for i, (c, w) in enumerate(zip(cols, widths)): cn = '' if i == 0: cn = 'name' elif i == 2: cn = 'date' if cn == self.sort_column: s += '{!black,green,bold!}' + c.ljust(w - 2) if self.reverse_sort: s += '^ ' else: s += 'v ' else: s += '{!green,black,bold!}' + c.ljust(w) self.add_string(off, s) off += 1 # Render files and folders for i, row in enumerate(self.formatted_rows[self.view_offset:]): i += self.view_offset # It's a folder color_string = '' if self.raw_rows[i][4]: if self.raw_rows[i][1] == -1: if i == self.cursel: color_string = '{!black,red,bold!}' else: color_string = '{!red,black!}' else: if i == self.cursel: color_string = '{!black,cyan,bold!}' else: color_string = '{!cyan,black!}' elif i == self.cursel: if self.raw_rows[i][0] in self.marked: color_string = '{!blue,white,bold!}' else: color_string = '{!black,white,bold!}' elif self.raw_rows[i][0] in self.marked: color_string = '{!white,blue,bold!}' self.add_string(off, color_string + row) off += 1 if off > self.rows - 2: break if not component.get('ConsoleUI').is_active_mode(self): return self.stdscr.noutrefresh() if self.popup: self.popup.refresh() curses.doupdate() def back_to_overview(self): self.parent_mode.go_top = False component.get('ConsoleUI').set_mode(self.parent_mode.mode_name) def _perform_action(self): if self.cursel < len(self.listing_dirs): self._enter_dir() else: s = self.raw_rows[self.cursel][0] if s not in self.marked: self.last_mark = self.cursel self.marked.add(s) self._show_add_dialog() def _enter_dir(self): # Enter currently selected directory dirname = self.raw_rows[self.cursel][0] new_dir = self.path_stack_pos >= len(self.path_stack) new_dir = new_dir or (dirname != self.path_stack[self.path_stack_pos]) if new_dir: self.path_stack = self.path_stack[:self.path_stack_pos] self.path_stack.append(dirname) path = os.path.join(*self.path_stack[:self.path_stack_pos + 1]) if not os.access(path, os.R_OK): self.path_stack = self.path_stack[:self.path_stack_pos] self.popup = MessagePopup(self, 'Error', '{!error!}Access denied: %s' % path) self.__refresh_listing() return self.path_stack_pos += 1 self.view_offset = 0 self.cursel = 0 self.last_mark = -1 self.marked = set() self.__refresh_listing() def _show_add_dialog(self): def _do_add(result, **kwargs): ress = {'succ': 0, 'fail': 0, 'total': len(self.marked), 'fmsg': []} def fail_cb(msg, t_file, ress): log.debug('failed to add torrent: %s: %s', t_file, msg) ress['fail'] += 1 ress['fmsg'].append('{!input!} * %s: {!error!}%s' % (t_file, msg)) if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status(component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg']) def success_cb(tid, t_file, ress): if tid: log.debug('added torrent: %s (%s)', t_file, tid) ress['succ'] += 1 if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status(component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg']) else: fail_cb('Already in session (probably)', t_file, ress) for m in self.marked: filename = m directory = os.path.join(*self.path_stack[:self.path_stack_pos]) path = os.path.join(directory, filename) with open(path, 'rb') as _file: filedump = base64.encodestring(_file.read()) t_options = {} if result['location']['value']: t_options['download_location'] = result['location']['value'] t_options['add_paused'] = result['add_paused']['value'] d = client.core.add_torrent_file(filename, filedump, t_options) d.addCallback(success_cb, filename, ress) d.addErrback(fail_cb, filename, ress) self.console_config['addtorrents']['last_path'] = os.path.join(*self.path_stack[:self.path_stack_pos]) self.console_config.save() self.back_to_overview() config = component.get('ConsoleUI').coreconfig if config['add_paused']: ap = 0 else: ap = 1 self.popup = InputPopup(self, 'Add Torrents (Esc to cancel)', close_cb=_do_add, height_req=17) msg = 'Adding torrent files:' for i, m in enumerate(self.marked): name = m msg += '\n * {!input!}%s' % name if i == 5: if i < len(self.marked): msg += '\n {!red!}And %i more' % (len(self.marked) - 5) break self.popup.add_text(msg) self.popup.add_spaces(1) self.popup.add_text_input('location', 'Download Folder:', config['download_location'], complete=True) self.popup.add_select_input('add_paused', 'Add Paused:', ['Yes', 'No'], [True, False], ap) def _go_up(self): # Go up in directory hierarchy if self.path_stack_pos > 1: self.path_stack_pos -= 1 self.view_offset = 0 self.cursel = 0 self.last_mark = -1 self.marked = set() self.__refresh_listing() def read_input(self): c = self.stdscr.getch() if self.popup: if self.popup.handle_read(c): self.popup = None self.refresh() return if util.is_printable_chr(c): if chr(c) == 'Q': component.get('ConsoleUI').quit() elif chr(c) == 'q': self.back_to_overview() return # Navigate the torrent list if c == curses.KEY_UP: self.scroll_list_up(1) elif c == curses.KEY_PPAGE: self.scroll_list_up(self.rows // 2) elif c == curses.KEY_HOME: self.scroll_list_up(len(self.formatted_rows)) elif c == curses.KEY_DOWN: self.scroll_list_down(1) elif c == curses.KEY_NPAGE: self.scroll_list_down(self.rows // 2) elif c == curses.KEY_END: self.scroll_list_down(len(self.formatted_rows)) elif c == curses.KEY_RIGHT: if self.cursel < len(self.listing_dirs): self._enter_dir() elif c == curses.KEY_LEFT: self._go_up() elif c in [curses.KEY_ENTER, util.KEY_ENTER2]: self._perform_action() elif c == util.KEY_ESC: self.back_to_overview() else: if util.is_printable_chr(c): if chr(c) == 'h': self.popup = MessagePopup(self, 'Help', HELP_STR, width_req=0.75) elif chr(c) == '>': if self.sort_column == 'date': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'date' self.reverse_sort = True self.__sort_rows() elif chr(c) == '<': if self.sort_column == 'name': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'name' self.reverse_sort = False self.__sort_rows() elif chr(c) == 'm': s = self.raw_rows[self.cursel][0] if s in self.marked: self.marked.remove(s) else: self.marked.add(s) self.last_mark = self.cursel elif chr(c) == 'j': self.scroll_list_up(1) elif chr(c) == 'k': self.scroll_list_down(1) elif chr(c) == 'M': if self.last_mark != -1: if self.last_mark > self.cursel: m = list(range(self.cursel, self.last_mark)) else: m = list(range(self.last_mark, self.cursel + 1)) for i in m: s = self.raw_rows[i][0] self.marked.add(s) elif chr(c) == 'c': self.marked.clear() self.refresh()
def read_input(self): c = self.stdscr.getch() if self.popup: if self.popup.handle_read(c): self.popup = None self.refresh() return if util.is_printable_chr(c): if chr(c) == 'Q': component.get('ConsoleUI').quit() elif chr(c) == 'q': self.back_to_overview() return # Navigate the torrent list if c == curses.KEY_UP: self.scroll_list_up(1) elif c == curses.KEY_PPAGE: self.scroll_list_up(self.rows // 2) elif c == curses.KEY_HOME: self.scroll_list_up(len(self.formatted_rows)) elif c == curses.KEY_DOWN: self.scroll_list_down(1) elif c == curses.KEY_NPAGE: self.scroll_list_down(self.rows // 2) elif c == curses.KEY_END: self.scroll_list_down(len(self.formatted_rows)) elif c == curses.KEY_RIGHT: if self.cursel < len(self.listing_dirs): self._enter_dir() elif c == curses.KEY_LEFT: self._go_up() elif c in [curses.KEY_ENTER, util.KEY_ENTER2]: self._perform_action() elif c == util.KEY_ESC: self.back_to_overview() else: if util.is_printable_chr(c): if chr(c) == 'h': self.popup = MessagePopup(self, 'Help', HELP_STR, width_req=0.75) elif chr(c) == '>': if self.sort_column == 'date': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'date' self.reverse_sort = True self.__sort_rows() elif chr(c) == '<': if self.sort_column == 'name': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'name' self.reverse_sort = False self.__sort_rows() elif chr(c) == 'm': s = self.raw_rows[self.cursel][0] if s in self.marked: self.marked.remove(s) else: self.marked.add(s) self.last_mark = self.cursel elif chr(c) == 'j': self.scroll_list_up(1) elif chr(c) == 'k': self.scroll_list_down(1) elif chr(c) == 'M': if self.last_mark != -1: if self.last_mark > self.cursel: m = list(range(self.cursel, self.last_mark)) else: m = list(range(self.last_mark, self.cursel + 1)) for i in m: s = self.raw_rows[i][0] self.marked.add(s) elif chr(c) == 'c': self.marked.clear() self.refresh()
def _show_add_dialog(self): def _do_add(result, **kwargs): ress = {'succ': 0, 'fail': 0, 'total': len(self.marked), 'fmsg': []} def fail_cb(msg, t_file, ress): log.debug('failed to add torrent: %s: %s', t_file, msg) ress['fail'] += 1 ress['fmsg'].append('{!input!} * %s: {!error!}%s' % (t_file, msg)) if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status(component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg']) def success_cb(tid, t_file, ress): if tid: log.debug('added torrent: %s (%s)', t_file, tid) ress['succ'] += 1 if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status(component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg']) else: fail_cb('Already in session (probably)', t_file, ress) for m in self.marked: filename = m directory = os.path.join(*self.path_stack[:self.path_stack_pos]) path = os.path.join(directory, filename) with open(path, 'rb') as _file: filedump = base64.encodestring(_file.read()) t_options = {} if result['location']['value']: t_options['download_location'] = result['location']['value'] t_options['add_paused'] = result['add_paused']['value'] d = client.core.add_torrent_file(filename, filedump, t_options) d.addCallback(success_cb, filename, ress) d.addErrback(fail_cb, filename, ress) self.console_config['addtorrents']['last_path'] = os.path.join(*self.path_stack[:self.path_stack_pos]) self.console_config.save() self.back_to_overview() config = component.get('ConsoleUI').coreconfig if config['add_paused']: ap = 0 else: ap = 1 self.popup = InputPopup(self, 'Add Torrents (Esc to cancel)', close_cb=_do_add, height_req=17) msg = 'Adding torrent files:' for i, m in enumerate(self.marked): name = m msg += '\n * {!input!}%s' % name if i == 5: if i < len(self.marked): msg += '\n {!red!}And %i more' % (len(self.marked) - 5) break self.popup.add_text(msg) self.popup.add_spaces(1) self.popup.add_text_input('location', 'Download Folder:', config['download_location'], complete=True) self.popup.add_select_input('add_paused', 'Add Paused:', ['Yes', 'No'], [True, False], ap)
def read_input(self): # Read the character affected_lines = None c = self.stdscr.getch() # Either ESC or ALT+<some key> if c == util.KEY_ESC: n = self.stdscr.getch() if n == -1: # Means it was the escape key pass else: # ALT+<some key> c = [c, n] if self.popup: ret = self.popup.handle_read(c) if self.popup and self.popup.closed(): self.pop_popup() self.refresh() return ret if util.is_printable_chr(c): if chr(c) == 'Q': component.get('ConsoleUI').quit() elif chr(c) == 'C': self.consoleui.set_mode('ConnectionManager') return elif chr(c) == 'q': self.torrentview.update_marked(self.torrentview.cursel) self.set_minor_mode( QueueMode(self, self.torrentview._selected_torrent_ids())) return elif chr(c) == '/': self.set_minor_mode(SearchMode(self)) return if self.sidebar.has_focus() and c not in [curses.KEY_RIGHT]: self.sidebar.handle_read(c) self.refresh() return if self.torrentview.numtorrents < 0: return elif self.minor_mode: self.minor_mode.handle_read(c) return affected_lines = None # Hand off to torrentview if self.torrentview.handle_read(c) == util.ReadState.CHANGED: affected_lines = self.torrentview.get_input_result() if c == curses.KEY_LEFT: if not self.sidebar.has_focus(): self.sidebar.set_focused(True) self.refresh() return elif c == curses.KEY_RIGHT: if self.sidebar.has_focus(): self.sidebar.set_focused(False) self.refresh() return # We enter a new mode for the selected torrent here tid = self.torrentview.current_torrent_id() if tid: self.show_torrent_details(tid) return elif util.is_printable_chr(c): if chr(c) == 'a': show_torrent_add_popup(self) elif chr(c) == 'v': self._show_visible_columns_popup() elif chr(c) == 'h': self.push_popup( MessagePopup(self, 'Help', HELP_STR, width_req=0.65)) elif chr(c) == 'p': mode = self.consoleui.set_mode('Preferences') mode.load_config() return elif chr(c) == 'e': self.consoleui.set_mode('EventView') return elif chr(c) == 'S': self.config['torrentview']['show_sidebar'] = ( self.config['torrentview']['show_sidebar'] is False) self.config.save() self.toggle_sidebar() elif chr(c) == 'l': self.consoleui.set_mode('CmdLine', refresh=True) return self.refresh(affected_lines)
class AddTorrents(BaseMode): def __init__(self, parent_mode, stdscr, console_config, encoding=None): self.console_config = console_config self.parent_mode = parent_mode self.popup = None self.view_offset = 0 self.cursel = 0 self.marked = set() self.last_mark = -1 path = os.path.expanduser(self.console_config['addtorrents']['last_path']) self.path_stack = ['/'] + path.strip('/').split('/') self.path_stack_pos = len(self.path_stack) self.listing_files = [] self.listing_dirs = [] self.raw_rows = [] self.raw_rows_files = [] self.raw_rows_dirs = [] self.formatted_rows = [] self.sort_column = self.console_config['addtorrents']['sort_column'] self.reverse_sort = self.console_config['addtorrents']['reverse_sort'] BaseMode.__init__(self, stdscr, encoding) self._listing_space = self.rows - 5 self.__refresh_listing() util.safe_curs_set(util.Curser.INVISIBLE) self.stdscr.notimeout(0) @overrides(component.Component) def start(self): pass @overrides(component.Component) def update(self): pass def __refresh_listing(self): path = os.path.join(*self.path_stack[: self.path_stack_pos]) listing = os.listdir(path) self.listing_files = [] self.listing_dirs = [] self.raw_rows = [] self.raw_rows_files = [] self.raw_rows_dirs = [] self.formatted_rows = [] for f in listing: if os.path.isdir(os.path.join(path, f)): if self.console_config['addtorrents']['show_hidden_folders']: self.listing_dirs.append(f) elif f[0] != '.': self.listing_dirs.append(f) elif os.path.isfile(os.path.join(path, f)): if self.console_config['addtorrents']['show_misc_files']: self.listing_files.append(f) elif f.endswith('.torrent'): self.listing_files.append(f) for dirname in self.listing_dirs: row = [] full_path = os.path.join(path, dirname) try: size = len(os.listdir(full_path)) except OSError: size = -1 time = os.stat(full_path).st_mtime row = [dirname, size, time, full_path, 1] self.raw_rows.append(row) self.raw_rows_dirs.append(row) # Highlight the directory we came from if self.path_stack_pos < len(self.path_stack): selected = self.path_stack[self.path_stack_pos] ld = sorted(self.listing_dirs, key=lambda n: n.lower()) c = ld.index(selected) self.cursel = c if (self.view_offset + self._listing_space) <= self.cursel: self.view_offset = self.cursel - self._listing_space for filename in self.listing_files: row = [] full_path = os.path.join(path, filename) size = os.stat(full_path).st_size time = os.stat(full_path).st_mtime row = [filename, size, time, full_path, 0] self.raw_rows.append(row) self.raw_rows_files.append(row) self.__sort_rows() def __sort_rows(self): self.console_config['addtorrents']['sort_column'] = self.sort_column self.console_config['addtorrents']['reverse_sort'] = self.reverse_sort self.console_config.save() self.raw_rows_dirs.sort(key=lambda r: r[0].lower()) if self.sort_column == 'name': self.raw_rows_files.sort( key=lambda r: r[0].lower(), reverse=self.reverse_sort ) elif self.sort_column == 'date': self.raw_rows_files.sort(key=lambda r: r[2], reverse=self.reverse_sort) self.raw_rows = self.raw_rows_dirs + self.raw_rows_files self.__refresh_rows() def __refresh_rows(self): self.formatted_rows = [] for row in self.raw_rows: filename = deluge.common.decode_bytes(row[0]) size = row[1] time = row[2] if row[4]: if size != -1: size_str = '%i items' % size else: size_str = ' unknown' cols = [filename, size_str, deluge.common.fdate(time)] widths = [self.cols - 35, 12, 23] self.formatted_rows.append(format_utils.format_row(cols, widths)) else: # Size of .torrent file itself couldn't matter less so we'll leave it out cols = [filename, deluge.common.fdate(time)] widths = [self.cols - 23, 23] self.formatted_rows.append(format_utils.format_row(cols, widths)) def scroll_list_up(self, distance): self.cursel -= distance if self.cursel < 0: self.cursel = 0 if self.cursel < self.view_offset + 1: self.view_offset = max(self.cursel - 1, 0) def scroll_list_down(self, distance): self.cursel += distance if self.cursel >= len(self.formatted_rows): self.cursel = len(self.formatted_rows) - 1 if (self.view_offset + self._listing_space) <= self.cursel + 1: self.view_offset = self.cursel - self._listing_space + 1 def set_popup(self, pu): self.popup = pu self.refresh() @overrides(BaseMode) def on_resize(self, rows, cols): BaseMode.on_resize(self, rows, cols) if self.popup: self.popup.handle_resize() self._listing_space = self.rows - 5 self.refresh() def refresh(self, lines=None): if self.mode_paused(): return # Update the status bars self.stdscr.erase() self.draw_statusbars() off = 1 # Render breadcrumbs s = 'Location: ' for i, e in enumerate(self.path_stack): if e == '/': if i == self.path_stack_pos - 1: s += '{!black,red,bold!}root' else: s += '{!red,black,bold!}root' else: if i == self.path_stack_pos - 1: s += '{!black,white,bold!}%s' % e else: s += '{!white,black,bold!}%s' % e if e != len(self.path_stack) - 1: s += '{!white,black!}/' self.add_string(off, s) off += 1 # Render header cols = ['Name', 'Contents', 'Modification time'] widths = [self.cols - 35, 12, 23] s = '' for i, (c, w) in enumerate(zip(cols, widths)): cn = '' if i == 0: cn = 'name' elif i == 2: cn = 'date' if cn == self.sort_column: s += '{!black,green,bold!}' + c.ljust(w - 2) if self.reverse_sort: s += '^ ' else: s += 'v ' else: s += '{!green,black,bold!}' + c.ljust(w) self.add_string(off, s) off += 1 # Render files and folders for i, row in enumerate(self.formatted_rows[self.view_offset :]): i += self.view_offset # It's a folder color_string = '' if self.raw_rows[i][4]: if self.raw_rows[i][1] == -1: if i == self.cursel: color_string = '{!black,red,bold!}' else: color_string = '{!red,black!}' else: if i == self.cursel: color_string = '{!black,cyan,bold!}' else: color_string = '{!cyan,black!}' elif i == self.cursel: if self.raw_rows[i][0] in self.marked: color_string = '{!blue,white,bold!}' else: color_string = '{!black,white,bold!}' elif self.raw_rows[i][0] in self.marked: color_string = '{!white,blue,bold!}' self.add_string(off, color_string + row) off += 1 if off > self.rows - 2: break if not component.get('ConsoleUI').is_active_mode(self): return self.stdscr.noutrefresh() if self.popup: self.popup.refresh() curses.doupdate() def back_to_overview(self): self.parent_mode.go_top = False component.get('ConsoleUI').set_mode(self.parent_mode.mode_name) def _perform_action(self): if self.cursel < len(self.listing_dirs): self._enter_dir() else: s = self.raw_rows[self.cursel][0] if s not in self.marked: self.last_mark = self.cursel self.marked.add(s) self._show_add_dialog() def _enter_dir(self): # Enter currently selected directory dirname = self.raw_rows[self.cursel][0] new_dir = self.path_stack_pos >= len(self.path_stack) new_dir = new_dir or (dirname != self.path_stack[self.path_stack_pos]) if new_dir: self.path_stack = self.path_stack[: self.path_stack_pos] self.path_stack.append(dirname) path = os.path.join(*self.path_stack[: self.path_stack_pos + 1]) if not os.access(path, os.R_OK): self.path_stack = self.path_stack[: self.path_stack_pos] self.popup = MessagePopup( self, 'Error', '{!error!}Access denied: %s' % path ) self.__refresh_listing() return self.path_stack_pos += 1 self.view_offset = 0 self.cursel = 0 self.last_mark = -1 self.marked = set() self.__refresh_listing() def _show_add_dialog(self): def _do_add(result, **kwargs): ress = {'succ': 0, 'fail': 0, 'total': len(self.marked), 'fmsg': []} def fail_cb(msg, t_file, ress): log.debug('failed to add torrent: %s: %s', t_file, msg) ress['fail'] += 1 ress['fmsg'].append('{!input!} * %s: {!error!}%s' % (t_file, msg)) if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status( component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg'], ) def success_cb(tid, t_file, ress): if tid: log.debug('added torrent: %s (%s)', t_file, tid) ress['succ'] += 1 if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status( component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg'], ) else: fail_cb('Already in session (probably)', t_file, ress) for m in self.marked: filename = m directory = os.path.join(*self.path_stack[: self.path_stack_pos]) path = os.path.join(directory, filename) with open(path, 'rb') as _file: filedump = b64encode(_file.read()) t_options = {} if result['location']['value']: t_options['download_location'] = result['location']['value'] t_options['add_paused'] = result['add_paused']['value'] d = client.core.add_torrent_file_async(filename, filedump, t_options) d.addCallback(success_cb, filename, ress) d.addErrback(fail_cb, filename, ress) self.console_config['addtorrents']['last_path'] = os.path.join( *self.path_stack[: self.path_stack_pos] ) self.console_config.save() self.back_to_overview() config = component.get('ConsoleUI').coreconfig if config['add_paused']: ap = 0 else: ap = 1 self.popup = InputPopup( self, 'Add Torrents (Esc to cancel)', close_cb=_do_add, height_req=17 ) msg = 'Adding torrent files:' for i, m in enumerate(self.marked): name = m msg += '\n * {!input!}%s' % name if i == 5: if i < len(self.marked): msg += '\n {!red!}And %i more' % (len(self.marked) - 5) break self.popup.add_text(msg) self.popup.add_spaces(1) self.popup.add_text_input( 'location', 'Download Folder:', config['download_location'], complete=True ) self.popup.add_select_input( 'add_paused', 'Add Paused:', ['Yes', 'No'], [True, False], ap ) def _go_up(self): # Go up in directory hierarchy if self.path_stack_pos > 1: self.path_stack_pos -= 1 self.view_offset = 0 self.cursel = 0 self.last_mark = -1 self.marked = set() self.__refresh_listing() def read_input(self): c = self.stdscr.getch() if self.popup: if self.popup.handle_read(c): self.popup = None self.refresh() return if util.is_printable_chr(c): if chr(c) == 'Q': component.get('ConsoleUI').quit() elif chr(c) == 'q': self.back_to_overview() return # Navigate the torrent list if c == curses.KEY_UP: self.scroll_list_up(1) elif c == curses.KEY_PPAGE: self.scroll_list_up(self.rows // 2) elif c == curses.KEY_HOME: self.scroll_list_up(len(self.formatted_rows)) elif c == curses.KEY_DOWN: self.scroll_list_down(1) elif c == curses.KEY_NPAGE: self.scroll_list_down(self.rows // 2) elif c == curses.KEY_END: self.scroll_list_down(len(self.formatted_rows)) elif c == curses.KEY_RIGHT: if self.cursel < len(self.listing_dirs): self._enter_dir() elif c == curses.KEY_LEFT: self._go_up() elif c in [curses.KEY_ENTER, util.KEY_ENTER2]: self._perform_action() elif c == util.KEY_ESC: self.back_to_overview() else: if util.is_printable_chr(c): if chr(c) == 'h': self.popup = MessagePopup(self, 'Help', HELP_STR, width_req=0.75) elif chr(c) == '>': if self.sort_column == 'date': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'date' self.reverse_sort = True self.__sort_rows() elif chr(c) == '<': if self.sort_column == 'name': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'name' self.reverse_sort = False self.__sort_rows() elif chr(c) == 'm': s = self.raw_rows[self.cursel][0] if s in self.marked: self.marked.remove(s) else: self.marked.add(s) self.last_mark = self.cursel elif chr(c) == 'j': self.scroll_list_up(1) elif chr(c) == 'k': self.scroll_list_down(1) elif chr(c) == 'M': if self.last_mark != -1: if self.last_mark > self.cursel: m = list(range(self.cursel, self.last_mark)) else: m = list(range(self.last_mark, self.cursel + 1)) for i in m: s = self.raw_rows[i][0] self.marked.add(s) elif chr(c) == 'c': self.marked.clear() self.refresh()
def read_input(self): c = self.stdscr.getch() if self.popup: if self.popup.handle_read(c): self.popup = None self.refresh() return if util.is_printable_chr(c): if chr(c) == 'Q': component.get('ConsoleUI').quit() elif chr(c) == 'q': self.back_to_overview() return # Navigate the torrent list if c == curses.KEY_UP: self.scroll_list_up(1) elif c == curses.KEY_PPAGE: self.scroll_list_up(self.rows // 2) elif c == curses.KEY_HOME: self.scroll_list_up(len(self.formatted_rows)) elif c == curses.KEY_DOWN: self.scroll_list_down(1) elif c == curses.KEY_NPAGE: self.scroll_list_down(self.rows // 2) elif c == curses.KEY_END: self.scroll_list_down(len(self.formatted_rows)) elif c == curses.KEY_RIGHT: if self.cursel < len(self.listing_dirs): self._enter_dir() elif c == curses.KEY_LEFT: self._go_up() elif c in [curses.KEY_ENTER, util.KEY_ENTER2]: self._perform_action() elif c == util.KEY_ESC: self.back_to_overview() else: if util.is_printable_chr(c): if chr(c) == 'h': self.popup = MessagePopup(self, 'Help', HELP_STR, width_req=0.75) elif chr(c) == '>': if self.sort_column == 'date': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'date' self.reverse_sort = True self.__sort_rows() elif chr(c) == '<': if self.sort_column == 'name': self.reverse_sort = not self.reverse_sort else: self.sort_column = 'name' self.reverse_sort = False self.__sort_rows() elif chr(c) == 'm': s = self.raw_rows[self.cursel][0] if s in self.marked: self.marked.remove(s) else: self.marked.add(s) self.last_mark = self.cursel elif chr(c) == 'j': self.scroll_list_up(1) elif chr(c) == 'k': self.scroll_list_down(1) elif chr(c) == 'M': if self.last_mark != -1: if self.last_mark > self.cursel: m = list(range(self.cursel, self.last_mark)) else: m = list(range(self.last_mark, self.cursel + 1)) for i in m: s = self.raw_rows[i][0] self.marked.add(s) elif chr(c) == 'c': self.marked.clear() self.refresh()
def _show_add_dialog(self): def _do_add(result, **kwargs): ress = {'succ': 0, 'fail': 0, 'total': len(self.marked), 'fmsg': []} def fail_cb(msg, t_file, ress): log.debug('failed to add torrent: %s: %s', t_file, msg) ress['fail'] += 1 ress['fmsg'].append('{!input!} * %s: {!error!}%s' % (t_file, msg)) if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status( component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg'], ) def success_cb(tid, t_file, ress): if tid: log.debug('added torrent: %s (%s)', t_file, tid) ress['succ'] += 1 if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status( component.get('TorrentList'), ress['succ'], ress['fail'], ress['fmsg'], ) else: fail_cb('Already in session (probably)', t_file, ress) for m in self.marked: filename = m directory = os.path.join(*self.path_stack[: self.path_stack_pos]) path = os.path.join(directory, filename) with open(path, 'rb') as _file: filedump = b64encode(_file.read()) t_options = {} if result['location']['value']: t_options['download_location'] = result['location']['value'] t_options['add_paused'] = result['add_paused']['value'] d = client.core.add_torrent_file_async(filename, filedump, t_options) d.addCallback(success_cb, filename, ress) d.addErrback(fail_cb, filename, ress) self.console_config['addtorrents']['last_path'] = os.path.join( *self.path_stack[: self.path_stack_pos] ) self.console_config.save() self.back_to_overview() config = component.get('ConsoleUI').coreconfig if config['add_paused']: ap = 0 else: ap = 1 self.popup = InputPopup( self, 'Add Torrents (Esc to cancel)', close_cb=_do_add, height_req=17 ) msg = 'Adding torrent files:' for i, m in enumerate(self.marked): name = m msg += '\n * {!input!}%s' % name if i == 5: if i < len(self.marked): msg += '\n {!red!}And %i more' % (len(self.marked) - 5) break self.popup.add_text(msg) self.popup.add_spaces(1) self.popup.add_text_input( 'location', 'Download Folder:', config['download_location'], complete=True ) self.popup.add_select_input( 'add_paused', 'Add Paused:', ['Yes', 'No'], [True, False], ap )
def action_torrent_info(mode=None, torrent_ids=None, **kwargs): popup = MessagePopup(mode, 'Torrent options', 'Querying core, please wait...') mode.push_popup(popup) torrents = torrent_ids options = {} def _do_set_torrent_options(torrent_ids, result): options = {} for opt, val in result.items(): if val['value'] not in ['multiple', None]: options[opt] = val['value'] client.core.set_torrent_options(torrent_ids, options) def on_torrent_status(status): for key in status: if key not in options: options[key] = status[key] elif options[key] != status[key]: options[key] = 'multiple' def create_popup(status): mode.pop_popup() def cb(result, **kwargs): if result is None: return _do_set_torrent_options(torrent_ids, result) if kwargs.get('close', False): mode.pop_popup() return True option_popup = InputPopup(mode, ' Set Torrent Options ', close_cb=cb, border_off_west=1, border_off_north=1, base_popup=kwargs.get('base_popup', None)) for field in torrent_options: caption = '{!info!}' + TORRENT_DATA_FIELD[field]['name'] value = options[field] if isinstance(value, ''.__class__): option_popup.add_text_input(field, caption, value) elif isinstance(value, bool): choices = (['Yes', 'No'], [True, False], [True, False].index(value)) option_popup.add_select_input(field, caption, choices[0], choices[1], choices[2]) elif isinstance(value, float): option_popup.add_float_spin_input(field, caption, value=value, min_val=-1) elif isinstance(value, int): option_popup.add_int_spin_input(field, caption, value=value, min_val=-1) mode.push_popup(option_popup) callbacks = [] for tid in torrents: deferred = component.get('SessionProxy').get_torrent_status( tid, torrent_options) callbacks.append(deferred.addCallback(on_torrent_status)) callbacks = defer.DeferredList(callbacks) callbacks.addCallback(create_popup)
def read_input(self): c = self.stdscr.getch() if self.popup: ret = self.popup.handle_read(c) if ret != util.ReadState.IGNORED and self.popup.closed(): self.pop_popup() self.refresh() return if c in [util.KEY_ESC, curses.KEY_LEFT, ord('q')]: self.back_to_overview() return util.ReadState.READ if not self.torrent_state: # actions below only make sense if there is a torrent state return # Navigate the torrent list if c == curses.KEY_UP: self.file_list_up() elif c == curses.KEY_PPAGE: self.file_list_up(self._listing_space - 2) elif c == curses.KEY_HOME: self.file_off = 0 self.current_file_idx = 0 elif c == curses.KEY_DOWN: self.file_list_down() elif c == curses.KEY_NPAGE: self.file_list_down(self._listing_space - 2) elif c == curses.KEY_END: self.current_file_idx = self.__get_file_list_length() - 1 self.file_off = self.current_file_idx - (self._listing_space - 3) elif c == curses.KEY_DC: torrent_actions_popup(self, [self.torrentid], action=ACTION.REMOVE) elif c in [curses.KEY_ENTER, util.KEY_ENTER2]: was_empty = self.marked == {} self.__mark_tree(self.file_list, self.current_file[1]) self.show_priority_popup(was_empty) elif c == util.KEY_SPACE: self.expcol_cur_file() elif c == ord('m'): if self.current_file: self.__mark_unmark(self.current_file[1]) elif c == ord('r'): self._show_rename_popup() elif c == ord('c'): self.marked = {} elif c == ord('a'): torrent_actions_popup(self, [self.torrentid], details=False) return elif c == ord('o'): torrent_actions_popup(self, [self.torrentid], action=ACTION.TORRENT_OPTIONS) return elif c == ord('h'): self.push_popup( MessagePopup(self, 'Help', HELP_STR, width_req=0.75)) elif c == ord('j'): self.file_list_up() elif c == ord('k'): self.file_list_down() self.refresh()