From 2e2851dc13d73352530dd4495c7e05603b2e520d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 23:38:38 +0200 Subject: Adding upstream version 2.1.2~dev0+20240219. Signed-off-by: Daniel Baumann --- deluge/ui/console/modes/torrentdetail.py | 1021 ++++++++++++++++++++++++++++++ 1 file changed, 1021 insertions(+) create mode 100644 deluge/ui/console/modes/torrentdetail.py (limited to 'deluge/ui/console/modes/torrentdetail.py') diff --git a/deluge/ui/console/modes/torrentdetail.py b/deluge/ui/console/modes/torrentdetail.py new file mode 100644 index 0000000..4383d58 --- /dev/null +++ b/deluge/ui/console/modes/torrentdetail.py @@ -0,0 +1,1021 @@ +# +# Copyright (C) 2011 Nick Lanham +# +# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with +# the additional special exception to link portions of this program with the OpenSSL library. +# See LICENSE for more details. +# + +import logging + +import deluge.component as component +from deluge.common import fsize +from deluge.decorators import overrides +from deluge.ui.client import client +from deluge.ui.common import FILE_PRIORITY +from deluge.ui.console.modes.basemode import BaseMode +from deluge.ui.console.modes.torrentlist.torrentactions import ( + ACTION, + torrent_actions_popup, +) +from deluge.ui.console.utils import colors +from deluge.ui.console.utils import curses_util as util +from deluge.ui.console.utils.column import get_column_value, torrent_data_fields +from deluge.ui.console.utils.format_utils import ( + format_priority, + format_progress, + format_row, +) +from deluge.ui.console.widgets.popup import ( + InputPopup, + MessagePopup, + PopupsHandler, + SelectablePopup, +) + +try: + import curses +except ImportError: + pass + +log = logging.getLogger(__name__) + +# Big help string that gets displayed when the user hits 'h' +HELP_STR = """\ +This screen shows detailed information about a torrent, and also the \ +information about the individual files in the torrent. + +You can navigate the file list with the Up/Down arrows and use space to \ +collapse/expand the file tree. + +All popup windows can be closed/canceled by hitting the Esc key \ +(you might need to wait a second for an Esc to register) + +The actions you can perform and the keys to perform them are as follows: + +{!info!}'h'{!normal!} - Show this help + +{!info!}'a'{!normal!} - Show torrent actions popup. Here you can do things like \ +pause/resume, recheck, set torrent options and so on. + +{!info!}'r'{!normal!} - Rename currently highlighted folder or a file. You can't \ +rename multiple files at once so you need to first clear your selection \ +with {!info!}'c'{!normal!} + +{!info!}'m'{!normal!} - Mark or unmark a file or a folder +{!info!}'c'{!normal!} - Un-mark all files + +{!info!}Space{!normal!} - Expand/Collapse currently selected folder + +{!info!}Enter{!normal!} - Show priority popup in which you can set the \ +download priority of selected files and folders. + +{!info!}Left Arrow{!normal!} - Go back to torrent overview. +""" + + +class TorrentDetail(BaseMode, PopupsHandler): + def __init__(self, parent_mode, stdscr, console_config, encoding=None): + PopupsHandler.__init__(self) + self.console_config = console_config + self.parent_mode = parent_mode + self.torrentid = None + self.torrent_state = None + self._status_keys = [ + 'files', + 'name', + 'state', + 'download_payload_rate', + 'upload_payload_rate', + 'progress', + 'eta', + 'all_time_download', + 'total_uploaded', + 'ratio', + 'num_seeds', + 'total_seeds', + 'num_peers', + 'total_peers', + 'active_time', + 'seeding_time', + 'time_added', + 'distributed_copies', + 'num_pieces', + 'piece_length', + 'download_location', + 'file_progress', + 'file_priorities', + 'message', + 'total_wanted', + 'tracker_host', + 'owner', + 'seed_rank', + 'last_seen_complete', + 'completed_time', + 'time_since_transfer', + 'super_seeding', + ] + self.file_list = None + self.current_file = None + self.current_file_idx = 0 + self.file_off = 0 + self.more_to_draw = False + self.full_names = None + self.column_string = '' + self.files_sep = None + self.marked = {} + + BaseMode.__init__(self, stdscr, encoding) + self.column_names = ['Filename', 'Size', 'Progress', 'Priority'] + self.__update_columns() + + self._listing_start = self.rows // 2 + self._listing_space = self._listing_start - self._listing_start + + client.register_event_handler( + 'TorrentFileRenamedEvent', self._on_torrentfilerenamed_event + ) + client.register_event_handler( + 'TorrentFolderRenamedEvent', self._on_torrentfolderrenamed_event + ) + client.register_event_handler( + 'TorrentRemovedEvent', self._on_torrentremoved_event + ) + + util.safe_curs_set(util.Curser.INVISIBLE) + self.stdscr.notimeout(0) + + def set_torrent_id(self, torrentid): + self.torrentid = torrentid + self.file_list = None + + def back_to_overview(self): + component.get('ConsoleUI').set_mode(self.parent_mode.mode_name) + + @overrides(component.Component) + def start(self): + self.update() + + @overrides(component.Component) + def update(self, torrentid=None): + if torrentid: + self.set_torrent_id(torrentid) + + if self.torrentid: + component.get('SessionProxy').get_torrent_status( + self.torrentid, self._status_keys + ).addCallback(self.set_state) + + @overrides(BaseMode) + def pause(self): + self.set_torrent_id(None) + + @overrides(BaseMode) + def on_resize(self, rows, cols): + BaseMode.on_resize(self, rows, cols) + self.__update_columns() + if self.popup: + self.popup.handle_resize() + + self._listing_start = self.rows // 2 + self.refresh() + + def set_state(self, state): + if state.get('files'): + self.full_names = {x['index']: x['path'] for x in state['files']} + + need_prio_update = False + if not self.file_list: + # don't keep getting the files once we've got them once + if state.get('files'): + self.files_sep = '{!green,black,bold,underline!}%s' % ( + ('Files (torrent has %d files)' % len(state['files'])).center( + self.cols + ) + ) + self.file_list, self.file_dict = self.build_file_list( + state['files'], state['file_progress'], state['file_priorities'] + ) + else: + self.files_sep = '{!green,black,bold,underline!}%s' % ( + ('Files (File list unknown)').center(self.cols) + ) + need_prio_update = True + + self.__fill_progress(self.file_list, state['file_progress']) + + for i, prio in enumerate(state['file_priorities']): + if self.file_dict[i][6] != prio: + need_prio_update = True + self.file_dict[i][6] = prio + if need_prio_update and self.file_list: + self.__fill_prio(self.file_list) + del state['file_progress'] + del state['file_priorities'] + self.torrent_state = state + self.refresh() + + def build_file_list(self, torrent_files, progress, priority): + """Split file list from torrent state into a directory tree. + + Returns: + + Tuple: + A list of lists in the form: + [file/dir_name, index, size, children, expanded, progress, priority] + + Dictionary: + Map of file index for fast updating of progress and priorities. + """ + + file_list = [] + file_dict = {} + # directory index starts from total file count. + dir_idx = len(torrent_files) + for torrent_file in torrent_files: + cur = file_list + paths = torrent_file['path'].split('/') + for path in paths: + if not cur or path != cur[-1][0]: + child_list = [] + if path == paths[-1]: + file_progress = format_progress( + progress[torrent_file['index']] * 100 + ) + entry = [ + path, + torrent_file['index'], + torrent_file['size'], + child_list, + False, + file_progress, + priority[torrent_file['index']], + ] + file_dict[torrent_file['index']] = entry + else: + entry = [path, dir_idx, -1, child_list, False, 0, -1] + file_dict[dir_idx] = entry + dir_idx += 1 + cur.append(entry) + cur = child_list + else: + cur = cur[-1][3] + self.__build_sizes(file_list) + self.__fill_progress(file_list, progress) + + return file_list, file_dict + + # fill in the sizes of the directory entries based on their children + def __build_sizes(self, fs): + ret = 0 + for f in fs: + if f[2] == -1: + val = self.__build_sizes(f[3]) + ret += val + f[2] = val + else: + ret += f[2] + return ret + + # fills in progress fields in all entries based on progs + # returns the # of bytes complete in all the children of fs + def __fill_progress(self, fs, progs): + if not progs: + return 0 + tb = 0 + for f in fs: + if f[3]: # dir, has some children + bd = self.__fill_progress(f[3], progs) + f[5] = format_progress(bd // f[2] * 100) + else: # file, update own prog and add to total + bd = f[2] * progs[f[1]] + f[5] = format_progress(progs[f[1]] * 100) + tb += bd + return tb + + def __fill_prio(self, fs): + for f in fs: + if f[3]: # dir, so fill in children and compute our prio + self.__fill_prio(f[3]) + child_prios = [e[6] for e in f[3]] + if len(child_prios) > 1: + f[6] = -2 # mixed + else: + f[6] = child_prios.pop(0) + + def __update_columns(self): + self.column_widths = [-1, 15, 15, 20] + req = sum(col_width for col_width in self.column_widths if col_width >= 0) + if req > self.cols: # can't satisfy requests, just spread out evenly + cw = self.cols // len(self.column_names) + for i in range(0, len(self.column_widths)): + self.column_widths[i] = cw + else: + rem = self.cols - req + var_cols = len( + [col_width for col_width in self.column_widths if col_width < 0] + ) + vw = rem // var_cols + for i in range(0, len(self.column_widths)): + if self.column_widths[i] < 0: + self.column_widths[i] = vw + + self.column_string = '{!green,black,bold!}%s' % ( + ''.join( + [ + '%s%s' + % ( + self.column_names[i], + ' ' * (self.column_widths[i] - len(self.column_names[i])), + ) + for i in range(0, len(self.column_names)) + ] + ) + ) + + def _on_torrentremoved_event(self, torrent_id): + if torrent_id == self.torrentid: + self.back_to_overview() + + def _on_torrentfilerenamed_event(self, torrent_id, index, new_name): + if torrent_id == self.torrentid: + self.file_dict[index][0] = new_name.split('/')[-1] + component.get('SessionProxy').get_torrent_status( + self.torrentid, self._status_keys + ).addCallback(self.set_state) + + def _on_torrentfolderrenamed_event(self, torrent_id, old_folder, new_folder): + if torrent_id == self.torrentid: + fe = None + fl = None + for i in old_folder.strip('/').split('/'): + if not fl: + fe = fl = self.file_list + s = [files for files in fl if files[0].strip('/') == i][0] + fe = s + fl = s[3] + fe[0] = new_folder.strip('/').rpartition('/')[-1] + + # self.__get_file_by_name(old_folder, self.file_list)[0] = new_folder.strip('/') + component.get('SessionProxy').get_torrent_status( + self.torrentid, self._status_keys + ).addCallback(self.set_state) + + def draw_files(self, files, depth, off, idx): + color_selected = 'blue' + color_partially_selected = 'magenta' + color_highlighted = 'white' + for fl in files: + # from sys import stderr + # print >> stderr, fl[6] + # kick out if we're going to draw too low on the screen + if off >= self.rows - 1: + self.more_to_draw = True + return -1, -1 + + # default color values + fg = 'white' + bg = 'black' + attr = '' + + priority_fg_color = { + -2: 'white', # Mixed + 0: 'red', # Skip + 1: 'yellow', # Low + 2: 'yellow', + 3: 'yellow', + 4: 'white', # Normal + 5: 'green', + 6: 'green', + 7: 'green', # High + } + + fg = priority_fg_color[fl[6]] + + if idx >= self.file_off: + # set fg/bg colors based on whether the file is selected/marked or not + + if fl[1] in self.marked: + bg = color_selected + if fl[3]: + if self.marked[fl[1]] < self.__get_contained_files_count( + file_list=fl[3] + ): + bg = color_partially_selected + attr = 'bold' + + if idx == self.current_file_idx: + self.current_file = fl + bg = color_highlighted + if fl[1] in self.marked: + fg = color_selected + if fl[3]: + if self.marked[fl[1]] < self.__get_contained_files_count( + file_list=fl[3] + ): + fg = color_partially_selected + else: + if fg == 'white': + fg = 'black' + attr = 'bold' + + if attr: + color_string = f'{{!{fg},{bg},{attr}!}}' + else: + color_string = f'{{!{fg},{bg}!}}' + + # actually draw the dir/file string + if fl[3] and fl[4]: # this is an expanded directory + xchar = 'v' + elif fl[3]: # collapsed directory + xchar = '>' + else: # file + xchar = '-' + + r = format_row( + [ + '{}{} {}'.format(' ' * depth, xchar, fl[0]), + fsize(fl[2]), + fl[5], + format_priority(fl[6]), + ], + self.column_widths, + ) + + self.add_string(off, f'{color_string}{r}', trim=False) + off += 1 + + if fl[3] and fl[4]: + # recurse if we have children and are expanded + off, idx = self.draw_files(fl[3], depth + 1, off, idx + 1) + if off < 0: + return (off, idx) + else: + idx += 1 + + return (off, idx) + + def __get_file_list_length(self, file_list=None): + """ + Counts length of the displayed file list. + """ + if file_list is None: + file_list = self.file_list + length = 0 + if file_list: + for element in file_list: + length += 1 + if element[3] and element[4]: + length += self.__get_file_list_length(element[3]) + return length + + def __get_contained_files_count(self, file_list=None, idx=None): + length = 0 + if file_list is None: + file_list = self.file_list + if idx is not None: + for element in file_list: + if element[1] == idx: + return self.__get_contained_files_count(file_list=element[3]) + elif element[3]: + c = self.__get_contained_files_count(file_list=element[3], idx=idx) + if c > 0: + return c + else: + for element in file_list: + length += 1 + if element[3]: + length -= 1 + length += self.__get_contained_files_count(element[3]) + return length + + def render_header(self, row): + status = self.torrent_state + + download_color = '{!info!}' + if status['download_payload_rate'] > 0: + download_color = colors.state_color['Downloading'] + + def add_field(name, row, pre_color='{!info!}', post_color='{!input!}'): + s = '{}{}: {}{}'.format( + pre_color, + torrent_data_fields[name]['name'], + post_color, + get_column_value(name, status), + ) + if row: + row = self.add_string(row, s) + return row + return s + + # Name + row = add_field('name', row) + # State + row = add_field('state', row) + + # Print DL info and ETA + s = add_field('downloaded', 0, download_color) + if status['progress'] != 100.0: + s += '/%s' % fsize(status['total_wanted']) + if status['download_payload_rate'] > 0: + s += ' {{!yellow!}}@ {}{}'.format( + download_color, + fsize(status['download_payload_rate']), + ) + s += add_field('eta', 0) + if s: + row = self.add_string(row, s) + + # Print UL info and ratio + s = add_field('uploaded', 0, download_color) + if status['upload_payload_rate'] > 0: + s += ' {{!yellow!}}@ {}{}'.format( + colors.state_color['Seeding'], + fsize(status['upload_payload_rate']), + ) + s += ' ' + add_field('ratio', 0) + row = self.add_string(row, s) + + # Seed/peer info + s = '{{!info!}}{}:{{!green!}} {} {{!input!}}({})'.format( + torrent_data_fields['seeds']['name'], + status['num_seeds'], + status['total_seeds'], + ) + row = self.add_string(row, s) + s = '{{!info!}}{}:{{!red!}} {} {{!input!}}({})'.format( + torrent_data_fields['peers']['name'], + status['num_peers'], + status['total_peers'], + ) + row = self.add_string(row, s) + + # Tracker + tracker_color = '{!green!}' if status['message'] == 'OK' else '{!red!}' + s = '{{!info!}}{}: {{!magenta!}}{}{{!input!}} says "{}{}{{!input!}}"'.format( + torrent_data_fields['tracker']['name'], + status['tracker_host'], + tracker_color, + status['message'], + ) + row = self.add_string(row, s) + + # Pieces and availability + s = '{{!info!}}{}: {{!yellow!}}{} {{!input!}}x {{!yellow!}}{}'.format( + torrent_data_fields['pieces']['name'], + status['num_pieces'], + fsize(status['piece_length']), + ) + if status['distributed_copies']: + s += '{{!info!}}{}: {{!input!}}{}'.format( + torrent_data_fields['seed_rank']['name'], + status['seed_rank'], + ) + row = self.add_string(row, s) + + # Time added + row = add_field('time_added', row) + # Time active + row = add_field('active_time', row) + if status['seeding_time']: + row = add_field('seeding_time', row) + # Download Folder + row = add_field('download_location', row) + # Seed Rank + row = add_field('seed_rank', row) + # Super Seeding + row = add_field('super_seeding', row) + # Last seen complete + row = add_field('last_seen_complete', row) + # Last activity + row = add_field('time_since_transfer', row) + # Owner + if status['owner']: + row = add_field('owner', row) + return row + # Last act + + @overrides(BaseMode) + def refresh(self, lines=None): + # Update the status bars + self.stdscr.erase() + self.draw_statusbars() + + row = 1 + if self.torrent_state: + row = self.render_header(row) + else: + self.add_string(1, 'Waiting for torrent state') + + row += 1 + + if self.files_sep: + self.add_string(row, self.files_sep) + row += 1 + + self._listing_start = row + self._listing_space = self.rows - self._listing_start + + self.add_string(row, self.column_string) + if self.file_list: + row += 1 + self.more_to_draw = False + self.draw_files(self.file_list, 0, row, 0) + + if not component.get('ConsoleUI').is_active_mode(self): + return + + self.stdscr.noutrefresh() + + if self.popup: + self.popup.refresh() + + curses.doupdate() + + def expcol_cur_file(self): + """ + Expand or collapse current file + """ + self.current_file[4] = not self.current_file[4] + self.refresh() + + def file_list_down(self, rows=1): + maxlen = self.__get_file_list_length() - 1 + + self.current_file_idx += rows + + if self.current_file_idx > maxlen: + self.current_file_idx = maxlen + + if self.current_file_idx > self.file_off + (self._listing_space - 3): + self.file_off = self.current_file_idx - (self._listing_space - 3) + + self.refresh() + + def file_list_up(self, rows=1): + self.current_file_idx = max(0, self.current_file_idx - rows) + self.file_off = min(self.file_off, self.current_file_idx) + self.refresh() + + # build list of priorities for all files in the torrent + # based on what is currently selected and a selected priority. + def build_prio_list(self, files, ret_list, parent_prio, selected_prio): + # has a priority been set on my parent (if so, I inherit it) + for f in files: + # Do not set priorities for the whole dir, just selected contents + if f[3]: + self.build_prio_list(f[3], ret_list, parent_prio, selected_prio) + else: # file, need to add to list + if f[1] in self.marked or parent_prio >= 0: + # selected (or parent selected), use requested priority + ret_list.append((f[1], selected_prio)) + else: + # not selected, just keep old priority + ret_list.append((f[1], f[6])) + + def do_priority(self, priority, was_empty): + plist = [] + self.build_prio_list(self.file_list, plist, -1, priority) + plist.sort() + priorities = [p[1] for p in plist] + client.core.set_torrent_options( + [self.torrentid], {'file_priorities': priorities} + ) + + if was_empty: + self.marked = {} + return True + + # show popup for priority selections + def show_priority_popup(self, was_empty): + def popup_func(name, data, was_empty, **kwargs): + if not name: + return + return self.do_priority(data[name], was_empty) + + if self.marked: + popup = SelectablePopup( + self, + 'Set File Priority', + popup_func, + border_off_north=1, + cb_args={'was_empty': was_empty}, + ) + popup.add_line( + 'skip_priority', + '_Skip', + foreground='red', + cb_arg=FILE_PRIORITY['Skip'], + was_empty=was_empty, + ) + popup.add_line( + 'low_priority', '_Low', cb_arg=FILE_PRIORITY['Low'], foreground='yellow' + ) + popup.add_line('normal_priority', '_Normal', cb_arg=FILE_PRIORITY['Normal']) + popup.add_line( + 'high_priority', + '_High', + cb_arg=FILE_PRIORITY['High'], + foreground='green', + ) + popup._selected = 1 + self.push_popup(popup) + + def __mark_unmark(self, idx): + """ + Selects or unselects file or a catalog(along with contained files) + """ + fc = self.__get_contained_files_count(idx=idx) + if idx not in self.marked: + # Not selected, select it + self.__mark_tree(self.file_list, idx) + elif self.marked[idx] < fc: + # Partially selected, unselect all contents + self.__unmark_tree(self.file_list, idx) + else: + # Selected, unselect it + self.__unmark_tree(self.file_list, idx) + + def __mark_tree(self, file_list, idx, mark_all=False): + """ + Given file_list of TorrentDetail and index of file or folder, + recursively selects all files contained + as well as marks folders higher in hierarchy as partially selected + """ + total_marked = 0 + for element in file_list: + marked = 0 + # Select the file if it's the one we want or + # if it's inside a directory that got selected + if (element[1] == idx) or mark_all: + # If it's a folder then select everything inside + if element[3]: + marked = self.__mark_tree(element[3], idx, True) + self.marked[element[1]] = marked + else: + marked = 1 + self.marked[element[1]] = 1 + else: + # Does not match but the item to be selected might be inside, recurse + if element[3]: + marked = self.__mark_tree(element[3], idx, False) + # Partially select the folder if it contains files that were selected + if marked > 0: + self.marked[element[1]] = marked + else: + if element[1] in self.marked: + # It's not the element we want but it's marked so count it + marked = 1 + # Count and then return total amount of files selected in all subdirectories + total_marked += marked + + return total_marked + + def __get_file_by_num(self, num, file_list, idx=0): + for element in file_list: + if idx == num: + return element + if element[3] and element[4]: + i = self.__get_file_by_num(num, element[3], idx + 1) + if not isinstance(i, int): + return i + idx = i + else: + idx += 1 + return idx + + def __get_file_by_name(self, name, file_list, idx=0): + for element in file_list: + if element[0].strip('/') == name.strip('/'): + return element + if element[3] and element[4]: + i = self.__get_file_by_name(name, element[3], idx + 1) + if not isinstance(i, int): + return i + else: + idx = i + else: + idx += 1 + return idx + + def __unmark_tree(self, file_list, idx, unmark_all=False): + """ + Given file_list of TorrentDetail and index of file or folder, + recursively deselects all files contained + as well as marks folders higher in hierarchy as unselected or partially selected + """ + total_marked = 0 + for element in file_list: + marked = 0 + # It's either the item we want to select or + # a contained item, deselect it + if (element[1] == idx) or unmark_all: + if element[1] in self.marked: + del self.marked[element[1]] + # Deselect all contents if it's a catalog + if element[3]: + self.__unmark_tree(element[3], idx, True) + else: + # Not file we wanted but it might be inside this folder, recurse inside + if element[3]: + marked = self.__unmark_tree(element[3], idx, False) + # If none of the contents remain selected, unselect this folder as well + if marked == 0: + if element[1] in self.marked: + del self.marked[element[1]] + # Otherwise update selection count + else: + self.marked[element[1]] = marked + else: + if element[1] in self.marked: + marked = 1 + + # Count and then return selection count so we can update + # directories higher up in the hierarchy + total_marked += marked + return total_marked + + def _selection_to_file_idx(self, file_list=None, idx=0, true_idx=0, closed=False): + if not file_list: + file_list = self.file_list + + for element in file_list: + if idx == self.current_file_idx: + return true_idx + + # It's a folder + if element[3]: + i = self._selection_to_file_idx( + element[3], idx + 1, true_idx, closed or not element[4] + ) + if isinstance(i, tuple): + idx, true_idx = i + if element[4]: + idx, true_idx = i + else: + idx += 1 + tmp, true_idx = i + else: + return i + else: + if not closed: + idx += 1 + true_idx += 1 + + return (idx, true_idx) + + def _get_full_folder_path(self, num, file_list=None, path='', idx=0): + if not file_list: + file_list = self.file_list + + for element in file_list: + if not element[3]: + idx += 1 + continue + if num == idx: + return f'{path}{element[0]}/' + if element[4]: + i = self._get_full_folder_path( + num, element[3], path + element[0] + '/', idx + 1 + ) + if not isinstance(i, int): + return i + idx = i + else: + idx += 1 + return idx + + def _do_rename_folder(self, torrent_id, folder, new_folder): + client.core.rename_folder(torrent_id, folder, new_folder) + + def _do_rename_file(self, torrent_id, file_idx, new_filename): + if not new_filename: + return + client.core.rename_files(torrent_id, [(file_idx, new_filename)]) + + def _show_rename_popup(self): + # Perhaps in the future: Renaming multiple files + if self.marked: + self.report_message( + 'Error (Enter to close)', + 'Sorry, you cannot rename multiple files, please clear ' + 'selection with {!info!}"c"{!normal!} key', + ) + else: + _file = self.__get_file_by_num(self.current_file_idx, self.file_list) + old_filename = _file[0] + idx = self._selection_to_file_idx() + tid = self.torrentid + + if _file[3]: + + def do_rename(result, **kwargs): + if ( + not result + or not result['new_foldername']['value'] + or kwargs.get('close', False) + ): + self.popup.close(None, call_cb=False) + return + old_fname = self._get_full_folder_path(self.current_file_idx) + new_fname = '{}/{}/'.format( + old_fname.strip('/').rpartition('/')[0], + result['new_foldername']['value'], + ) + self._do_rename_folder(tid, old_fname, new_fname) + + popup = InputPopup( + self, 'Rename folder (Esc to cancel)', close_cb=do_rename + ) + popup.add_text_input( + 'new_foldername', + 'Enter new folder name:', + old_filename.strip('/'), + complete=True, + ) + self.push_popup(popup) + else: + + def do_rename(result, **kwargs): + if ( + not result + or not result['new_filename']['value'] + or kwargs.get('close', False) + ): + self.popup.close(None, call_cb=False) + return + fname = '{}/{}'.format( + self.full_names[idx].rpartition('/')[0], + result['new_filename']['value'], + ) + self._do_rename_file(tid, idx, fname) + + popup = InputPopup(self, ' Rename file ', close_cb=do_rename) + popup.add_text_input( + 'new_filename', 'Enter new filename:', old_filename, complete=True + ) + self.push_popup(popup) + + @overrides(BaseMode) + 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_down() + elif c == ord('k'): + self.file_list_up() + + self.refresh() -- cgit v1.2.3