def __load_config(self): auth_file = deluge.configmanager.get_config_dir("auth") if not os.path.exists(auth_file): from deluge.common import create_localclient_account create_localclient_account() localclient_username, localclient_password = get_localhost_auth() DEFAULT_CONFIG = { "hosts": [(hashlib.sha1(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, localclient_username, localclient_password)] } config = ConfigManager("hostlist.conf.1.2", DEFAULT_CONFIG) config.run_converter((0, 1), 2, self.__migrate_config_1_to_2) return config
def __load_config(self): auth_file = deluge.configmanager.get_config_dir("auth") if not os.path.exists(auth_file): from deluge.common import create_localclient_account create_localclient_account() localclient_username, localclient_password = get_localhost_auth() DEFAULT_CONFIG = { "hosts": [( hashlib.sha1(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, localclient_username, localclient_password )] } config = ConfigManager("hostlist.conf.1.2", DEFAULT_CONFIG) config.run_converter((0, 1), 2, self.__migrate_config_1_to_2) return config
class ConsoleUI(component.Component, TermResizeHandler): def __init__(self, options, cmds, log_stream): component.Component.__init__(self, 'ConsoleUI') TermResizeHandler.__init__(self) self.options = options self.log_stream = log_stream # keep track of events for the log view self.events = [] self.torrents = [] self.statusbars = None self.modes = {} self.active_mode = None self.initialized = False try: locale.setlocale(locale.LC_ALL, '') self.encoding = locale.getpreferredencoding() except locale.Error: self.encoding = sys.getdefaultencoding() log.debug('Using encoding: %s', self.encoding) # start up the session proxy self.sessionproxy = SessionProxy() client.set_disconnect_callback(self.on_client_disconnect) # Set the interactive flag to indicate where we should print the output self.interactive = True self._commands = cmds self.coreconfig = CoreConfig() def start_ui(self): """Start the console UI. Note: When running console UI reactor.run() will be called which effectively blocks this function making the return value insignificant. However, when running unit tests, the reacor is replaced by a mock object, leaving the return deferred object necessary for the tests to run properly. Returns: Deferred: If valid commands are provided, a deferred that fires when all commands are executed. Else None is returned. """ if self.options.parsed_cmds: self.interactive = False if not self._commands: print('No valid console commands found') return deferred = self.exec_args(self.options) reactor.run() return deferred else: # Interactive if deluge.common.windows_check(): print( """\nDeluge-console does not run in interactive mode on Windows. \n Please use commands from the command line, e.g.:\n deluge-console.exe help deluge-console.exe info deluge-console.exe "add --help" deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent" """) else: class ConsoleLog(object): def write(self, data): pass def flush(self): pass # We don't ever want log output to terminal when running in # interactive mode, so insert a dummy here self.log_stream.out = ConsoleLog() # Set Esc key delay to 0 to avoid a very annoying delay # due to curses waiting in case of other key are pressed # after ESC is pressed os.environ.setdefault('ESCDELAY', '0') # We use the curses.wrapper function to prevent the console from getting # messed up if an uncaught exception is experienced. from curses import wrapper wrapper(self.run) def quit(self): if client.connected(): def on_disconnect(result): reactor.stop() return client.disconnect().addCallback(on_disconnect) else: try: reactor.stop() except error.ReactorNotRunning: pass def exec_args(self, options): """Execute console commands from command line.""" from deluge.ui.console.cmdline.command import Commander commander = Commander(self._commands) def on_connect(result): def on_components_started(result): def on_started(result): def do_command(result, cmd): return commander.do_command(cmd) def exec_command(result, cmd): return commander.exec_command(cmd) d = defer.succeed(None) for command in options.parsed_cmds: if command.command in ('quit', 'exit'): break d.addCallback(exec_command, command) d.addCallback(do_command, 'quit') return d # We need to wait for the rpcs in start() to finish before processing # any of the commands. self.started_deferred.addCallback(on_started) return self.started_deferred d = self.start_console() d.addCallback(on_components_started) return d def on_connect_fail(reason): if reason.check(DelugeError): rm = reason.getErrorMessage() else: rm = reason.value.message print('Could not connect to daemon: %s:%s\n %s' % (options.daemon_addr, options.daemon_port, rm)) commander.do_command('quit') d = None if not self.interactive and options.parsed_cmds[0].command == 'connect': d = commander.exec_command(options.parsed_cmds.pop(0)) else: log.info( 'connect: host=%s, port=%s, username=%s, password=%s', options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass, ) d = client.connect( options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass, ) d.addCallback(on_connect) d.addErrback(on_connect_fail) return d def run(self, stdscr): """This method is called by the curses.wrapper to start the mainloop and screen. Args: stdscr (_curses.curses window): curses screen passed in from curses.wrapper. """ # We want to do an interactive session, so start up the curses screen and # pass it the function that handles commands colors.init_colors() self.stdscr = stdscr self.config = ConfigManager('console.conf', defaults=DEFAULT_CONSOLE_PREFS, file_version=2) self.config.run_converter((0, 1), 2, self._migrate_config_1_to_2) self.statusbars = StatusBars() from deluge.ui.console.modes.connectionmanager import ConnectionManager self.register_mode(ConnectionManager(stdscr, self.encoding), set_mode=True) torrentlist = self.register_mode( TorrentList(self.stdscr, self.encoding)) self.register_mode(CmdLine(self.stdscr, self.encoding)) self.register_mode(EventView(torrentlist, self.stdscr, self.encoding)) self.register_mode( TorrentDetail(torrentlist, self.stdscr, self.config, self.encoding)) self.register_mode( Preferences(torrentlist, self.stdscr, self.config, self.encoding)) self.register_mode( AddTorrents(torrentlist, self.stdscr, self.config, self.encoding)) self.eventlog = EventLog() self.active_mode.topbar = ('{!status!}Deluge ' + deluge.common.get_version() + ' Console') self.active_mode.bottombar = '{!status!}' self.active_mode.refresh() # Start the twisted mainloop reactor.run() @overrides(TermResizeHandler) def on_terminal_size(self, *args): rows, cols = super(ConsoleUI, self).on_terminal_size(args) for mode in self.modes: self.modes[mode].on_resize(rows, cols) def register_mode(self, mode, set_mode=False): self.modes[mode.mode_name] = mode if set_mode: self.set_mode(mode.mode_name) return mode def set_mode(self, mode_name, refresh=False): log.debug('Setting console mode: %s', mode_name) mode = self.modes.get(mode_name, None) if mode is None: log.error('Non-existent mode requested: %s', mode_name) return self.stdscr.erase() if self.active_mode: self.active_mode.pause() d = component.pause([self.active_mode.mode_name]) def on_mode_paused(result, mode, *args): from deluge.ui.console.widgets.popup import PopupsHandler if isinstance(mode, PopupsHandler): if mode.popup is not None: # If popups are not removed, they are still referenced in the memory # which can cause issues as the popup's screen will not be destroyed. # This can lead to the popup border being visible for short periods # while the current modes' screen is repainted. log.error( 'Mode "%s" still has popups available after being paused.' ' Ensure all popups are removed on pause!', mode.popup.title, ) d.addCallback(on_mode_paused, self.active_mode) reactor.removeReader(self.active_mode) self.active_mode = mode self.statusbars.screen = self.active_mode # The Screen object is designed to run as a twisted reader so that it # can use twisted's select poll for non-blocking user input. reactor.addReader(self.active_mode) self.stdscr.clear() if self.active_mode._component_state == 'Stopped': component.start([self.active_mode.mode_name]) else: component.resume([self.active_mode.mode_name]) mode.resume() if refresh: mode.refresh() return mode def switch_mode(self, func, error_smg): def on_stop(arg): if arg and True in arg[0]: func() else: self.messages.append(('Error', error_smg)) component.stop(['TorrentList']).addCallback(on_stop) def is_active_mode(self, mode): return mode == self.active_mode def start_components(self): def on_started(result): component.pause([ 'TorrentList', 'EventView', 'AddTorrents', 'TorrentDetail', 'Preferences', ]) if self.interactive: d = component.start().addCallback(on_started) else: d = component.start(['SessionProxy', 'ConsoleUI', 'CoreConfig']) return d def start_console(self): # Maintain a list of (torrent_id, name) for use in tab completion self.started_deferred = defer.Deferred() if not self.initialized: self.initialized = True d = self.start_components() else: def on_stopped(result): return component.start(['SessionProxy']) d = component.stop(['SessionProxy']).addCallback(on_stopped) return d def start(self): def on_session_state(result): self.torrents = [] self.events = [] def on_torrents_status(torrents): for torrent_id, status in torrents.items(): self.torrents.append((torrent_id, status['name'])) self.started_deferred.callback(True) client.core.get_torrents_status({ 'id': result }, ['name']).addCallback(on_torrents_status) d = client.core.get_session_state().addCallback(on_session_state) # Register event handlers to keep the torrent list up-to-date client.register_event_handler('TorrentAddedEvent', self.on_torrent_added_event) client.register_event_handler('TorrentRemovedEvent', self.on_torrent_removed_event) return d def on_torrent_added_event(self, event, from_state=False): def on_torrent_status(status): self.torrents.append((event, status['name'])) client.core.get_torrent_status(event, ['name']).addCallback(on_torrent_status) def on_torrent_removed_event(self, event): for index, (tid, name) in enumerate(self.torrents): if event == tid: del self.torrents[index] def match_torrents(self, strings): torrent_ids = [] for s in strings: torrent_ids.extend(self.match_torrent(s)) return list(set(torrent_ids)) def match_torrent(self, string): """ Returns a list of torrent_id matches for the string. It will search both torrent_ids and torrent names, but will only return torrent_ids. :param string: str, the string to match on :returns: list of matching torrent_ids. Will return an empty list if no matches are found. """ deluge.common.decode_bytes(string, self.encoding) if string == '*' or string == '': return [tid for tid, name in self.torrents] match_func = '__eq__' if string.startswith('*'): string = string[1:] match_func = 'endswith' if string.endswith('*'): match_func = '__contains__' if match_func == 'endswith' else 'startswith' string = string[:-1] matches = [] for tid, name in self.torrents: deluge.common.decode_bytes(name, self.encoding) if getattr(tid, match_func, None)(string) or getattr( name, match_func, None)(string): matches.append(tid) return matches def get_torrent_name(self, torrent_id): for tid, name in self.torrents: if torrent_id == tid: return name return None def set_batch_write(self, batch): if self.interactive and isinstance( self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): return self.active_mode.set_batch_write(batch) def tab_complete_torrent(self, line): if self.interactive and isinstance( self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): return self.active_mode.tab_complete_torrent(line) def tab_complete_path(self, line, path_type='file', ext='', sort='name', dirs_first=True): if self.interactive and isinstance( self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): return self.active_mode.tab_complete_path(line, path_type=path_type, ext=ext, sort=sort, dirs_first=dirs_first) def on_client_disconnect(self): component.stop() def write(self, s): if self.interactive: if isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): self.active_mode.write(s) else: component.get('CmdLine').add_line(s, False) self.events.append(s) else: print(colors.strip_colors(s)) def write_event(self, s): if self.interactive: if isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): self.events.append(s) self.active_mode.write(s) else: component.get('CmdLine').add_line(s, False) self.events.append(s) else: print(colors.strip_colors(s)) def _migrate_config_1_to_2(self, config): """Create better structure by moving most settings out of dict root and into sub categories. Some keys are also renamed to be consistent with other UIs. """ def move_key(source, dest, source_key, dest_key=None): if dest_key is None: dest_key = source_key dest[dest_key] = source[source_key] del source[source_key] # These are moved to 'torrentview' sub dict for k in [ 'sort_primary', 'sort_secondary', 'move_selection', 'separate_complete', ]: move_key(config, config['torrentview'], k) # These are moved to 'addtorrents' sub dict for k in [ 'show_misc_files', 'show_hidden_folders', 'sort_column', 'reverse_sort', 'last_path', ]: move_key(config, config['addtorrents'], 'addtorrents_%s' % k, dest_key=k) # These are moved to 'cmdline' sub dict for k in [ 'ignore_duplicate_lines', 'torrents_per_tab_press', 'third_tab_lists_all', ]: move_key(config, config['cmdline'], k) move_key( config, config['cmdline'], 'save_legacy_history', dest_key='save_command_history', ) # Add key for localization config['language'] = DEFAULT_CONSOLE_PREFS['language'] # Migrate column settings columns = [ 'queue', 'size', 'state', 'progress', 'seeds', 'peers', 'downspeed', 'upspeed', 'eta', 'ratio', 'avail', 'added', 'tracker', 'savepath', 'downloaded', 'uploaded', 'remaining', 'owner', 'downloading_time', 'seeding_time', 'completed', 'seeds_peers_ratio', 'complete_seen', 'down_limit', 'up_limit', 'shared', 'name', ] column_name_mapping = { 'downspeed': 'download_speed', 'upspeed': 'upload_speed', 'added': 'time_added', 'savepath': 'download_location', 'completed': 'completed_time', 'complete_seen': 'last_seen_complete', 'down_limit': 'max_download_speed', 'up_limit': 'max_upload_speed', 'downloading_time': 'active_time', } from deluge.ui.console.modes.torrentlist.torrentview import default_columns # These are moved to 'torrentview.columns' sub dict for k in columns: column_name = column_name_mapping.get(k, k) config['torrentview']['columns'][column_name] = {} if k == 'name': config['torrentview']['columns'][column_name]['visible'] = True else: move_key( config, config['torrentview']['columns'][column_name], 'show_%s' % k, dest_key='visible', ) move_key( config, config['torrentview']['columns'][column_name], '%s_width' % k, dest_key='width', ) config['torrentview']['columns'][column_name][ 'order'] = default_columns[column_name]['order'] return config
class ConsoleUI(component.Component, TermResizeHandler): def __init__(self, options, cmds, log_stream): component.Component.__init__(self, 'ConsoleUI') TermResizeHandler.__init__(self) self.options = options self.log_stream = log_stream # keep track of events for the log view self.events = [] self.torrents = [] self.statusbars = None self.modes = {} self.active_mode = None self.initialized = False try: locale.setlocale(locale.LC_ALL, '') self.encoding = locale.getpreferredencoding() except locale.Error: self.encoding = sys.getdefaultencoding() log.debug('Using encoding: %s', self.encoding) # start up the session proxy self.sessionproxy = SessionProxy() client.set_disconnect_callback(self.on_client_disconnect) # Set the interactive flag to indicate where we should print the output self.interactive = True self._commands = cmds self.coreconfig = CoreConfig() def start_ui(self): """Start the console UI. Note: When running console UI reactor.run() will be called which effectively blocks this function making the return value insignificant. However, when running unit tests, the reacor is replaced by a mock object, leaving the return deferred object necessary for the tests to run properly. Returns: Deferred: If valid commands are provided, a deferred that fires when all commands are executed. Else None is returned. """ if self.options.parsed_cmds: self.interactive = False if not self._commands: print('No valid console commands found') return deferred = self.exec_args(self.options) reactor.run() return deferred else: # Interactive if deluge.common.windows_check(): print("""\nDeluge-console does not run in interactive mode on Windows. \n Please use commands from the command line, e.g.:\n deluge-console.exe help deluge-console.exe info deluge-console.exe "add --help" deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent" """) else: class ConsoleLog(object): def write(self, data): pass def flush(self): pass # We don't ever want log output to terminal when running in # interactive mode, so insert a dummy here self.log_stream.out = ConsoleLog() # Set Esc key delay to 0 to avoid a very annoying delay # due to curses waiting in case of other key are pressed # after ESC is pressed os.environ.setdefault('ESCDELAY', '0') # We use the curses.wrapper function to prevent the console from getting # messed up if an uncaught exception is experienced. import curses.wrapper curses.wrapper(self.run) def quit(self): if client.connected(): def on_disconnect(result): reactor.stop() return client.disconnect().addCallback(on_disconnect) else: try: reactor.stop() except error.ReactorNotRunning: pass def exec_args(self, options): """Execute console commands from command line.""" from deluge.ui.console.cmdline.command import Commander commander = Commander(self._commands) def on_connect(result): def on_components_started(result): def on_started(result): def do_command(result, cmd): return commander.do_command(cmd) def exec_command(result, cmd): return commander.exec_command(cmd) d = defer.succeed(None) for command in options.parsed_cmds: if command.command in ('quit', 'exit'): break d.addCallback(exec_command, command) d.addCallback(do_command, 'quit') return d # We need to wait for the rpcs in start() to finish before processing # any of the commands. self.started_deferred.addCallback(on_started) return self.started_deferred d = self.start_console() d.addCallback(on_components_started) return d def on_connect_fail(reason): if reason.check(DelugeError): rm = reason.getErrorMessage() else: rm = reason.value.message print('Could not connect to daemon: %s:%s\n %s' % (options.daemon_addr, options.daemon_port, rm)) commander.do_command('quit') d = None if not self.interactive and options.parsed_cmds[0].command == 'connect': d = commander.do_command(options.parsed_cmds.pop(0)) else: log.info('connect: host=%s, port=%s, username=%s, password=%s', options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass) d = client.connect(options.daemon_addr, options.daemon_port, options.daemon_user, options.daemon_pass) d.addCallback(on_connect) d.addErrback(on_connect_fail) return d def run(self, stdscr): """This method is called by the curses.wrapper to start the mainloop and screen. Args: stdscr (_curses.curses window): curses screen passed in from curses.wrapper. """ # We want to do an interactive session, so start up the curses screen and # pass it the function that handles commands colors.init_colors() self.stdscr = stdscr self.config = ConfigManager('console.conf', defaults=DEFAULT_CONSOLE_PREFS, file_version=2) self.config.run_converter((0, 1), 2, self._migrate_config_1_to_2) self.statusbars = StatusBars() from deluge.ui.console.modes.connectionmanager import ConnectionManager self.register_mode(ConnectionManager(stdscr, self.encoding), set_mode=True) torrentlist = self.register_mode(TorrentList(self.stdscr, self.encoding)) self.register_mode(CmdLine(self.stdscr, self.encoding)) self.register_mode(EventView(torrentlist, self.stdscr, self.encoding)) self.register_mode(TorrentDetail(torrentlist, self.stdscr, self.config, self.encoding)) self.register_mode(Preferences(torrentlist, self.stdscr, self.config, self.encoding)) self.register_mode(AddTorrents(torrentlist, self.stdscr, self.config, self.encoding)) self.eventlog = EventLog() self.active_mode.topbar = '{!status!}Deluge ' + deluge.common.get_version() + ' Console' self.active_mode.bottombar = '{!status!}' self.active_mode.refresh() # Start the twisted mainloop reactor.run() @overrides(TermResizeHandler) def on_terminal_size(self, *args): rows, cols = super(ConsoleUI, self).on_terminal_size(args) for mode in self.modes: self.modes[mode].on_resize(rows, cols) def register_mode(self, mode, set_mode=False): self.modes[mode.mode_name] = mode if set_mode: self.set_mode(mode.mode_name) return mode def set_mode(self, mode_name, refresh=False): log.debug('Setting console mode: %s', mode_name) mode = self.modes.get(mode_name, None) if mode is None: log.error('Non-existent mode requested: %s', mode_name) return self.stdscr.erase() if self.active_mode: self.active_mode.pause() d = component.pause([self.active_mode.mode_name]) def on_mode_paused(result, mode, *args): from deluge.ui.console.widgets.popup import PopupsHandler if isinstance(mode, PopupsHandler): if mode.popup is not None: # If popups are not removed, they are still referenced in the memory # which can cause issues as the popup's screen will not be destroyed. # This can lead to the popup border being visible for short periods # while the current modes' screen is repainted. log.error('Mode "%s" still has popups available after being paused.' ' Ensure all popups are removed on pause!', mode.popup.title) d.addCallback(on_mode_paused, self.active_mode) reactor.removeReader(self.active_mode) self.active_mode = mode self.statusbars.screen = self.active_mode # The Screen object is designed to run as a twisted reader so that it # can use twisted's select poll for non-blocking user input. reactor.addReader(self.active_mode) self.stdscr.clear() if self.active_mode._component_state == 'Stopped': component.start([self.active_mode.mode_name]) else: component.resume([self.active_mode.mode_name]) mode.resume() if refresh: mode.refresh() return mode def switch_mode(self, func, error_smg): def on_stop(arg): if arg and True in arg[0]: func() else: self.messages.append(('Error', error_smg)) component.stop(['TorrentList']).addCallback(on_stop) def is_active_mode(self, mode): return mode == self.active_mode def start_components(self): def on_started(result): component.pause(['TorrentList', 'EventView', 'AddTorrents', 'TorrentDetail', 'Preferences']) if self.interactive: d = component.start().addCallback(on_started) else: d = component.start(['SessionProxy', 'ConsoleUI', 'CoreConfig']) return d def start_console(self): # Maintain a list of (torrent_id, name) for use in tab completion self.started_deferred = defer.Deferred() if not self.initialized: self.initialized = True d = self.start_components() else: def on_stopped(result): return component.start(['SessionProxy']) d = component.stop(['SessionProxy']).addCallback(on_stopped) return d def start(self): def on_session_state(result): self.torrents = [] self.events = [] def on_torrents_status(torrents): for torrent_id, status in torrents.items(): self.torrents.append((torrent_id, status['name'])) self.started_deferred.callback(True) client.core.get_torrents_status({'id': result}, ['name']).addCallback(on_torrents_status) d = client.core.get_session_state().addCallback(on_session_state) # Register event handlers to keep the torrent list up-to-date client.register_event_handler('TorrentAddedEvent', self.on_torrent_added_event) client.register_event_handler('TorrentRemovedEvent', self.on_torrent_removed_event) return d def on_torrent_added_event(self, event, from_state=False): def on_torrent_status(status): self.torrents.append((event, status['name'])) client.core.get_torrent_status(event, ['name']).addCallback(on_torrent_status) def on_torrent_removed_event(self, event): for index, (tid, name) in enumerate(self.torrents): if event == tid: del self.torrents[index] def match_torrents(self, strings): torrent_ids = [] for s in strings: torrent_ids.extend(self.match_torrent(s)) return list(set(torrent_ids)) def match_torrent(self, string): """ Returns a list of torrent_id matches for the string. It will search both torrent_ids and torrent names, but will only return torrent_ids. :param string: str, the string to match on :returns: list of matching torrent_ids. Will return an empty list if no matches are found. """ deluge.common.decode_bytes(string, self.encoding) if string == '*' or string == '': return [tid for tid, name in self.torrents] match_func = '__eq__' if string.startswith('*'): string = string[1:] match_func = 'endswith' if string.endswith('*'): match_func = '__contains__' if match_func == 'endswith' else 'startswith' string = string[:-1] matches = [] for tid, name in self.torrents: deluge.common.decode_bytes(name, self.encoding) if getattr(tid, match_func, None)(string) or getattr(name, match_func, None)(string): matches.append(tid) return matches def get_torrent_name(self, torrent_id): for tid, name in self.torrents: if torrent_id == tid: return name return None def set_batch_write(self, batch): if self.interactive and isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): return self.active_mode.set_batch_write(batch) def tab_complete_torrent(self, line): if self.interactive and isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): return self.active_mode.tab_complete_torrent(line) def tab_complete_path(self, line, path_type='file', ext='', sort='name', dirs_first=True): if self.interactive and isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): return self.active_mode.tab_complete_path(line, path_type=path_type, ext=ext, sort=sort, dirs_first=dirs_first) def on_client_disconnect(self): component.stop() def write(self, s): if self.interactive: if isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): self.active_mode.write(s) else: component.get('CmdLine').add_line(s, False) self.events.append(s) else: print(colors.strip_colors(s.encode('utf8'))) def write_event(self, s): if self.interactive: if isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine): self.events.append(s) self.active_mode.write(s) else: component.get('CmdLine').add_line(s, False) self.events.append(s) else: print(colors.strip_colors(s.encode('utf8'))) def _migrate_config_1_to_2(self, config): """Create better structure by moving most settings out of dict root and into sub categories. Some keys are also renamed to be consistent with other UIs. """ def move_key(source, dest, source_key, dest_key=None): if dest_key is None: dest_key = source_key dest[dest_key] = source[source_key] del source[source_key] # These are moved to 'torrentview' sub dict for k in ['sort_primary', 'sort_secondary', 'move_selection', 'separate_complete']: move_key(config, config['torrentview'], k) # These are moved to 'addtorrents' sub dict for k in ['show_misc_files', 'show_hidden_folders', 'sort_column', 'reverse_sort', 'last_path']: move_key(config, config['addtorrents'], 'addtorrents_%s' % k, dest_key=k) # These are moved to 'cmdline' sub dict for k in ['ignore_duplicate_lines', 'torrents_per_tab_press', 'third_tab_lists_all']: move_key(config, config['cmdline'], k) move_key(config, config['cmdline'], 'save_legacy_history', dest_key='save_command_history') # Add key for localization config['language'] = DEFAULT_CONSOLE_PREFS['language'] # Migrate column settings columns = ['queue', 'size', 'state', 'progress', 'seeds', 'peers', 'downspeed', 'upspeed', 'eta', 'ratio', 'avail', 'added', 'tracker', 'savepath', 'downloaded', 'uploaded', 'remaining', 'owner', 'downloading_time', 'seeding_time', 'completed', 'seeds_peers_ratio', 'complete_seen', 'down_limit', 'up_limit', 'shared', 'name'] column_name_mapping = { 'downspeed': 'download_speed', 'upspeed': 'upload_speed', 'added': 'time_added', 'savepath': 'download_location', 'completed': 'completed_time', 'complete_seen': 'last_seen_complete', 'down_limit': 'max_download_speed', 'up_limit': 'max_upload_speed', 'downloading_time': 'active_time' } from deluge.ui.console.modes.torrentlist.torrentview import default_columns # These are moved to 'torrentview.columns' sub dict for k in columns: column_name = column_name_mapping.get(k, k) config['torrentview']['columns'][column_name] = {} if k == 'name': config['torrentview']['columns'][column_name]['visible'] = True else: move_key(config, config['torrentview']['columns'][column_name], 'show_%s' % k, dest_key='visible') move_key(config, config['torrentview']['columns'][column_name], '%s_width' % k, dest_key='width') config['torrentview']['columns'][column_name]['order'] = default_columns[column_name]['order'] return config