diff options
Diffstat (limited to 'deluge/ui')
142 files changed, 970 insertions, 1309 deletions
diff --git a/deluge/ui/client.py b/deluge/ui/client.py index 180d8ef2b..6b657d5ca 100644 --- a/deluge/ui/client.py +++ b/deluge/ui/client.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import subprocess import sys @@ -33,7 +30,7 @@ def format_kwargs(kwargs): return ', '.join([key + '=' + str(value) for key, value in kwargs.items()]) -class DelugeRPCRequest(object): +class DelugeRPCRequest: """ This object is created whenever there is a RPCRequest to be sent to the daemon. It is generally only used by the DaemonProxy's call method. @@ -243,7 +240,7 @@ class DelugeRPCClientFactory(ClientFactory): self.daemon.disconnect_callback() -class DaemonProxy(object): +class DaemonProxy: pass @@ -526,7 +523,7 @@ class DaemonStandaloneProxy(DaemonProxy): self.__daemon.core.eventmanager.deregister_event_handler(event, handler) -class DottedObject(object): +class DottedObject: """ This is used for dotted name calls to client """ @@ -551,7 +548,7 @@ class RemoteMethod(DottedObject): return self.daemon.call(self.base, *args, **kwargs) -class Client(object): +class Client: """ This class is used to connect to a daemon process and issue RPC requests. """ @@ -615,7 +612,7 @@ class Client(object): d.addErrback(on_authenticate_fail) return d - d.addCallback(on_connected) + d.addCallbacks(on_connected) d.addErrback(on_connect_fail) if not skip_authentication: d.addCallback(authenticate, username, password) diff --git a/deluge/ui/common.py b/deluge/ui/common.py index c5064a6f4..f9f774e23 100644 --- a/deluge/ui/common.py +++ b/deluge/ui/common.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) Damien Churchill 2008-2009 <damoxc@gmail.com> # Copyright (C) Andrew Resch 2009 <andrewresch@gmail.com> @@ -11,8 +10,6 @@ """ The ui common module contains methods and classes that are deemed useful for all the interfaces. """ -from __future__ import unicode_literals - import logging import os from hashlib import sha1 as sha @@ -167,7 +164,7 @@ DISK_CACHE_KEYS = [ ] -class TorrentInfo(object): +class TorrentInfo: """Collects information about a torrent file. Args: @@ -186,7 +183,7 @@ class TorrentInfo(object): try: with open(filename, 'rb') as _file: self._filedata = _file.read() - except IOError as ex: + except OSError as ex: log.warning('Unable to open %s: %s', filename, ex) return @@ -387,7 +384,7 @@ class TorrentInfo(object): return self._filedata -class FileTree2(object): +class FileTree2: """ Converts a list of paths in to a file tree. @@ -467,7 +464,7 @@ class FileTree2(object): return '\n'.join(lines) -class FileTree(object): +class FileTree: """ Convert a list of paths in a file tree. diff --git a/deluge/ui/console/__init__.py b/deluge/ui/console/__init__.py index 56e8d629d..7da04a6de 100644 --- a/deluge/ui/console/__init__.py +++ b/deluge/ui/console/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from deluge.ui.console.console import Console UI_PATH = __path__[0] @@ -16,4 +13,4 @@ UI_PATH = __path__[0] def start(): - Console().start() + return Console().start() diff --git a/deluge/ui/console/cmdline/command.py b/deluge/ui/console/cmdline/command.py index 2ff32dff9..40edd78f0 100644 --- a/deluge/ui/console/cmdline/command.py +++ b/deluge/ui/console/cmdline/command.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -9,8 +8,6 @@ # See LICENSE for more details. # -from __future__ import print_function, unicode_literals - import logging import shlex @@ -23,7 +20,7 @@ from deluge.ui.console.utils.colors import strip_colors log = logging.getLogger(__name__) -class Commander(object): +class Commander: def __init__(self, cmds, interactive=False): self._commands = cmds self.interactive = interactive @@ -144,7 +141,7 @@ class Commander(object): return ret -class BaseCommand(object): +class BaseCommand: usage = None interactive_only = False diff --git a/deluge/ui/console/cmdline/commands/__init__.py b/deluge/ui/console/cmdline/commands/__init__.py index 628fae597..39dbefe2a 100644 --- a/deluge/ui/console/cmdline/commands/__init__.py +++ b/deluge/ui/console/cmdline/commands/__init__.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - from deluge.ui.console.cmdline.command import BaseCommand __all__ = ['BaseCommand'] diff --git a/deluge/ui/console/cmdline/commands/add.py b/deluge/ui/console/cmdline/commands/add.py index da42695b5..706ae168e 100644 --- a/deluge/ui/console/cmdline/commands/add.py +++ b/deluge/ui/console/cmdline/commands/add.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,10 +7,10 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import os from base64 import b64encode +from urllib.parse import urlparse +from urllib.request import url2pathname from twisted.internet import defer @@ -21,14 +20,6 @@ from deluge.ui.client import client from . import BaseCommand -try: - from urllib.parse import urlparse - from urllib.request import url2pathname -except ImportError: - # PY2 fallback - from urllib import url2pathname # pylint: disable=ungrouped-imports - from urlparse import urlparse # pylint: disable=ungrouped-imports - class Command(BaseCommand): """Add torrents""" diff --git a/deluge/ui/console/cmdline/commands/cache.py b/deluge/ui/console/cmdline/commands/cache.py index e427f085f..fe6cd580d 100644 --- a/deluge/ui/console/cmdline/commands/cache.py +++ b/deluge/ui/console/cmdline/commands/cache.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from deluge.ui.client import client from deluge.ui.common import DISK_CACHE_KEYS @@ -24,7 +21,7 @@ class Command(BaseCommand): def on_cache_status(status): for key, value in sorted(status.items()): - self.console.write('{!info!}%s: {!input!}%s' % (key, value)) + self.console.write(f'{{!info!}}{key}: {{!input!}}{value}') return client.core.get_session_status(DISK_CACHE_KEYS).addCallback( on_cache_status diff --git a/deluge/ui/console/cmdline/commands/config.py b/deluge/ui/console/cmdline/commands/config.py index 9821e47bc..8b31ca3cd 100644 --- a/deluge/ui/console/cmdline/commands/config.py +++ b/deluge/ui/console/cmdline/commands/config.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import json import logging import re @@ -97,10 +94,10 @@ class Command(BaseCommand): value = pprint.pformat(value, 2, 80) new_value = [] for line in value.splitlines(): - new_value.append('%s%s' % (color, line)) + new_value.append(f'{color}{line}') value = '\n'.join(new_value) - string += '%s: %s%s\n' % (key, color, value) + string += f'{key}: {color}{value}\n' self.console.write(string.strip()) return client.core.get_config().addCallback(_on_get_config) @@ -132,7 +129,7 @@ class Command(BaseCommand): def on_set_config(result): self.console.write('{!success!}Configuration value successfully updated.') - self.console.write('Setting "%s" to: %r' % (key, val)) + self.console.write(f'Setting "{key}" to: {val!r}') return client.core.set_config({key: val}).addCallback(on_set_config) def complete(self, text): diff --git a/deluge/ui/console/cmdline/commands/connect.py b/deluge/ui/console/cmdline/commands/connect.py index 6588f7a04..4c76de38f 100644 --- a/deluge/ui/console/cmdline/commands/connect.py +++ b/deluge/ui/console/cmdline/commands/connect.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -57,17 +54,12 @@ class Command(BaseCommand): def on_connect(result): if self.console.interactive: - self.console.write('{!success!}Connected to %s:%s!' % (host, port)) + self.console.write(f'{{!success!}}Connected to {host}:{port}!') return component.start() def on_connect_fail(result): - try: - msg = result.value.exception_msg - except AttributeError: - msg = result.value.message self.console.write( - '{!error!}Failed to connect to %s:%s with reason: %s' - % (host, port, msg) + f'{{!error!}}Failed to connect to {host}:{port} with reason: {result.value.message}' ) return result diff --git a/deluge/ui/console/cmdline/commands/debug.py b/deluge/ui/console/cmdline/commands/debug.py index 3ca06ed15..af48a8b7f 100644 --- a/deluge/ui/console/cmdline/commands/debug.py +++ b/deluge/ui/console/cmdline/commands/debug.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from twisted.internet import defer import deluge.component as component diff --git a/deluge/ui/console/cmdline/commands/gui.py b/deluge/ui/console/cmdline/commands/gui.py index 10e4c499b..575bc9b3a 100644 --- a/deluge/ui/console/cmdline/commands/gui.py +++ b/deluge/ui/console/cmdline/commands/gui.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component diff --git a/deluge/ui/console/cmdline/commands/halt.py b/deluge/ui/console/cmdline/commands/halt.py index 635595898..608f2de9d 100644 --- a/deluge/ui/console/cmdline/commands/halt.py +++ b/deluge/ui/console/cmdline/commands/halt.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from deluge.ui.client import client diff --git a/deluge/ui/console/cmdline/commands/help.py b/deluge/ui/console/cmdline/commands/help.py index 2711eea99..754dadbec 100644 --- a/deluge/ui/console/cmdline/commands/help.py +++ b/deluge/ui/console/cmdline/commands/help.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from twisted.internet import defer diff --git a/deluge/ui/console/cmdline/commands/info.py b/deluge/ui/console/cmdline/commands/info.py index 0d22f76a9..7ea9a6773 100644 --- a/deluge/ui/console/cmdline/commands/info.py +++ b/deluge/ui/console/cmdline/commands/info.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - from os.path import sep as dirsep import deluge.component as component @@ -70,6 +67,7 @@ STATUS_KEYS = [ 'total_payload_download', 'total_payload_upload', 'time_added', + 'label', ] # Add filter specific state to torrent states @@ -177,7 +175,7 @@ class Command(BaseCommand): sort_key = 'name' sort_reverse = False for key, value in sorted( - list(status.items()), + status.items(), key=lambda x: x[1].get(sort_key), reverse=sort_reverse, ): @@ -218,9 +216,9 @@ class Command(BaseCommand): for depth, subdir in enumerate(filepath): indent = ' ' * depth * spaces_per_level if depth >= len(prevpath): - self.console.write('%s{!cyan!}%s' % (indent, subdir)) + self.console.write(f'{indent}{{!cyan!}}{subdir}') elif subdir != prevpath[depth]: - self.console.write('%s{!cyan!}%s' % (indent, subdir)) + self.console.write(f'{indent}{{!cyan!}}{subdir}') depth = len(filepath) @@ -296,7 +294,7 @@ class Command(BaseCommand): s += peer['ip'] else: # IPv6 - s += '[%s]:%s' % ( + s += '[{}]:{}'.format( ':'.join(peer['ip'].split(':')[:-1]), peer['ip'].split(':')[-1], ) @@ -308,7 +306,7 @@ class Command(BaseCommand): s += '\t\t' else: s += '\t' - s += '%s%s\t%s%s' % ( + s += '{}{}\t{}{}'.format( colors.state_color['Seeding'], fspeed(peer['up_speed']), colors.state_color['Downloading'], @@ -336,7 +334,7 @@ class Command(BaseCommand): if verbose or detailed: self.console.write('{!info!}Name: {!input!}%s' % (status['name'])) self.console.write('{!info!}ID: {!input!}%s' % (torrent_id)) - s = '{!info!}State: %s%s' % ( + s = '{{!info!}}State: {}{}'.format( colors.state_color[status['state']], status['state'], ) @@ -354,12 +352,12 @@ class Command(BaseCommand): self.console.write(s) if status['state'] in ('Seeding', 'Downloading', 'Queued'): - s = '{!info!}Seeds: {!input!}%s (%s)' % ( + s = '{{!info!}}Seeds: {{!input!}}{} ({})'.format( status['num_seeds'], status['total_seeds'], ) s += sep - s += '{!info!}Peers: {!input!}%s (%s)' % ( + s += '{{!info!}}Peers: {{!input!}}{} ({})'.format( status['num_peers'], status['total_peers'], ) @@ -378,7 +376,7 @@ class Command(BaseCommand): if total_done == total_size: s = '{!info!}Size: {!input!}%s' % (total_size) else: - s = '{!info!}Size: {!input!}%s/%s' % (total_done, total_size) + s = f'{{!info!}}Size: {{!input!}}{total_done}/{total_size}' s += sep s += '{!info!}Downloaded: {!input!}%s' % fsize( status['all_time_download'], shortform=True @@ -418,14 +416,20 @@ class Command(BaseCommand): pbar = f_progressbar( status['progress'], cols - (13 + len('%.2f%%' % status['progress'])) ) - s = '{!info!}Progress: {!input!}%.2f%% %s' % (status['progress'], pbar) + s = '{{!info!}}Progress: {{!input!}}{:.2f}% {}'.format( + status['progress'], pbar + ) self.console.write(s) s = '{!info!}Download Folder: {!input!}%s' % status['download_location'] - self.console.write(s + '\n') + self.console.write(s) + + if 'label' in status: + s = '{!info!}Label: {!input!}%s' % status['label'] + self.console.write(s) if detailed: - self.console.write('{!info!}Files in torrent') + self.console.write('\n{!info!}Files in torrent') self.show_file_info(torrent_id, status) self.console.write('{!info!}Connected peers') self.show_peer_info(torrent_id, status) @@ -433,7 +437,7 @@ class Command(BaseCommand): up_color = colors.state_color['Seeding'] down_color = colors.state_color['Downloading'] - s = '%s%s' % ( + s = '{}{}'.format( colors.state_color[status['state']], '[' + status['state'][0] + ']', ) @@ -458,7 +462,7 @@ class Command(BaseCommand): ) if status['download_payload_rate'] > 0: - dl_info += ' @ %s%s' % ( + dl_info += ' @ {}{}'.format( down_color, fspeed(status['download_payload_rate'], shortform=True), ) @@ -468,7 +472,7 @@ class Command(BaseCommand): status['total_uploaded'], status['total_payload_upload'] ) if status['upload_payload_rate'] > 0: - ul_info += ' @ %s%s' % ( + ul_info += ' @ {}{}'.format( up_color, fspeed(status['upload_payload_rate'], shortform=True), ) diff --git a/deluge/ui/console/cmdline/commands/manage.py b/deluge/ui/console/cmdline/commands/manage.py index 6375a74c3..e5ea9b255 100644 --- a/deluge/ui/console/cmdline/commands/manage.py +++ b/deluge/ui/console/cmdline/commands/manage.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from twisted.internet import defer @@ -69,7 +66,7 @@ class Command(BaseCommand): self.console.write('{!info!}ID: {!input!}%s' % torrentid) for k, v in data.items(): if k != 'name': - self.console.write('{!info!}%s: {!input!}%s' % (k, v)) + self.console.write(f'{{!info!}}{k}: {{!input!}}{v}') def on_torrents_status_fail(reason): self.console.write('{!error!}Failed to get torrent data.') @@ -106,9 +103,7 @@ class Command(BaseCommand): self.console.write('{!success!}Torrent option successfully updated.') deferred.callback(True) - self.console.write( - 'Setting %s to %s for torrents %s..' % (key, val, torrent_ids) - ) + self.console.write(f'Setting {key} to {val} for torrents {torrent_ids}..') client.core.set_torrent_options(torrent_ids, {key: val}).addCallback( on_set_config ) diff --git a/deluge/ui/console/cmdline/commands/move.py b/deluge/ui/console/cmdline/commands/move.py index 13e475e6f..67ee0af1d 100644 --- a/deluge/ui/console/cmdline/commands/move.py +++ b/deluge/ui/console/cmdline/commands/move.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os.path @@ -52,7 +49,7 @@ class Command(BaseCommand): names.append(self.console.get_torrent_name(tid)) def on_move(res): - msg = 'Moved "%s" to %s' % (', '.join(names), options.path) + msg = 'Moved "{}" to {}'.format(', '.join(names), options.path) self.console.write(msg) log.info(msg) diff --git a/deluge/ui/console/cmdline/commands/pause.py b/deluge/ui/console/cmdline/commands/pause.py index 1f7ef31a0..133424267 100644 --- a/deluge/ui/console/cmdline/commands/pause.py +++ b/deluge/ui/console/cmdline/commands/pause.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from deluge.ui.client import client diff --git a/deluge/ui/console/cmdline/commands/plugin.py b/deluge/ui/console/cmdline/commands/plugin.py index 72cecb40f..c424cb201 100644 --- a/deluge/ui/console/cmdline/commands/plugin.py +++ b/deluge/ui/console/cmdline/commands/plugin.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component import deluge.configmanager from deluge.ui.client import client diff --git a/deluge/ui/console/cmdline/commands/quit.py b/deluge/ui/console/cmdline/commands/quit.py index 261a01a9b..4459dfc70 100644 --- a/deluge/ui/console/cmdline/commands/quit.py +++ b/deluge/ui/console/cmdline/commands/quit.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from . import BaseCommand diff --git a/deluge/ui/console/cmdline/commands/recheck.py b/deluge/ui/console/cmdline/commands/recheck.py index c9b6360c9..046cb0b1e 100644 --- a/deluge/ui/console/cmdline/commands/recheck.py +++ b/deluge/ui/console/cmdline/commands/recheck.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from deluge.ui.client import client diff --git a/deluge/ui/console/cmdline/commands/resume.py b/deluge/ui/console/cmdline/commands/resume.py index 1f62c5f00..27b852894 100644 --- a/deluge/ui/console/cmdline/commands/resume.py +++ b/deluge/ui/console/cmdline/commands/resume.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from deluge.ui.client import client diff --git a/deluge/ui/console/cmdline/commands/rm.py b/deluge/ui/console/cmdline/commands/rm.py index c34148ac9..4a3fd008a 100644 --- a/deluge/ui/console/cmdline/commands/rm.py +++ b/deluge/ui/console/cmdline/commands/rm.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -74,7 +71,7 @@ class Command(BaseCommand): 'Error(s) occurred when trying to delete torrent(s).' ) for t_id, e_msg in errors: - self.console.write('Error removing torrent %s : %s' % (t_id, e_msg)) + self.console.write(f'Error removing torrent {t_id} : {e_msg}') log.info('Removing %d torrents', len(torrent_ids)) d = client.core.remove_torrents(torrent_ids, options.remove_data) diff --git a/deluge/ui/console/cmdline/commands/status.py b/deluge/ui/console/cmdline/commands/status.py index 948ad6b94..05c9796ce 100644 --- a/deluge/ui/console/cmdline/commands/status.py +++ b/deluge/ui/console/cmdline/commands/status.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from twisted.internet import defer @@ -65,7 +62,12 @@ class Command(BaseCommand): deferreds = [] ds = client.core.get_session_status( - ['num_peers', 'payload_upload_rate', 'payload_download_rate', 'dht_nodes'] + [ + 'peer.num_peers_connected', + 'payload_upload_rate', + 'payload_download_rate', + 'dht.dht_nodes', + ] ) ds.addCallback(on_session_status) deferreds.append(ds) @@ -95,7 +97,7 @@ class Command(BaseCommand): '{!info!}Total download: %s' % fspeed(self.status['payload_download_rate']) ) - self.console.write('{!info!}DHT Nodes: %i' % self.status['dht_nodes']) + self.console.write('{!info!}DHT Nodes: %i' % self.status['dht.dht_nodes']) if isinstance(self.torrents, int): if self.torrents == -2: diff --git a/deluge/ui/console/cmdline/commands/update_tracker.py b/deluge/ui/console/cmdline/commands/update_tracker.py index 591b95192..c05569d7b 100644 --- a/deluge/ui/console/cmdline/commands/update_tracker.py +++ b/deluge/ui/console/cmdline/commands/update_tracker.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.component as component from deluge.ui.client import client diff --git a/deluge/ui/console/console.py b/deluge/ui/console/console.py index f683c749d..8ef87e8de 100644 --- a/deluge/ui/console/console.py +++ b/deluge/ui/console/console.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -7,8 +6,6 @@ # the additional special exception to link portions of this program with the OpenSSL library. # See LICENSE for more details. # -from __future__ import print_function, unicode_literals - import fnmatch import logging import os @@ -53,7 +50,7 @@ def load_commands(command_dir): return dict(commands) -class LogStream(object): +class LogStream: out = sys.stdout def write(self, data): @@ -68,9 +65,7 @@ class Console(UI): cmd_description = """Console or command-line user interface""" def __init__(self, *args, **kwargs): - super(Console, self).__init__( - 'console', *args, log_stream=LogStream(), **kwargs - ) + super().__init__('console', *args, log_stream=LogStream(), **kwargs) group = self.parser.add_argument_group( _('Console Options'), @@ -150,7 +145,7 @@ class Console(UI): self.console_parser.subcommand = False self.parser.subcommand = False if i == -1 else True - super(Console, self).start(self.console_parser) + super().start(self.console_parser) from deluge.ui.console.main import ConsoleUI # import here because (see top) def run(options): diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py index c74d9022f..31d1db177 100644 --- a/deluge/ui/console/main.py +++ b/deluge/ui/console/main.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import print_function, unicode_literals - import locale import logging import os @@ -67,7 +64,7 @@ DEFAULT_CONSOLE_PREFS = { } -class MockConsoleLog(object): +class MockConsoleLog: def write(self, data): pass @@ -286,7 +283,7 @@ deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent" @overrides(TermResizeHandler) def on_terminal_size(self, *args): - rows, cols = super(ConsoleUI, self).on_terminal_size(args) + rows, cols = super().on_terminal_size(args) for mode in self.modes: self.modes[mode].on_resize(rows, cols) @@ -711,7 +708,7 @@ class EventLog(component.Component): if not t_name: return - self.write('%s: {!info!}%s ({!cyan!}%s{!info!})' % (state, t_name, torrent_id)) + self.write(f'{state}: {{!info!}}{t_name} ({{!cyan!}}{torrent_id}{{!info!}})') def on_torrent_finished_event(self, torrent_id): if component.get('TorrentList').config['ring_bell']: @@ -739,7 +736,7 @@ class EventLog(component.Component): except KeyError: pass - self.write('ConfigValueChanged: {!input!}%s: %s%s' % (key, color, value)) + self.write(f'ConfigValueChanged: {{!input!}}{key}: {color}{value}') def write(self, s): current_time = time.localtime() @@ -753,8 +750,6 @@ class EventLog(component.Component): if date_different: string = time.strftime(self.date_change_format) - if deluge.common.PY2: - string = string.decode() self.console.write_event(' ') self.console.write_event(string) diff --git a/deluge/ui/console/modes/add_util.py b/deluge/ui/console/modes/add_util.py index ac60b8974..9d29a1f4f 100644 --- a/deluge/ui/console/modes/add_util.py +++ b/deluge/ui/console/modes/add_util.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -9,15 +8,11 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import glob import logging import os from base64 import b64encode -from six import unichr as chr # noqa: A001 shadowing - import deluge.common from deluge.ui.client import client from deluge.ui.common import TorrentInfo diff --git a/deluge/ui/console/modes/addtorrents.py b/deluge/ui/console/modes/addtorrents.py index 6b2c105d9..217b63d85 100644 --- a/deluge/ui/console/modes/addtorrents.py +++ b/deluge/ui/console/modes/addtorrents.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2012 Arek Stefański <asmageddon@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os from base64 import b64encode @@ -24,12 +21,6 @@ from deluge.ui.console.utils import format_utils from deluge.ui.console.widgets.popup import InputPopup, MessagePopup try: - from future_builtins import zip -except ImportError: - # Ignore on Py3. - pass - -try: import curses except ImportError: pass @@ -377,7 +368,7 @@ class AddTorrents(BaseMode): 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)) + ress['fmsg'].append(f'{{!input!}} * {t_file}: {{!error!}}{msg}') if (ress['succ'] + ress['fail']) >= ress['total']: report_add_status( component.get('TorrentList'), @@ -526,9 +517,9 @@ class AddTorrents(BaseMode): 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) == 'k': + self.scroll_list_up(1) elif chr(c) == 'M': if self.last_mark != -1: if self.last_mark > self.cursel: diff --git a/deluge/ui/console/modes/basemode.py b/deluge/ui/console/modes/basemode.py index 5267eae5a..5ebaf86fe 100644 --- a/deluge/ui/console/modes/basemode.py +++ b/deluge/ui/console/modes/basemode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import sys @@ -36,7 +33,7 @@ except ImportError: log = logging.getLogger(__name__) -class InputKeyHandler(object): +class InputKeyHandler: def __init__(self): self._input_result = None @@ -62,7 +59,7 @@ class InputKeyHandler(object): return util.ReadState.IGNORED -class TermResizeHandler(object): +class TermResizeHandler: def __init__(self): try: signal.signal(signal.SIGWINCH, self.on_terminal_size) @@ -80,14 +77,14 @@ class TermResizeHandler(object): return rows, cols -class CursesStdIO(object): +class CursesStdIO: """ fake fd to be registered as a reader with the twisted reactor. Curses classes needing input should extend this """ def fileno(self): - """ We want to select on FD 0 """ + """We want to select on FD 0""" return 0 def doRead(self): # NOQA: N802 diff --git a/deluge/ui/console/modes/cmdline.py b/deluge/ui/console/modes/cmdline.py index 2735168db..7b0ff2dfc 100644 --- a/deluge/ui/console/modes/cmdline.py +++ b/deluge/ui/console/modes/cmdline.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> @@ -8,16 +7,12 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os import re -from io import open import deluge.component as component import deluge.configmanager -from deluge.common import PY2 from deluge.decorators import overrides from deluge.ui.console.cmdline.command import Commander from deluge.ui.console.modes.basemode import BaseMode, move_cursor @@ -139,18 +134,18 @@ class CmdLine(BaseMode, Commander): self._hf_lines = [0, 0] if self.console_config['cmdline']['save_command_history']: try: - with open(self.history_file[0], 'r', encoding='utf8') as _file: + with open(self.history_file[0], encoding='utf8') as _file: lines1 = _file.read().splitlines() self._hf_lines[0] = len(lines1) - except IOError: + except OSError: lines1 = [] self._hf_lines[0] = 0 try: - with open(self.history_file[1], 'r', encoding='utf8') as _file: + with open(self.history_file[1], encoding='utf8') as _file: lines2 = _file.read().splitlines() self._hf_lines[1] = len(lines2) - except IOError: + except OSError: lines2 = [] self._hf_lines[1] = 0 @@ -332,10 +327,10 @@ class CmdLine(BaseMode, Commander): # A key to add to the input string else: - if c > 31 and c < 256: + if 31 < c < 256: # Emulate getwch stroke = chr(c) - uchar = '' if PY2 else stroke + uchar = stroke while not uchar: try: uchar = stroke.decode(self.encoding) @@ -826,21 +821,21 @@ class CmdLine(BaseMode, Commander): # Let's avoid listing all torrents twice if there's no pattern if not empty and torrent_id.startswith(line): # Highlight the matching part - text = '{!info!}%s{!input!}%s - "%s"' % ( + text = '{{!info!}}{}{{!input!}}{} - "{}"'.format( torrent_id[:line_len], torrent_id[line_len:], torrent_name, ) possible_matches.append(text) if torrent_name.startswith(line): - text = '{!info!}%s{!input!}%s ({!cyan!}%s{!input!})' % ( + text = '{{!info!}}{}{{!input!}}{} ({{!cyan!}}{}{{!input!}})'.format( escaped_name[:line_len], escaped_name[line_len:], torrent_id, ) possible_matches.append(text) elif torrent_name.lower().startswith(line.lower()): - text = '{!info!}%s{!input!}%s ({!cyan!}%s{!input!})' % ( + text = '{{!info!}}{}{{!input!}}{} ({{!cyan!}}{}{{!input!}})'.format( escaped_name[:line_len], escaped_name[line_len:], torrent_id, diff --git a/deluge/ui/console/modes/connectionmanager.py b/deluge/ui/console/modes/connectionmanager.py index a5c596860..0ccdd93db 100644 --- a/deluge/ui/console/modes/connectionmanager.py +++ b/deluge/ui/console/modes/connectionmanager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -33,9 +30,12 @@ class ConnectionManager(BaseMode, PopupsHandler): self.all_torrents = None self.hostlist = HostList() BaseMode.__init__(self, stdscr, encoding=encoding) - self.update_hosts_status() def update_select_host_popup(self): + if self.popup and not isinstance(self.popup, SelectablePopup): + # Ignore MessagePopup on popup stack upon connect fail + return + selected_index = self.popup.current_selection() if self.popup else None popup = SelectablePopup( @@ -50,22 +50,24 @@ class ConnectionManager(BaseMode, PopupsHandler): % (_('Quit'), _('Add Host'), _('Delete Host')), space_below=True, ) - self.push_popup(popup, clear=True) for host_entry in self.hostlist.get_hosts_info(): host_id, hostname, port, user = host_entry - args = {'data': host_id, 'foreground': 'red'} - state = 'Offline' - if host_id in self.statuses: - state = 'Online' - args.update({'data': self.statuses[host_id], 'foreground': 'green'}) - host_str = '%s:%d [%s]' % (hostname, port, state) - self.popup.add_line( + host_status = self.statuses.get(host_id) + + state = host_status[1] if host_status else 'Offline' + state_color = 'green' if state in ('Online', 'Connected') else 'red' + host_str = f'{hostname}:{port} [{state}]' + + args = {'data': host_id, 'foreground': state_color} + popup.add_line( host_id, host_str, selectable=True, use_underline=True, **args ) if selected_index: - self.popup.set_selection(selected_index) + popup.set_selection(selected_index) + + self.push_popup(popup, clear=True) self.inlist = True self.refresh() @@ -85,7 +87,7 @@ class ConnectionManager(BaseMode, PopupsHandler): d.addCallback(on_console_start) def _on_connect_fail(self, result): - self.report_message('Failed to connect!', result) + self.report_message('Failed to connect!', result.getErrorMessage()) self.refresh() if hasattr(result, 'getTraceback'): log.exception(result) @@ -128,7 +130,7 @@ class ConnectionManager(BaseMode, PopupsHandler): try: self.hostlist.add_host(hostname, port, username, password) except ValueError as ex: - self.report_message(_('Error adding host'), '%s: %s' % (hostname, ex)) + self.report_message(_('Error adding host'), f'{hostname}: {ex}') else: self.update_select_host_popup() @@ -167,7 +169,9 @@ class ConnectionManager(BaseMode, PopupsHandler): if not self.popup: self.update_select_host_popup() - self.popup.refresh() + if self.popup: + self.popup.refresh() + curses.doupdate() @overrides(BaseMode) diff --git a/deluge/ui/console/modes/eventview.py b/deluge/ui/console/modes/eventview.py index cd3308cf9..b6e63b019 100644 --- a/deluge/ui/console/modes/eventview.py +++ b/deluge/ui/console/modes/eventview.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -100,9 +97,9 @@ class EventView(BaseMode): elif c == curses.KEY_END: self.offset += num_events elif c == ord('j'): - self.offset -= 1 - elif c == ord('k'): self.offset += 1 + elif c == ord('k'): + self.offset -= 1 if self.offset <= 0: self.offset = 0 diff --git a/deluge/ui/console/modes/preferences/__init__.py b/deluge/ui/console/modes/preferences/__init__.py index 15d77c4a8..e827d91a3 100644 --- a/deluge/ui/console/modes/preferences/__init__.py +++ b/deluge/ui/console/modes/preferences/__init__.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from deluge.ui.console.modes.preferences.preferences import Preferences __all__ = ['Preferences'] diff --git a/deluge/ui/console/modes/preferences/preference_panes.py b/deluge/ui/console/modes/preferences/preference_panes.py index 8663d8a9c..b47bc4b07 100644 --- a/deluge/ui/console/modes/preferences/preference_panes.py +++ b/deluge/ui/console/modes/preferences/preference_panes.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,11 +6,9 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging -from deluge.common import is_ip +from deluge.common import is_interface from deluge.decorators import overrides from deluge.i18n import get_languages from deluge.ui.client import client @@ -94,11 +91,12 @@ class BasePreferencePane(BaseInputPane, BaseWindow, PopupsHandler): ) elif ipt.name == 'listen_interface': listen_interface = ipt.get_value().strip() - if is_ip(listen_interface) or not listen_interface: + if is_interface(listen_interface) or not listen_interface: conf_dict['listen_interface'] = listen_interface elif ipt.name == 'outgoing_interface': outgoing_interface = ipt.get_value().strip() - conf_dict['outgoing_interface'] = outgoing_interface + if is_interface(outgoing_interface) or not outgoing_interface: + conf_dict['outgoing_interface'] = outgoing_interface elif ipt.name.startswith('proxy_'): if ipt.name == 'proxy_type': conf_dict.setdefault('proxy', {})['type'] = ipt.get_value() diff --git a/deluge/ui/console/modes/preferences/preferences.py b/deluge/ui/console/modes/preferences/preferences.py index 45a39a621..2c95323c6 100644 --- a/deluge/ui/console/modes/preferences/preferences.py +++ b/deluge/ui/console/modes/preferences/preferences.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from collections import deque @@ -74,7 +71,7 @@ arrow to edit the other value, and escape to get back to the check box. """ -class ZONE(object): +class ZONE: length = 3 CATEGORIES, PREFRENCES, ACTIONS = list(range(length)) diff --git a/deluge/ui/console/modes/torrentdetail.py b/deluge/ui/console/modes/torrentdetail.py index 758cac878..16bd08a5c 100644 --- a/deluge/ui/console/modes/torrentdetail.py +++ b/deluge/ui/console/modes/torrentdetail.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - import logging import deluge.component as component @@ -425,9 +422,9 @@ class TorrentDetail(BaseMode, PopupsHandler): attr = 'bold' if attr: - color_string = '{!%s,%s,%s!}' % (fg, bg, attr) + color_string = f'{{!{fg},{bg},{attr}!}}' else: - color_string = '{!%s,%s!}' % (fg, bg) + color_string = f'{{!{fg},{bg}!}}' # actually draw the dir/file string if fl[3] and fl[4]: # this is an expanded directory @@ -439,7 +436,7 @@ class TorrentDetail(BaseMode, PopupsHandler): r = format_row( [ - '%s%s %s' % (' ' * depth, xchar, fl[0]), + '{}{} {}'.format(' ' * depth, xchar, fl[0]), fsize(fl[2]), fl[5], format_priority(fl[6]), @@ -447,7 +444,7 @@ class TorrentDetail(BaseMode, PopupsHandler): self.column_widths, ) - self.add_string(off, '%s%s' % (color_string, r), trim=False) + self.add_string(off, f'{color_string}{r}', trim=False) off += 1 if fl[3] and fl[4]: @@ -502,7 +499,7 @@ class TorrentDetail(BaseMode, PopupsHandler): download_color = colors.state_color['Downloading'] def add_field(name, row, pre_color='{!info!}', post_color='{!input!}'): - s = '%s%s: %s%s' % ( + s = '{}{}: {}{}'.format( pre_color, torrent_data_fields[name]['name'], post_color, @@ -523,7 +520,7 @@ class TorrentDetail(BaseMode, PopupsHandler): if status['progress'] != 100.0: s += '/%s' % fsize(status['total_wanted']) if status['download_payload_rate'] > 0: - s += ' {!yellow!}@ %s%s' % ( + s += ' {{!yellow!}}@ {}{}'.format( download_color, fsize(status['download_payload_rate']), ) @@ -534,7 +531,7 @@ class TorrentDetail(BaseMode, PopupsHandler): # Print UL info and ratio s = add_field('uploaded', 0, download_color) if status['upload_payload_rate'] > 0: - s += ' {!yellow!}@ %s%s' % ( + s += ' {{!yellow!}}@ {}{}'.format( colors.state_color['Seeding'], fsize(status['upload_payload_rate']), ) @@ -542,13 +539,13 @@ class TorrentDetail(BaseMode, PopupsHandler): row = self.add_string(row, s) # Seed/peer info - s = '{!info!}%s:{!green!} %s {!input!}(%s)' % ( + s = '{{!info!}}{}:{{!green!}} {} {{!input!}}({})'.format( torrent_data_fields['seeds']['name'], status['num_seeds'], status['total_seeds'], ) row = self.add_string(row, s) - s = '{!info!}%s:{!red!} %s {!input!}(%s)' % ( + s = '{{!info!}}{}:{{!red!}} {} {{!input!}}({})'.format( torrent_data_fields['peers']['name'], status['num_peers'], status['total_peers'], @@ -557,7 +554,7 @@ class TorrentDetail(BaseMode, PopupsHandler): # Tracker tracker_color = '{!green!}' if status['message'] == 'OK' else '{!red!}' - s = '{!info!}%s: {!magenta!}%s{!input!} says "%s%s{!input!}"' % ( + s = '{{!info!}}{}: {{!magenta!}}{}{{!input!}} says "{}{}{{!input!}}"'.format( torrent_data_fields['tracker']['name'], status['tracker_host'], tracker_color, @@ -566,13 +563,13 @@ class TorrentDetail(BaseMode, PopupsHandler): row = self.add_string(row, s) # Pieces and availability - s = '{!info!}%s: {!yellow!}%s {!input!}x {!yellow!}%s' % ( + 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!}%s: {!input!}%s' % ( + s += '{{!info!}}{}: {{!input!}}{}'.format( torrent_data_fields['seed_rank']['name'], status['seed_rank'], ) @@ -878,7 +875,7 @@ class TorrentDetail(BaseMode, PopupsHandler): idx += 1 continue if num == idx: - return '%s%s/' % (path, element[0]) + return f'{path}{element[0]}/' if element[4]: i = self._get_full_folder_path( num, element[3], path + element[0] + '/', idx + 1 @@ -923,7 +920,7 @@ class TorrentDetail(BaseMode, PopupsHandler): self.popup.close(None, call_cb=False) return old_fname = self._get_full_folder_path(self.current_file_idx) - new_fname = '%s/%s/' % ( + new_fname = '{}/{}/'.format( old_fname.strip('/').rpartition('/')[0], result['new_foldername']['value'], ) @@ -949,7 +946,7 @@ class TorrentDetail(BaseMode, PopupsHandler): ): self.popup.close(None, call_cb=False) return - fname = '%s/%s' % ( + fname = '{}/{}'.format( self.full_names[idx].rpartition('/')[0], result['new_filename']['value'], ) @@ -1019,8 +1016,8 @@ class TorrentDetail(BaseMode, PopupsHandler): 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() + elif c == ord('k'): + self.file_list_up() self.refresh() diff --git a/deluge/ui/console/modes/torrentlist/__init__.py b/deluge/ui/console/modes/torrentlist/__init__.py index 18c4db377..48c60ce5a 100644 --- a/deluge/ui/console/modes/torrentlist/__init__.py +++ b/deluge/ui/console/modes/torrentlist/__init__.py @@ -1,7 +1,4 @@ -from __future__ import unicode_literals - - -class ACTION(object): +class ACTION: PAUSE = 'pause' RESUME = 'resume' REANNOUNCE = 'update_tracker' diff --git a/deluge/ui/console/modes/torrentlist/add_torrents_popup.py b/deluge/ui/console/modes/torrentlist/add_torrents_popup.py index b0ac483a0..3ff9ab78d 100644 --- a/deluge/ui/console/modes/torrentlist/add_torrents_popup.py +++ b/deluge/ui/console/modes/torrentlist/add_torrents_popup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.common @@ -40,7 +37,7 @@ def show_torrent_add_popup(torrentlist): def fail_cb(msg, url): log.debug('failed to add torrent: %s: %s', url, msg) - error_msg = '{!input!} * %s: {!error!}%s' % (url, msg) + error_msg = f'{{!input!}} * {url}: {{!error!}}{msg}' report_add_status(torrentlist, 0, 1, [error_msg]) def success_cb(tid, url): diff --git a/deluge/ui/console/modes/torrentlist/filtersidebar.py b/deluge/ui/console/modes/torrentlist/filtersidebar.py index 0f39b5c3c..982e2457a 100644 --- a/deluge/ui/console/modes/torrentlist/filtersidebar.py +++ b/deluge/ui/console/modes/torrentlist/filtersidebar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import curses import logging diff --git a/deluge/ui/console/modes/torrentlist/queue_mode.py b/deluge/ui/console/modes/torrentlist/queue_mode.py index 0c44aafdf..33af0135d 100644 --- a/deluge/ui/console/modes/torrentlist/queue_mode.py +++ b/deluge/ui/console/modes/torrentlist/queue_mode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from deluge.ui.client import client from deluge.ui.console.utils import curses_util as util from deluge.ui.console.widgets.popup import MessagePopup, SelectablePopup @@ -38,7 +35,7 @@ Change queue position of selected torrents """ -class QueueMode(object): +class QueueMode: def __init__(self, torrentslist, torrent_ids): self.torrentslist = torrentslist self.torrentview = torrentslist.torrentview diff --git a/deluge/ui/console/modes/torrentlist/search_mode.py b/deluge/ui/console/modes/torrentlist/search_mode.py index 57a8e5f64..6f79628fb 100644 --- a/deluge/ui/console/modes/torrentlist/search_mode.py +++ b/deluge/ui/console/modes/torrentlist/search_mode.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,11 +6,8 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging -from deluge.common import PY2 from deluge.decorators import overrides from deluge.ui.console.modes.basemode import InputKeyHandler, move_cursor from deluge.ui.console.modes.torrentlist.torrentactions import torrent_actions_popup @@ -49,7 +45,7 @@ SEARCH_FORMAT = { class SearchMode(InputKeyHandler): def __init__(self, torrentlist): - super(SearchMode, self).__init__() + super().__init__() self.torrentlist = torrentlist self.torrentview = torrentlist.torrentview self.search_state = SEARCH_EMPTY @@ -176,7 +172,7 @@ class SearchMode(InputKeyHandler): elif c > 31 and c < 256: old_search_string = self.search_string stroke = chr(c) - uchar = '' if PY2 else stroke + uchar = stroke while not uchar: try: uchar = stroke.decode(self.torrentlist.encoding) diff --git a/deluge/ui/console/modes/torrentlist/torrentactions.py b/deluge/ui/console/modes/torrentlist/torrentactions.py index f3cd39509..6450118c6 100644 --- a/deluge/ui/console/modes/torrentlist/torrentactions.py +++ b/deluge/ui/console/modes/torrentlist/torrentactions.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os @@ -46,7 +43,7 @@ def action_remove(mode=None, torrent_ids=None, **kwargs): if errors: error_msgs = '' for t_id, e_msg in errors: - error_msgs += 'Error removing torrent %s : %s\n' % (t_id, e_msg) + error_msgs += f'Error removing torrent {t_id} : {e_msg}\n' mode.report_message( 'Error(s) occured when trying to delete torrent(s).', error_msgs ) @@ -77,7 +74,7 @@ def action_remove(mode=None, torrent_ids=None, **kwargs): show_max = 6 for i, (name, state) in enumerate(status): color = colors.state_color[state] - rem_msg += '\n %s* {!input!}%s' % (color, name) + rem_msg += f'\n {color}* {{!input!}}{name}' if i == show_max - 1: if i < len(status) - 1: rem_msg += '\n {!red!}And %i more' % (len(status) - show_max) diff --git a/deluge/ui/console/modes/torrentlist/torrentlist.py b/deluge/ui/console/modes/torrentlist/torrentlist.py index a427d65b0..d3c32ec0e 100644 --- a/deluge/ui/console/modes/torrentlist/torrentlist.py +++ b/deluge/ui/console/modes/torrentlist/torrentlist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from collections import deque @@ -179,7 +176,7 @@ class TorrentList(BaseMode, PopupsHandler): @overrides(BaseMode) def resume(self): - super(TorrentList, self).resume() + super().resume() @overrides(BaseMode) def on_resize(self, rows, cols): @@ -222,7 +219,9 @@ class TorrentList(BaseMode, PopupsHandler): # Update the status bars statusbar_args = {'scr': self.stdscr, 'bottombar_help': True} if self.torrentview.curr_filter is not None: - statusbar_args['topbar'] = '%s {!filterstatus!}Current filter: %s' % ( + statusbar_args[ + 'topbar' + ] = '{} {{!filterstatus!}}Current filter: {}'.format( self.statusbars.topbar, self.torrentview.curr_filter, ) diff --git a/deluge/ui/console/modes/torrentlist/torrentview.py b/deluge/ui/console/modes/torrentlist/torrentview.py index 67de3e786..1ce509788 100644 --- a/deluge/ui/console/modes/torrentlist/torrentview.py +++ b/deluge/ui/console/modes/torrentlist/torrentview.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- # # 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. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -90,7 +87,7 @@ for col_i, col_name in enumerate(torrentviewcolumns.column_pref_names): class TorrentView(InputKeyHandler): def __init__(self, torrentlist, config): - super(TorrentView, self).__init__() + super().__init__() self.torrentlist = torrentlist self.config = config self.filter_dict = {} @@ -331,7 +328,7 @@ class TorrentView(InputKeyHandler): self.torrentlist.add_string( currow + self.torrentlist_offset, - '%s%s' % (colorstr, row[0]), + f'{colorstr}{row[0]}', trim=False, scr=self.torrentlist.torrentview_panel, ) @@ -467,9 +464,9 @@ class TorrentView(InputKeyHandler): ) self.torrentlist.refresh() elif c == ord('j'): - affected_lines = self._scroll_up(1) - elif c == ord('k'): affected_lines = self._scroll_down(1) + elif c == ord('k'): + affected_lines = self._scroll_up(1) elif c == ord('m'): self.mark_unmark(self.cursel) affected_lines = [self.cursel] diff --git a/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py b/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py index 9dff84306..586a56978 100644 --- a/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py +++ b/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from deluge.decorators import overrides from deluge.ui.console.utils import curses_util as util from deluge.ui.console.utils.column import torrent_data_fields diff --git a/deluge/ui/console/parser.py b/deluge/ui/console/parser.py index 917773aef..c0686b156 100644 --- a/deluge/ui/console/parser.py +++ b/deluge/ui/console/parser.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import print_function, unicode_literals - import argparse import shlex @@ -29,7 +26,7 @@ class ConsoleBaseParser(argparse.ArgumentParser): # Handle epilog manually to keep the text formatting epilog = self.epilog self.epilog = '' - help_str = super(ConsoleBaseParser, self).format_help() + help_str = super().format_help() if epilog is not None: help_str += epilog self.epilog = epilog @@ -51,7 +48,7 @@ class ConsoleCommandParser(ConsoleBaseParser): for cmd_line in cmd_lines: cmds = shlex.split(cmd_line) - cmd_options = super(ConsoleCommandParser, self).parse_args(args=cmds) + cmd_options = super().parse_args(args=cmds) cmd_options.command = cmds[0] command_options.append(cmd_options) @@ -96,7 +93,7 @@ class ConsoleCommandParser(ConsoleBaseParser): options = self.base_parser.parse_args(args=args) options.parsed_cmds = [] else: - options = super(ConsoleCommandParser, self).parse_args(args=args) + options = super().parse_args(args=args) options.parsed_cmds = [options] if not hasattr(options, 'remaining'): @@ -107,7 +104,7 @@ class ConsoleCommandParser(ConsoleBaseParser): class OptionParser(ConsoleBaseParser): def __init__(self, **kwargs): - super(OptionParser, self).__init__(**kwargs) + super().__init__(**kwargs) self.formatter = ConsoleColorFormatter() def exit(self, status=0, msg=None): @@ -139,5 +136,5 @@ class OptionParser(ConsoleBaseParser): def format_help(self): """Return help formatted with colors.""" - help_str = super(OptionParser, self).format_help() + help_str = super().format_help() return self.formatter.format_colors(help_str) diff --git a/deluge/ui/console/utils/colors.py b/deluge/ui/console/utils/colors.py index 587c1f3f6..cc414fea5 100644 --- a/deluge/ui/console/utils/colors.py +++ b/deluge/ui/console/utils/colors.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import re @@ -91,8 +88,8 @@ def init_colors(): curses.init_pair(counter, fg, bg) color_pairs[(fg_name, bg_name)] = counter counter += 1 - except curses.error as ex: - log.warning('Error: %s', ex) + except (curses.error, ValueError) as ex: + log.debug(f'Color pair {fg_name} {bg_name} not available: {ex}') return counter # Create the color_pairs dict @@ -271,7 +268,7 @@ def parse_color_string(string): last_color_attr = color_pair attrs = attrs[2:] # Remove colors except KeyError: - raise BadColorString('Bad color value in tag: %s,%s' % (fg, bg)) + raise BadColorString(f'Bad color value in tag: {fg},{bg}') # Check for additional attributes and OR them to the color_pair color_pair = apply_attrs(color_pair, attrs) last_color_attr = color_pair @@ -292,7 +289,7 @@ def parse_color_string(string): return ret -class ConsoleColorFormatter(object): +class ConsoleColorFormatter: """ Format help in a way suited to deluge CmdLine mode - colors, format, indentation... """ diff --git a/deluge/ui/console/utils/column.py b/deluge/ui/console/utils/column.py index d93215957..ecbe04ba3 100644 --- a/deluge/ui/console/utils/column.py +++ b/deluge/ui/console/utils/column.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import copy import logging diff --git a/deluge/ui/console/utils/common.py b/deluge/ui/console/utils/common.py index df1c07917..fdc88c402 100644 --- a/deluge/ui/console/utils/common.py +++ b/deluge/ui/console/utils/common.py @@ -1,12 +1,9 @@ -# -*- coding: utf-8 -*- # # 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. # -from __future__ import unicode_literals - TORRENT_OPTIONS = { 'max_download_speed': float, 'max_upload_speed': float, diff --git a/deluge/ui/console/utils/curses_util.py b/deluge/ui/console/utils/curses_util.py index a0cd6dc4b..50b044402 100644 --- a/deluge/ui/console/utils/curses_util.py +++ b/deluge/ui/console/utils/curses_util.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - try: import curses except ImportError: @@ -39,7 +36,7 @@ def is_int_chr(c): return c > 47 and c < 58 -class Curser(object): +class Curser: INVISIBLE = 0 NORMAL = 1 VERY_VISIBLE = 2 @@ -59,7 +56,7 @@ def safe_curs_set(visibility): pass -class ReadState(object): +class ReadState: IGNORED = 0 READ = 1 CHANGED = 2 diff --git a/deluge/ui/console/utils/format_utils.py b/deluge/ui/console/utils/format_utils.py index 029fb2011..50ec1915f 100644 --- a/deluge/ui/console/utils/format_utils.py +++ b/deluge/ui/console/utils/format_utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import re from collections import deque from unicodedata import east_asian_width, normalize @@ -98,7 +95,7 @@ def f_seedrank_dash(seed_rank, seeding_time): def ftotal_sized(first, second): - return '%s (%s)' % ( + return '{} ({})'.format( deluge.common.fsize(first, shortform=True), deluge.common.fsize(second, shortform=True), ) @@ -159,7 +156,7 @@ def format_column(col, lim): if size >= lim - 1: return trim_string(col, lim, dbls > 0) else: - return '%s%s' % (col, ' ' * (lim - size)) + return '{}{}'.format(col, ' ' * (lim - size)) def format_row(row, column_widths): @@ -213,7 +210,7 @@ def wrap_string(string, width, min_lines=0, strip_colors=True): mtc = mtchs.popleft() - offset clr = clrs.popleft() end_pos += len(clr) - s = '%s%s%s' % (s[:mtc], clr, s[mtc:]) + s = f'{s[:mtc]}{clr}{s[mtc:]}' return s for s in s1: @@ -238,11 +235,11 @@ def wrap_string(string, width, min_lines=0, strip_colors=True): else: cstr = s - def append_indent(l, string, offset): + def append_indent(line, string, offset): """Prepends indent to string if specified""" if indent and offset != 0: string = indent + string - l.append(string) + line.append(string) while cstr: # max with for a line. If indent is specified, we account for this @@ -290,7 +287,7 @@ def wrap_string(string, width, min_lines=0, strip_colors=True): last_color_string = '' for i, line in enumerate(ret): if i != 0: - ret[i] = '%s%s' % (last_color_string, ret[i]) + ret[i] = f'{last_color_string}{ret[i]}' colors = re.findall('\\{![^!]+!\\}', line) if colors: @@ -313,9 +310,9 @@ def pad_string(string, length, character=' ', side='right'): w = strwidth(string) diff = length - w if side == 'left': - return '%s%s' % (character * diff, string) + return f'{character * diff}{string}' elif side == 'right': - return '%s%s' % (string, character * diff) + return f'{string}{character * diff}' def delete_alt_backspace(input_text, input_cursor, sep_chars=' *?!._~-#$^;\'"/'): diff --git a/deluge/ui/console/widgets/__init__.py b/deluge/ui/console/widgets/__init__.py index a11e3f2b8..bc88a3b6b 100644 --- a/deluge/ui/console/widgets/__init__.py +++ b/deluge/ui/console/widgets/__init__.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from deluge.ui.console.widgets.inputpane import BaseInputPane from deluge.ui.console.widgets.statusbars import StatusBars from deluge.ui.console.widgets.window import BaseWindow diff --git a/deluge/ui/console/widgets/fields.py b/deluge/ui/console/widgets/fields.py index 021cab738..d8d892d52 100644 --- a/deluge/ui/console/widgets/fields.py +++ b/deluge/ui/console/widgets/fields.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> @@ -9,12 +8,9 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os -from deluge.common import PY2 from deluge.decorators import overrides from deluge.ui.console.modes.basemode import InputKeyHandler from deluge.ui.console.utils import colors @@ -35,7 +31,7 @@ log = logging.getLogger(__name__) class BaseField(InputKeyHandler): def __init__(self, parent=None, name=None, selectable=True, **kwargs): - super(BaseField, self).__init__() + super().__init__() self.name = name self.parent = parent self.fmt_keys = {} @@ -74,7 +70,7 @@ class BaseField(InputKeyHandler): def build_fmt_string(self, focused, active, value_key='msg', **kwargs): color_key, font_key = self.get_fmt_keys(focused, active, **kwargs) - return '{!%%(%s)s,%%(%s)s!}%%(%s)s{!%%(%s)s!}' % ( + return '{{!%({})s,%({})s!}}%({})s{{!%({})s!}}'.format( color_key, font_key, value_key, @@ -176,7 +172,7 @@ class InfoField(NoInputField): NoInputField.__init__(self, parent=parent, name=name, **kwargs) self.label = label self.value = value - self.txt = '%s %s' % (label, value) + self.txt = f'{label} {value}' @overrides(BaseField) def render(self, screen, row, col=0, **kwargs): @@ -187,9 +183,9 @@ class InfoField(NoInputField): def set_value(self, v): self.value = v if isinstance(v, float): - self.txt = '%s %.2f' % (self.label, self.value) + self.txt = f'{self.label} {self.value:.2f}' else: - self.txt = '%s %s' % (self.label, self.value) + self.txt = f'{self.label} {self.value}' class CheckedInput(InputField): @@ -202,7 +198,7 @@ class CheckedInput(InputField): checked_char='X', unchecked_char=' ', checkbox_format='[%s] ', - **kwargs + **kwargs, ): InputField.__init__(self, parent, name, message, **kwargs) self.set_value(checked) @@ -231,9 +227,7 @@ class CheckedInput(InputField): @overrides(BaseField) def get_fmt_keys(self, focused, active, **kwargs): - color_key, font_key = super(CheckedInput, self).get_fmt_keys( - focused, active, **kwargs - ) + color_key, font_key = super().get_fmt_keys(focused, active, **kwargs) if self.checked: color_key += '_checked' font_key += '_checked' @@ -284,7 +278,7 @@ class CheckedPlusInput(CheckedInput): child_always_visible=False, show_usage_hints=True, msg_fmt='%s ', - **kwargs + **kwargs, ): CheckedInput.__init__(self, parent, name, message, **kwargs) self.child = child @@ -372,7 +366,7 @@ class IntSpinInput(InputField): incr_large=10, strict_validation=False, fmt='%d', - **kwargs + **kwargs, ): InputField.__init__(self, parent, name, message, **kwargs) self.convert_func = int @@ -618,7 +612,7 @@ class SelectInput(InputField): active_index, active_default=False, require_select_action=True, - **kwargs + **kwargs, ): InputField.__init__(self, parent, name, message, **kwargs) self.opts = opts @@ -667,9 +661,7 @@ class SelectInput(InputField): @overrides(BaseField) def get_fmt_keys(self, focused, active, selected=False, **kwargs): - color_key, font_key = super(SelectInput, self).get_fmt_keys( - focused, active, **kwargs - ) + color_key, font_key = super().get_fmt_keys(focused, active, **kwargs) if selected: color_key += '_selected' font_key += '_selected' @@ -739,7 +731,7 @@ class TextInput(InputField): value, complete=False, activate_input=False, - **kwargs + **kwargs, ): InputField.__init__(self, parent, name, message, **kwargs) self.move_func = move_func @@ -815,7 +807,7 @@ class TextInput(InputField): focused=True, col=0, cursor_offset=0, - **kwargs + **kwargs, ): if not self.value and not active and len(self.default_value) != 0: self.value = self.default_value @@ -951,7 +943,7 @@ class TextInput(InputField): elif c > 31 and c < 256: # Emulate getwch stroke = chr(c) - uchar = '' if PY2 else stroke + uchar = stroke while not uchar: try: uchar = stroke.decode(self.parent.encoding) @@ -1081,7 +1073,7 @@ class ComboInput(InputField): choice[1], selectable=True, selected=choice[0] == self.get_value(), - **args + **args, ) self.parent.push_popup(select_popup) return util.ReadState.CHANGED @@ -1149,7 +1141,7 @@ class TextArea(TextField): for i, line in enumerate(lines): self.parent.add_string( row + i, - '%s%s' % (color, line), + f'{color}{line}', scr=screen, col=col, pad=False, @@ -1176,7 +1168,7 @@ class DividerField(NoInputField): selectable=False, fill_width=True, value_fmt='%s', - **kwargs + **kwargs, ): NoInputField.__init__( self, parent=parent, name=name, selectable=selectable, **kwargs diff --git a/deluge/ui/console/widgets/inputpane.py b/deluge/ui/console/widgets/inputpane.py index 097a6cb8d..d8d217501 100644 --- a/deluge/ui/console/widgets/inputpane.py +++ b/deluge/ui/console/widgets/inputpane.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> @@ -9,8 +8,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from deluge.decorators import overrides diff --git a/deluge/ui/console/widgets/popup.py b/deluge/ui/console/widgets/popup.py index d588bbb24..4b0d0274e 100644 --- a/deluge/ui/console/widgets/popup.py +++ b/deluge/ui/console/widgets/popup.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from deluge.decorators import overrides @@ -25,7 +22,7 @@ except ImportError: log = logging.getLogger(__name__) -class ALIGN(object): +class ALIGN: TOP_LEFT = 1 TOP_CENTER = 2 TOP_RIGHT = 3 @@ -38,7 +35,7 @@ class ALIGN(object): DEFAULT = MIDDLE_CENTER -class PopupsHandler(object): +class PopupsHandler: def __init__(self): self._popups = [] diff --git a/deluge/ui/console/widgets/sidebar.py b/deluge/ui/console/widgets/sidebar.py index cc237174d..4015a1375 100644 --- a/deluge/ui/console/widgets/sidebar.py +++ b/deluge/ui/console/widgets/sidebar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import curses import logging diff --git a/deluge/ui/console/widgets/statusbars.py b/deluge/ui/console/widgets/statusbars.py index fcf4f2f41..1b9173707 100644 --- a/deluge/ui/console/widgets/statusbars.py +++ b/deluge/ui/console/widgets/statusbars.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,13 +6,12 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import deluge.common import deluge.component as component -from deluge.core.preferencesmanager import DEFAULT_PREFS from deluge.ui.client import client +DEFAULT_DAEMON_PORT = 58846 + class StatusBars(component.Component): def __init__(self): @@ -38,19 +36,23 @@ class StatusBars(component.Component): def on_get_session_status(status): self.upload = deluge.common.fsize(status['payload_upload_rate']) self.download = deluge.common.fsize(status['payload_download_rate']) - self.connections = status['num_peers'] + self.connections = status['peer.num_peers_connected'] if 'dht_nodes' in status: - self.dht = status['dht_nodes'] + self.dht = status['dht.dht_nodes'] self.update_statusbars() def on_get_external_ip(external_ip): self.external_ip = external_ip - keys = ['num_peers', 'payload_upload_rate', 'payload_download_rate'] + keys = [ + 'peer.num_peers_connected', + 'payload_upload_rate', + 'payload_download_rate', + ] if self.config['dht']: - keys.append('dht_nodes') + keys.append('dht.dht_nodes') client.core.get_session_status(keys).addCallback(on_get_session_status) client.core.get_external_ip().addCallback(on_get_external_ip) @@ -76,7 +78,7 @@ class StatusBars(component.Component): connection_info += '{!white,blue,bold!}@{!red,blue,bold!}%s' # Port - if info[1] == DEFAULT_PREFS['daemon_port']: + if info[1] == DEFAULT_DAEMON_PORT: connection_info += '{!white,blue!}:%s' else: connection_info += '{!status!}:%s' diff --git a/deluge/ui/console/widgets/window.py b/deluge/ui/console/widgets/window.py index 2ef35281e..77aff8817 100644 --- a/deluge/ui/console/widgets/window.py +++ b/deluge/ui/console/widgets/window.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Nick Lanham <nick@afternight.org> # Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com> @@ -9,8 +8,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from deluge.ui.console.modes.basemode import add_string, mkpad, mkpanel @@ -24,7 +21,7 @@ except ImportError: log = logging.getLogger(__name__) -class BaseWindow(object): +class BaseWindow: """ BaseWindow creates a curses screen to be used for showing panels and popup dialogs """ diff --git a/deluge/ui/coreconfig.py b/deluge/ui/coreconfig.py index ed6b614a2..1e2927b5e 100644 --- a/deluge/ui/coreconfig.py +++ b/deluge/ui/coreconfig.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component diff --git a/deluge/ui/countries.py b/deluge/ui/countries.py index af390853e..eb94df6d9 100644 --- a/deluge/ui/countries.py +++ b/deluge/ui/countries.py @@ -1,10 +1,7 @@ -# -*- coding: utf-8 -*- # # This file is public domain. # -from __future__ import unicode_literals - # ISO 3166-1 country names and codes COUNTRIES = { 'AF': _('Afghanistan'), diff --git a/deluge/ui/data/share/applications/deluge.desktop.in b/deluge/ui/data/share/applications/deluge.desktop.in index c952d424a..4335b6da4 100644 --- a/deluge/ui/data/share/applications/deluge.desktop.in +++ b/deluge/ui/data/share/applications/deluge.desktop.in @@ -4,6 +4,7 @@ _Name=Deluge _GenericName=BitTorrent Client _X-GNOME-FullName=Deluge BitTorrent Client _Comment=Download and share files over BitTorrent +_Keywords=bittorrent;torrent;magnet;download;p2p;torrents;downloading;uploading;share;sharing; TryExec=deluge-gtk Exec=deluge-gtk %U Icon=deluge diff --git a/deluge/ui/gtk3/__init__.py b/deluge/ui/gtk3/__init__.py index 8e8b19613..8db2773e4 100644 --- a/deluge/ui/gtk3/__init__.py +++ b/deluge/ui/gtk3/__init__.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from os import environ @@ -25,7 +22,7 @@ class Gtk(UI): cmd_description = """GTK-based graphical user interface""" def __init__(self, *args, **kwargs): - super(Gtk, self).__init__( + super().__init__( 'gtk', *args, description='Starts the Deluge GTK+ interface', **kwargs ) @@ -42,7 +39,7 @@ class Gtk(UI): ) def start(self): - super(Gtk, self).start() + super().start() import deluge.common from .gtkui import GtkUI diff --git a/deluge/ui/gtk3/aboutdialog.py b/deluge/ui/gtk3/aboutdialog.py index 9974a13de..fe3452b82 100644 --- a/deluge/ui/gtk3/aboutdialog.py +++ b/deluge/ui/gtk3/aboutdialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Marcos Mobley ('markybob') <markybob@gmail.com> # @@ -7,7 +6,7 @@ # See LICENSE for more details. # -from __future__ import unicode_literals +from datetime import date from gi.repository import Gtk @@ -18,7 +17,7 @@ from deluge.ui.client import client from .common import get_deluge_icon, get_pixbuf -class AboutDialog(object): +class AboutDialog: def __init__(self): self.about = Gtk.AboutDialog() self.about.set_transient_for(component.get('MainWindow').window) @@ -38,7 +37,7 @@ class AboutDialog(object): self.about.set_copyright( _('Copyright %(year_start)s-%(year_end)s Deluge Team') - % {'year_start': 2007, 'year_end': 2019} + % {'year_start': 2007, 'year_end': date.today().year} ) self.about.set_comments( _('A peer-to-peer file sharing program\nutilizing the BitTorrent protocol.') diff --git a/deluge/ui/gtk3/addtorrentdialog.py b/deluge/ui/gtk3/addtorrentdialog.py index 81b8dbaf3..cf3851d6c 100644 --- a/deluge/ui/gtk3/addtorrentdialog.py +++ b/deluge/ui/gtk3/addtorrentdialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # @@ -7,11 +6,9 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - import logging import os -from base64 import b64encode +from base64 import b64decode, b64encode from xml.sax.saxutils import escape as xml_escape from xml.sax.saxutils import unescape as xml_unescape @@ -19,6 +16,7 @@ from gi.repository import Gtk from gi.repository.GObject import TYPE_INT64, TYPE_UINT64 import deluge.component as component +from deluge.bencode import bdecode from deluge.common import ( create_magnet_uri, decode_bytes, @@ -271,6 +269,7 @@ class AddTorrentDialog(component.Component): return if metadata: + metadata = bdecode(b64decode(metadata)) info = TorrentInfo.from_metadata(metadata, [[t] for t in trackers]) self.files[info_hash] = info.files self.infos[info_hash] = info.filedata @@ -775,7 +774,7 @@ class AddTorrentDialog(component.Component): else: ErrorDialog( _('Invalid URL'), - '%s %s' % (url, _('is not a valid URL.')), + '{} {}'.format(url, _('is not a valid URL.')), self.dialog, ).run() @@ -817,7 +816,7 @@ class AddTorrentDialog(component.Component): dialog.destroy() ErrorDialog( _('Download Failed'), - '%s %s' % (_('Failed to download:'), url), + '{} {}'.format(_('Failed to download:'), url), details=result.getErrorMessage(), parent=self.dialog, ).run() diff --git a/deluge/ui/gtk3/common.py b/deluge/ui/gtk3/common.py index e7b46c8d5..42a14b407 100644 --- a/deluge/ui/gtk3/common.py +++ b/deluge/ui/gtk3/common.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Marcos Mobley ('markybob') <markybob@gmail.com> # @@ -7,15 +6,13 @@ # See LICENSE for more details. # """Common functions for various parts of gtkui to use.""" -from __future__ import unicode_literals - import contextlib import logging import os +import pickle import shutil import sys -import six.moves.cPickle as pickle # noqa: N813 from gi.repository.Gdk import SELECTION_CLIPBOARD, SELECTION_PRIMARY, Display from gi.repository.GdkPixbuf import Colorspace, Pixbuf from gi.repository.GLib import GError @@ -29,7 +26,7 @@ from gi.repository.Gtk import ( SortType, ) -from deluge.common import PY2, get_pixmap, is_ip, osx_check, windows_check +from deluge.common import get_pixmap, is_ip, osx_check, windows_check log = logging.getLogger(__name__) @@ -62,12 +59,36 @@ def create_blank_pixbuf(size=16): return pix -def get_pixbuf(filename): +def get_pixbuf(filename: str, size: int = 0) -> Pixbuf: + """Creates a new pixbuf by loading an image from file + + Args: + filename: An image file to load + size: Specify a size constraint (equal aspect ratio) + + Returns: + A newly created pixbuf + + """ + # Skip ico and gif that cause Pixbuf crash on Windows + # https://dev.deluge-torrent.org/ticket/3501 + if windows_check() and filename.endswith(('.ico', '.gif')): + return create_blank_pixbuf(size) + + if not os.path.isabs(filename): + filename = get_pixmap(filename) + + pixbuf = None try: - return Pixbuf.new_from_file(get_pixmap(filename)) + if size: + pixbuf = Pixbuf.new_from_file_at_size(filename, size, size) + else: + pixbuf = Pixbuf.new_from_file(filename) except GError as ex: + # Failed to load the pixbuf (Bad image file), so return a blank pixbuf. log.warning(ex) - return create_blank_pixbuf() + + return pixbuf or create_blank_pixbuf(size or 16) # Status icons.. Create them from file only once to avoid constantly re-creating them. @@ -79,17 +100,6 @@ icon_queued = get_pixbuf('queued16.png') icon_checking = get_pixbuf('checking16.png') -def get_pixbuf_at_size(filename, size): - if not os.path.isabs(filename): - filename = get_pixmap(filename) - try: - return Pixbuf.new_from_file_at_size(filename, size, size) - except GError as ex: - # Failed to load the pixbuf (Bad image file), so return a blank pixbuf. - log.warning(ex) - return create_blank_pixbuf(size) - - def get_logo(size): """A Deluge logo. @@ -102,7 +112,7 @@ def get_logo(size): filename = 'deluge.svg' if windows_check(): filename = 'deluge.png' - return get_pixbuf_at_size(filename, size) + return get_pixbuf(filename, size) def build_menu_radio_list( @@ -232,14 +242,11 @@ def associate_magnet_links(overwrite=False): """ if windows_check(): - try: - import winreg - except ImportError: - import _winreg as winreg # For Python 2. + import winreg try: hkey = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'Magnet') - except WindowsError: + except OSError: overwrite = True else: winreg.CloseKey(hkey) @@ -248,7 +255,7 @@ def associate_magnet_links(overwrite=False): deluge_exe = os.path.join(os.path.dirname(sys.executable), 'deluge.exe') try: magnet_key = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, 'Magnet') - except WindowsError: + except OSError: # Could not create for all users, falling back to current user magnet_key = winreg.CreateKey( winreg.HKEY_CURRENT_USER, 'Software\\Classes\\Magnet' @@ -257,14 +264,12 @@ def associate_magnet_links(overwrite=False): winreg.SetValue(magnet_key, '', winreg.REG_SZ, 'URL:Magnet Protocol') winreg.SetValueEx(magnet_key, 'URL Protocol', 0, winreg.REG_SZ, '') winreg.SetValueEx(magnet_key, 'BrowserFlags', 0, winreg.REG_DWORD, 0x8) - winreg.SetValue( - magnet_key, 'DefaultIcon', winreg.REG_SZ, '{},0'.format(deluge_exe) - ) + winreg.SetValue(magnet_key, 'DefaultIcon', winreg.REG_SZ, f'{deluge_exe},0') winreg.SetValue( magnet_key, r'shell\open\command', winreg.REG_SZ, - '"{}" "%1"'.format(deluge_exe), + f'"{deluge_exe}" "%1"', ) winreg.CloseKey(magnet_key) @@ -320,7 +325,7 @@ def save_pickled_state_file(filename, state): if os.path.isfile(filepath): log.debug('Creating backup of %s at: %s', filename, filepath_bak) shutil.copy2(filepath, filepath_bak) - except IOError as ex: + except OSError as ex: log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex) else: log.info('Saving the %s at: %s', filename, filepath) @@ -331,7 +336,7 @@ def save_pickled_state_file(filename, state): _file.flush() os.fsync(_file.fileno()) shutil.move(filepath_tmp, filepath) - except (IOError, EOFError, pickle.PicklingError) as ex: + except (OSError, EOFError, pickle.PicklingError) as ex: log.error('Unable to save %s: %s', filename, ex) if os.path.isfile(filepath_bak): log.info('Restoring backup of %s from: %s', filename, filepath_bak) @@ -356,11 +361,8 @@ def load_pickled_state_file(filename): log.info('Opening %s for load: %s', filename, _filepath) try: with open(_filepath, 'rb') as _file: - if PY2: - state = pickle.load(_file) - else: - state = pickle.load(_file, encoding='utf8') - except (IOError, pickle.UnpicklingError) as ex: + state = pickle.load(_file, encoding='utf8') + except (OSError, pickle.UnpicklingError) as ex: log.warning('Unable to load %s: %s', _filepath, ex) else: log.info('Successfully loaded %s: %s', filename, _filepath) diff --git a/deluge/ui/gtk3/connectionmanager.py b/deluge/ui/gtk3/connectionmanager.py index d5883c4b3..b53dd8e04 100644 --- a/deluge/ui/gtk3/connectionmanager.py +++ b/deluge/ui/gtk3/connectionmanager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,11 +6,10 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os -from socket import gaierror, gethostbyname +from socket import gaierror, getaddrinfo +from urllib.parse import urlparse from gi.repository import Gtk from twisted.internet import defer, reactor @@ -26,12 +24,6 @@ from deluge.ui.hostlist import DEFAULT_PORT, LOCALHOST, HostList from .common import get_clipboard_text from .dialogs import AuthenticationDialog, ErrorDialog -try: - from urllib.parse import urlparse -except ImportError: - # PY2 fallback - from urlparse import urlparse # pylint: disable=ungrouped-imports - log = logging.getLogger(__name__) HOSTLIST_COL_ID = 0 @@ -230,7 +222,7 @@ class ConnectionManager(component.Component): __, host, port, __, __, status, __, __ = model[row] try: - gethostbyname(host) + getaddrinfo(host, None) except gaierror as ex: log.error( 'Error resolving host %s to ip: %s', row[HOSTLIST_COL_HOST], ex.args[1] diff --git a/deluge/ui/gtk3/createtorrentdialog.py b/deluge/ui/gtk3/createtorrentdialog.py index 1e5e73cb6..e9f16906c 100644 --- a/deluge/ui/gtk3/createtorrentdialog.py +++ b/deluge/ui/gtk3/createtorrentdialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - import logging import os.path from base64 import b64encode @@ -31,7 +28,7 @@ from .torrentview_data_funcs import cell_data_size log = logging.getLogger(__name__) -class CreateTorrentDialog(object): +class CreateTorrentDialog: def __init__(self): pass diff --git a/deluge/ui/gtk3/details_tab.py b/deluge/ui/gtk3/details_tab.py index 2431e0836..04a5eabfe 100644 --- a/deluge/ui/gtk3/details_tab.py +++ b/deluge/ui/gtk3/details_tab.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from xml.sax.saxutils import escape as xml_escape @@ -23,7 +20,7 @@ log = logging.getLogger(__name__) class DetailsTab(Tab): def __init__(self): - super(DetailsTab, self).__init__('Details', 'details_tab', 'details_tab_label') + super().__init__('Details', 'details_tab', 'details_tab_label') self.add_tab_widget('summary_name', None, ('name',)) self.add_tab_widget('summary_total_size', fsize, ('total_size',)) @@ -65,7 +62,7 @@ class DetailsTab(Tab): txt = xml_escape(self.widget_status_as_fstr(widget, status)) if decode_bytes(widget.obj.get_text()) != txt: if 'comment' in widget.status_keys and is_url(txt): - widget.obj.set_markup('<a href="%s">%s</a>' % (txt, txt)) + widget.obj.set_markup(f'<a href="{txt}">{txt}</a>') else: widget.obj.set_markup(txt) diff --git a/deluge/ui/gtk3/dialogs.py b/deluge/ui/gtk3/dialogs.py index 4a2a60a7e..db337d3d5 100644 --- a/deluge/ui/gtk3/dialogs.py +++ b/deluge/ui/gtk3/dialogs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com> # @@ -9,7 +8,7 @@ # pylint: disable=super-on-old-class -from __future__ import unicode_literals +from collections import namedtuple from gi.repository import Gtk from twisted.internet import defer @@ -17,7 +16,9 @@ from twisted.internet import defer import deluge.component as component from deluge.common import windows_check -from .common import get_deluge_icon, get_pixbuf_at_size +from .common import get_deluge_icon, get_pixbuf + +Account = namedtuple('Account', 'username password authlevel') class BaseDialog(Gtk.Dialog): @@ -34,7 +35,7 @@ class BaseDialog(Gtk.Dialog): :param parent: gtkWindow, the parent window, if None it will default to the MainWindow """ - super(BaseDialog, self).__init__( + super().__init__( title=header, parent=parent if parent else component.get('MainWindow').window, flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT, @@ -55,7 +56,7 @@ class BaseDialog(Gtk.Dialog): # Hack for Windows since it doesn't support svg if icon.endswith('.svg') and windows_check(): icon = icon.rpartition('.svg')[0] + '16.png' - image.set_from_pixbuf(get_pixbuf_at_size(icon, 24)) + image.set_from_pixbuf(get_pixbuf(icon, 24)) else: image.set_from_icon_name(icon, Gtk.IconSize.LARGE_TOOLBAR) image.set_alignment(0.5, 0.0) @@ -103,7 +104,7 @@ class YesNoDialog(BaseDialog): :param text: see `:class:BaseDialog` :param parent: see `:class:BaseDialog` """ - super(YesNoDialog, self).__init__( + super().__init__( header, text, 'dialog-question', @@ -127,7 +128,7 @@ class InformationDialog(BaseDialog): :param text: see `:class:BaseDialog` :param parent: see `:class:BaseDialog` """ - super(InformationDialog, self).__init__( + super().__init__( header, text, 'dialog-information', @@ -154,7 +155,7 @@ class ErrorDialog(BaseDialog): :param traceback: show the traceback information in the details area :type traceback: bool """ - super(ErrorDialog, self).__init__( + super().__init__( header, text, 'dialog-error', (_('_Close'), Gtk.ResponseType.CLOSE), parent ) @@ -198,7 +199,7 @@ class AuthenticationDialog(BaseDialog): :param err_msg: the error message we got back from the server :type err_msg: string """ - super(AuthenticationDialog, self).__init__( + super().__init__( _('Authenticate'), err_msg, 'dialog-password', @@ -255,7 +256,7 @@ class AccountDialog(BaseDialog): parent=None, ): if username: - super(AccountDialog, self).__init__( + super().__init__( _('Edit Account'), _('Edit existing account'), 'dialog-information', @@ -268,7 +269,7 @@ class AccountDialog(BaseDialog): parent, ) else: - super(AccountDialog, self).__init__( + super().__init__( _('New Account'), _('Create a new account'), 'dialog-information', @@ -276,21 +277,21 @@ class AccountDialog(BaseDialog): parent, ) - self.levels_mapping = levels_mapping + self.account = None table = Gtk.Table(2, 3, False) - self.username_label = Gtk.Label() - self.username_label.set_markup('<b>' + _('Username:') + '</b>') - self.username_label.set_alignment(1.0, 0.5) - self.username_label.set_padding(5, 5) + username_label = Gtk.Label() + username_label.set_markup('<b>' + _('Username:') + '</b>') + username_label.set_alignment(1.0, 0.5) + username_label.set_padding(5, 5) self.username_entry = Gtk.Entry() - table.attach(self.username_label, 0, 1, 0, 1) + table.attach(username_label, 0, 1, 0, 1) table.attach(self.username_entry, 1, 2, 0, 1) - self.authlevel_label = Gtk.Label() - self.authlevel_label.set_markup('<b>' + _('Authentication Level:') + '</b>') - self.authlevel_label.set_alignment(1.0, 0.5) - self.authlevel_label.set_padding(5, 5) + authlevel_label = Gtk.Label() + authlevel_label.set_markup('<b>' + _('Authentication Level:') + '</b>') + authlevel_label.set_alignment(1.0, 0.5) + authlevel_label.set_padding(5, 5) # combo_box_new_text is deprecated but no other pygtk alternative. self.authlevel_combo = Gtk.ComboBoxText() @@ -305,16 +306,16 @@ class AccountDialog(BaseDialog): if active_idx is not None: self.authlevel_combo.set_active(active_idx) - table.attach(self.authlevel_label, 0, 1, 1, 2) + table.attach(authlevel_label, 0, 1, 1, 2) table.attach(self.authlevel_combo, 1, 2, 1, 2) - self.password_label = Gtk.Label() - self.password_label.set_markup('<b>' + _('Password:') + '</b>') - self.password_label.set_alignment(1.0, 0.5) - self.password_label.set_padding(5, 5) + password_label = Gtk.Label() + password_label.set_markup('<b>' + _('Password:') + '</b>') + password_label.set_alignment(1.0, 0.5) + password_label.set_padding(5, 5) self.password_entry = Gtk.Entry() self.password_entry.set_visibility(False) - table.attach(self.password_label, 0, 1, 2, 3) + table.attach(password_label, 0, 1, 2, 3) table.attach(self.password_entry, 1, 2, 2, 3) self.vbox.pack_start(table, False, False, padding=5) @@ -327,18 +328,17 @@ class AccountDialog(BaseDialog): if password: self.password_entry.set_text(username) - self.show_all() - - def get_username(self): - return self.username_entry.get_text() - - def get_password(self): - return self.password_entry.get_text() + self.vbox.show_all() - def get_authlevel(self): - combobox = self.authlevel_combo - level = combobox.get_model()[combobox.get_active()][0] - return level + def _on_response(self, widget, response): + if response == Gtk.ResponseType.OK: + self.account = Account( + self.username_entry.get_text(), + self.password_entry.get_text(), + self.authlevel_combo.get_active_text(), + ) + self.destroy() + self.deferred.callback(response) class OtherDialog(BaseDialog): @@ -359,7 +359,7 @@ class OtherDialog(BaseDialog): if not icon: icon = 'dialog-information' - super(OtherDialog, self).__init__( + super().__init__( header, text, icon, @@ -421,7 +421,7 @@ class PasswordDialog(BaseDialog): :param password_msg: the error message we got back from the server :type password_msg: string """ - super(PasswordDialog, self).__init__( + super().__init__( header=_('Password Protected'), text=password_msg, icon='dialog-password', @@ -455,3 +455,44 @@ class PasswordDialog(BaseDialog): def on_password_activate(self, widget): self.response(Gtk.ResponseType.OK) + + +class CopyMagnetDialog(BaseDialog): + """ + Displays a dialog with a magnet URI + """ + + def __init__(self, torrent_magnet='', parent=None): + super().__init__( + header=_('Copy Magnet URI'), + text='', + icon='magnet_copy.svg', + buttons=(_('_Close'), Gtk.ResponseType.CLOSE), + parent=parent, + ) + self.copied = False + + table = Gtk.Table(1, 2, False) + self.magnet_entry = Gtk.Entry() + self.magnet_entry.set_text(torrent_magnet) + self.magnet_entry.set_editable(False) + self.magnet_entry.connect('copy-clipboard', self.on_copy_emitted) + table.attach(self.magnet_entry, 0, 1, 0, 1) + + copy_button = Gtk.Button.new_with_label(_('Copy')) + copy_button.connect('clicked', self.on_copy_clicked) + table.attach(copy_button, 1, 2, 0, 1) + + self.vbox.pack_start(table, False, False, padding=5) + self.set_focus(self.magnet_entry) + + self.show_all() + + def on_copy_clicked(self, widget): + self.magnet_entry.select_region(0, -1) + self.magnet_entry.copy_clipboard() + self.magnet_entry.set_position(0) + self.copied = True + + def on_copy_emitted(self, widget): + self.copied = True diff --git a/deluge/ui/gtk3/edittrackersdialog.py b/deluge/ui/gtk3/edittrackersdialog.py index a21a7d71f..861e3924b 100644 --- a/deluge/ui/gtk3/edittrackersdialog.py +++ b/deluge/ui/gtk3/edittrackersdialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os.path @@ -77,7 +74,7 @@ def trackers_tiers_from_text(text_str=''): return trackers -class EditTrackersDialog(object): +class EditTrackersDialog: def __init__(self, torrent_id, parent=None): self.torrent_id = torrent_id self.builder = Gtk.Builder() @@ -134,6 +131,11 @@ class EditTrackersDialog(object): self.dialog.connect('response', self._on_response) self.treeview.connect('button_press_event', self.on_button_press_event) + self.add_tracker_dialog.connect('key-press-event', self.on_key_add_press_event) + self.add_tracker_dialog.connect('delete-event', self.on_delete_event_add) + self.edit_tracker_entry.connect('key-press-event', self.on_key_edit_press_event) + self.edit_tracker_entry.connect('delete-event', self.on_delete_event_edit) + def run(self): # Make sure we have a torrent_id.. if not just return if self.torrent_id is None: @@ -192,7 +194,7 @@ class EditTrackersDialog(object): self.old_trackers = list(status['trackers']) for tracker in self.old_trackers: self.add_tracker(tracker['tier'], tracker['url']) - self.treeview.set_cursor((0)) + self.treeview.set_cursor(0) self.dialog.show() def add_tracker(self, tier, url): @@ -208,6 +210,7 @@ class EditTrackersDialog(object): # Show the add tracker dialog self.add_tracker_dialog.show() self.builder.get_object('textview_trackers').grab_focus() + self.dialog.set_sensitive(False) def on_button_remove_clicked(self, widget): log.debug('on_button_remove_clicked') @@ -236,11 +239,27 @@ class EditTrackersDialog(object): self.edit_tracker_entry.grab_focus() self.dialog.set_sensitive(False) - def on_button_edit_cancel_clicked(self, widget): - log.debug('on_button_edit_cancel_clicked') + def _close_edit_dialog(self): self.dialog.set_sensitive(True) self.edit_tracker_entry.hide() + def on_button_edit_cancel_clicked(self, widget): + """handles the cancel button""" + log.debug('on_button_edit_cancel_clicked') + self._close_edit_dialog() + + def on_key_edit_press_event(self, widget, event): + """handles Escape key press""" + if event.keyval == Gdk.KEY_Escape: + log.debug('on_key_edit_press_event') + self._close_edit_dialog() + + def on_delete_event_edit(self, widget, event): + """handles the Top-Right X button""" + log.debug('on_delete_event_edit') + self._close_edit_dialog() + return True + def on_button_edit_ok_clicked(self, widget): log.debug('on_button_edit_ok_clicked') selected = self.get_selected() @@ -301,11 +320,29 @@ class EditTrackersDialog(object): # Clear the entry widget and hide the dialog textview_buf.set_text('') + self.dialog.set_sensitive(True) self.add_tracker_dialog.hide() - def on_button_add_cancel_clicked(self, widget): - log.debug('on_button_add_cancel_clicked') + def _discard_and_close_add_dialog(self): # Clear the entry widget and hide the dialog b = Gtk.TextBuffer() self.builder.get_object('textview_trackers').set_buffer(b) + self.dialog.set_sensitive(True) self.add_tracker_dialog.hide() + + def on_button_add_cancel_clicked(self, widget): + """handles the cancel button""" + log.debug('on_button_add_cancel_clicked') + self._discard_and_close_add_dialog() + + def on_key_add_press_event(self, widget, event): + """handles Escape key press""" + if event.keyval == Gdk.KEY_Escape: + log.debug('on_key_add_press_event') + self._discard_and_close_add_dialog() + + def on_delete_event_add(self, widget, event): + """handles the Top-Right X button""" + log.debug('on_delete_event_add') + self._discard_and_close_add_dialog() + return True diff --git a/deluge/ui/gtk3/files_tab.py b/deluge/ui/gtk3/files_tab.py index 50f8a4587..24c169727 100644 --- a/deluge/ui/gtk3/files_tab.py +++ b/deluge/ui/gtk3/files_tab.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,15 +6,13 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - import json import logging import os.path import gi # isort:skip (Required before Gtk import). -gi.require_version('Gtk', '3.0') # NOQA: E402 +gi.require_version('Gtk', '3.0') # isort:imports-thirdparty from gi.repository import Gio, Gtk @@ -84,7 +81,7 @@ def cell_progress(column, cell, model, row, data): class FilesTab(Tab): def __init__(self): - super(FilesTab, self).__init__('Files', 'files_tab', 'files_tab_label') + super().__init__('Files', 'files_tab', 'files_tab_label') self.listview = self.main_builder.get_object('files_listview') # filename, size, progress string, progress value, priority, file index, icon id diff --git a/deluge/ui/gtk3/filtertreeview.py b/deluge/ui/gtk3/filtertreeview.py index 4272ef018..40752d78c 100644 --- a/deluge/ui/gtk3/filtertreeview.py +++ b/deluge/ui/gtk3/filtertreeview.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com> # 2008 Andrew Resch <andrewresch@gmail.com> @@ -9,8 +8,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os import warnings @@ -24,7 +21,7 @@ from deluge.common import TORRENT_STATE, decode_bytes, resource_filename from deluge.configmanager import ConfigManager from deluge.ui.client import client -from .common import get_pixbuf, get_pixbuf_at_size +from .common import get_pixbuf log = logging.getLogger(__name__) @@ -90,7 +87,7 @@ class FilterTreeView(component.Component): self.treeview.set_level_indentation(-21) # Force theme to use expander-size so we don't cut out entries due to indentation hack. provider = Gtk.CssProvider() - provider.load_from_data('* {-GtkTreeView-expander-size: 9;}'.encode()) + provider.load_from_data(b'* {-GtkTreeView-expander-size: 9;}') context = self.treeview.get_style_context() context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) @@ -256,7 +253,7 @@ class FilterTreeView(component.Component): return get_pixbuf('%s16.png' % pix) def set_row_image(self, cat, value, filename): - pix = get_pixbuf_at_size(filename, 16) + pix = get_pixbuf(filename, size=16) row = self.filters[(cat, value)] self.treestore.set_value(row, 4, pix) return False diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui index a7a8caeaa..8adbad329 100644 --- a/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui +++ b/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui @@ -144,8 +144,6 @@ <property name="invisible_char">•</property> <property name="activates_default">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.ui index 4d3680344..7183272e1 100644 --- a/deluge/ui/gtk3/glade/add_torrent_dialog.ui +++ b/deluge/ui/gtk3/glade/add_torrent_dialog.ui @@ -727,8 +727,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_maxup"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment2</property> <property name="update_policy">if-valid</property> </object> @@ -741,8 +739,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_maxconnections"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment3</property> </object> <packing> @@ -754,8 +750,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_maxupslots"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment4</property> </object> <packing> @@ -767,8 +761,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_maxdown"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment1</property> </object> <packing> diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui index ecbd0f7cf..6b75b235f 100644 --- a/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui +++ b/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui @@ -143,8 +143,6 @@ <property name="invisible_char">•</property> <property name="activates_default">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/connect_peer_dialog.ui b/deluge/ui/gtk3/glade/connect_peer_dialog.ui index f5e9337ea..4a60751d0 100644 --- a/deluge/ui/gtk3/glade/connect_peer_dialog.ui +++ b/deluge/ui/gtk3/glade/connect_peer_dialog.ui @@ -128,8 +128,6 @@ <property name="width_chars">39</property> <property name="text" translatable="yes">hostname:port</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/connection_manager.addhost.ui b/deluge/ui/gtk3/glade/connection_manager.addhost.ui index 641a71cc1..ea5376e45 100644 --- a/deluge/ui/gtk3/glade/connection_manager.addhost.ui +++ b/deluge/ui/gtk3/glade/connection_manager.addhost.ui @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.22.1 --> +<!-- Generated with glade 3.22.2 --> <interface> <requires lib="gtk+" version="3.0"/> <object class="GtkAdjustment" id="adjustment_port"> @@ -16,7 +16,7 @@ <property name="window_position">center-on-parent</property> <property name="destroy_with_parent">True</property> <property name="type_hint">dialog</property> - <child> + <child type="titlebar"> <placeholder/> </child> <child internal-child="vbox"> @@ -24,11 +24,12 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="orientation">vertical</property> - <property name="spacing">2</property> + <property name="spacing">5</property> <child internal-child="action_area"> <object class="GtkButtonBox" id="dialog-action_area5"> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="margin_top">15</property> <property name="layout_style">end</property> <child> <object class="GtkButton" id="button_addhost_cancel"> @@ -65,49 +66,41 @@ <property name="expand">False</property> <property name="fill">True</property> <property name="pack_type">end</property> - <property name="position">0</property> + <property name="position">1</property> </packing> </child> <child> - <object class="GtkBox" id="hbox2"> + <object class="GtkGrid"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="spacing">5</property> + <property name="row_spacing">5</property> + <property name="column_spacing">5</property> <child> <object class="GtkLabel" id="label3"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="halign">start</property> <property name="label" translatable="yes">Hostname:</property> + <property name="xalign">0</property> </object> <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">0</property> + <property name="left_attach">0</property> + <property name="top_attach">0</property> </packing> </child> <child> - <object class="GtkAlignment" id="alignment4"> + <object class="GtkEntry" id="entry_hostname"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="left_padding">1</property> - <child> - <object class="GtkEntry" id="entry_hostname"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">•</property> - <property name="activates_default">True</property> - <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> - <signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" swapped="no"/> - </object> - </child> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">•</property> + <property name="activates_default">True</property> + <property name="truncate_multiline">True</property> + <signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" swapped="no"/> </object> <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> + <property name="left_attach">1</property> + <property name="top_attach">0</property> </packing> </child> <child> @@ -115,116 +108,90 @@ <property name="visible">True</property> <property name="can_focus">False</property> <property name="label" translatable="yes">Port:</property> + <property name="xalign">0</property> </object> <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">2</property> + <property name="left_attach">0</property> + <property name="top_attach">1</property> </packing> </child> <child> <object class="GtkSpinButton" id="spinbutton_port"> <property name="visible">True</property> <property name="can_focus">True</property> + <property name="halign">start</property> <property name="max_length">5</property> <property name="invisible_char">•</property> <property name="width_chars">5</property> + <property name="max_width_chars">5</property> <property name="progress_pulse_step">1</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_port</property> <property name="climb_rate">1</property> <property name="numeric">True</property> </object> <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">3</property> + <property name="left_attach">1</property> + <property name="top_attach">1</property> </packing> </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkGrid" id="table1"> - <property name="visible">True</property> - <property name="can_focus">False</property> <child> - <object class="GtkAlignment" id="alignment3"> + <object class="GtkLabel" id="label5"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="left_padding">5</property> - <child> - <object class="GtkEntry" id="entry_password"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="visibility">False</property> - <property name="invisible_char">•</property> - <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> - </object> - </child> + <property name="halign">start</property> + <property name="label" translatable="yes">Username:</property> + <property name="xalign">0</property> </object> <packing> - <property name="left_attach">1</property> - <property name="top_attach">1</property> + <property name="left_attach">0</property> + <property name="top_attach">2</property> </packing> </child> <child> - <object class="GtkAlignment" id="alignment2"> + <object class="GtkEntry" id="entry_username"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="left_padding">5</property> - <child> - <object class="GtkEntry" id="entry_username"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">•</property> - <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> - </object> - </child> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="invisible_char">•</property> + <property name="truncate_multiline">True</property> </object> <packing> <property name="left_attach">1</property> - <property name="top_attach">0</property> + <property name="top_attach">2</property> </packing> </child> <child> - <object class="GtkLabel" id="label5"> + <object class="GtkLabel" id="label6"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="halign">start</property> - <property name="label" translatable="yes">Username:</property> + <property name="label" translatable="yes">Password:</property> + <property name="xalign">0</property> </object> <packing> <property name="left_attach">0</property> - <property name="top_attach">0</property> + <property name="top_attach">3</property> </packing> </child> <child> - <object class="GtkLabel" id="label6"> + <object class="GtkEntry" id="entry_password"> <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="halign">start</property> - <property name="label" translatable="yes">Password:</property> + <property name="can_focus">True</property> + <property name="hexpand">True</property> + <property name="visibility">False</property> + <property name="invisible_char">•</property> + <property name="truncate_multiline">True</property> </object> <packing> - <property name="left_attach">0</property> - <property name="top_attach">1</property> + <property name="left_attach">1</property> + <property name="top_attach">3</property> </packing> </child> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">2</property> + <property name="position">0</property> </packing> </child> </object> diff --git a/deluge/ui/gtk3/glade/connection_manager.ui b/deluge/ui/gtk3/glade/connection_manager.ui index 11516aa76..44f4b34e3 100644 --- a/deluge/ui/gtk3/glade/connection_manager.ui +++ b/deluge/ui/gtk3/glade/connection_manager.ui @@ -30,7 +30,7 @@ <property name="modal">True</property> <property name="window_position">center-on-parent</property> <property name="default_width">300</property> - <property name="default_height">250</property> + <property name="default_height">285</property> <property name="destroy_with_parent">True</property> <property name="type_hint">dialog</property> <child> diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui index dc7b7e93e..43283305a 100644 --- a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui +++ b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui @@ -143,8 +143,6 @@ <property name="invisible_char">•</property> <property name="activates_default">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui index a380718e4..712305489 100644 --- a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui +++ b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui @@ -143,8 +143,6 @@ <property name="invisible_char">•</property> <property name="activates_default">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.ui index 0166e2d0b..0d1594014 100644 --- a/deluge/ui/gtk3/glade/create_torrent_dialog.ui +++ b/deluge/ui/gtk3/glade/create_torrent_dialog.ui @@ -356,8 +356,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> @@ -393,8 +391,6 @@ <object class="GtkEntry" id="entry_comments"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/edit_trackers.edit.ui b/deluge/ui/gtk3/glade/edit_trackers.edit.ui index 2521e8ff8..fc3e51b83 100644 --- a/deluge/ui/gtk3/glade/edit_trackers.edit.ui +++ b/deluge/ui/gtk3/glade/edit_trackers.edit.ui @@ -144,8 +144,6 @@ <property name="invisible_char">•</property> <property name="activates_default">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">True</property> diff --git a/deluge/ui/gtk3/glade/main_window.tabs.ui b/deluge/ui/gtk3/glade/main_window.tabs.ui index d4984dd9d..7ecf61821 100644 --- a/deluge/ui/gtk3/glade/main_window.tabs.ui +++ b/deluge/ui/gtk3/glade/main_window.tabs.ui @@ -1166,8 +1166,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">spin_stop_ratio_adjustment</property> <property name="digits">1</property> <property name="numeric">True</property> @@ -1267,8 +1265,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">spin_max_connections_adjustment</property> <property name="numeric">True</property> </object> @@ -1282,8 +1278,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">spin_max_upload_adjustment</property> <property name="digits">1</property> <property name="numeric">True</property> @@ -1298,8 +1292,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">spin_max_download_adjustment</property> <property name="climb_rate">1</property> <property name="digits">1</property> @@ -1356,8 +1348,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">spin_max_upload_slots_adjustment</property> <property name="numeric">True</property> </object> diff --git a/deluge/ui/gtk3/glade/other_dialog.ui b/deluge/ui/gtk3/glade/other_dialog.ui index 26d3d08bf..01a5597f2 100644 --- a/deluge/ui/gtk3/glade/other_dialog.ui +++ b/deluge/ui/gtk3/glade/other_dialog.ui @@ -148,8 +148,6 @@ <property name="max_length">6</property> <property name="activates_default">True</property> <property name="width_chars">6</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment1</property> </object> </child> diff --git a/deluge/ui/gtk3/glade/path_combo_chooser.ui b/deluge/ui/gtk3/glade/path_combo_chooser.ui index f79685d26..871bac01e 100644 --- a/deluge/ui/gtk3/glade/path_combo_chooser.ui +++ b/deluge/ui/gtk3/glade/path_combo_chooser.ui @@ -98,8 +98,6 @@ <property name="visible">True</property> <property name="can_focus">True</property> <property name="max_length">2</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment3</property> <property name="climb_rate">1</property> <property name="numeric">True</property> @@ -571,6 +569,7 @@ <object class="GtkEntry" id="entry_text"> <property name="visible">True</property> <property name="can_focus">True</property> + <property name="hexpand">True</property> <property name="invisible_char">•</property> <signal name="changed" handler="on_entry_text_changed" swapped="no"/> <signal name="delete-text" handler="on_entry_text_delete_text" swapped="no"/> diff --git a/deluge/ui/gtk3/glade/preferences_dialog.ui b/deluge/ui/gtk3/glade/preferences_dialog.ui index 4b223cb44..aa1531d75 100644 --- a/deluge/ui/gtk3/glade/preferences_dialog.ui +++ b/deluge/ui/gtk3/glade/preferences_dialog.ui @@ -929,8 +929,6 @@ and daemon (does not apply in Standalone mode).</property> <property name="width_chars">16</property> <property name="text">********</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">False</property> @@ -1150,6 +1148,7 @@ and daemon (does not apply in Standalone mode).</property> <property name="top_padding">2</property> <property name="bottom_padding">2</property> <property name="left_padding">12</property> + <property name="right_padding">12</property> <child> <object class="GtkGrid" id="table9"> <property name="visible">True</property> @@ -1545,8 +1544,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_max_connections_per_second"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_conn_per_sec</property> <property name="numeric">True</property> </object> @@ -1559,8 +1556,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_max_half_open_connections"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_half_open_conn</property> <property name="numeric">True</property> </object> @@ -1624,8 +1619,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum number of connections allowed. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_conn_global</property> <property name="climb_rate">1</property> <property name="snap_to_ticks">True</property> @@ -1655,8 +1648,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum download speed for all torrents. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_download</property> <property name="climb_rate">1</property> <property name="digits">1</property> @@ -1672,8 +1663,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum upload speed for all torrents. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_upload</property> <property name="climb_rate">1</property> <property name="digits">1</property> @@ -1689,8 +1678,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum upload slots for all torrents. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_upload_slots_global</property> <property name="climb_rate">1</property> <property name="snap_to_ticks">True</property> @@ -1849,8 +1836,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum upload slots per torrent. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_upload_slots_per_torrent</property> <property name="climb_rate">1</property> <property name="snap_to_ticks">True</property> @@ -1866,8 +1851,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_conn_per_torrent</property> <property name="snap_to_ticks">True</property> <property name="numeric">True</property> @@ -1930,8 +1913,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum number download speed per torrent. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_download_per_torrent</property> <property name="digits">1</property> <property name="numeric">True</property> @@ -1946,8 +1927,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes">The maximum upload speed per torrent. Set -1 for unlimited.</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_max_upload_per_torrent</property> <property name="digits">1</property> <property name="numeric">True</property> @@ -2131,8 +2110,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_active"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_active</property> <property name="snap_to_ticks">True</property> <property name="numeric">True</property> @@ -2146,8 +2123,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_seeding"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_seeding</property> <property name="snap_to_ticks">True</property> <property name="numeric">True</property> @@ -2185,8 +2160,6 @@ used sparingly.</property> <object class="GtkSpinButton" id="spin_downloading"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_downloading</property> <property name="snap_to_ticks">True</property> <property name="numeric">True</property> @@ -2323,8 +2296,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_seed_time_limit</property> </object> <packing> @@ -2337,8 +2308,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_time_ratio_limit</property> <property name="digits">2</property> </object> @@ -2352,8 +2321,6 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_share_ratio_limit</property> <property name="digits">2</property> </object> @@ -2425,8 +2392,6 @@ used sparingly.</property> <property name="sensitive">False</property> <property name="can_focus">True</property> <property name="invisible_char">•</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_share_ratio</property> <property name="digits">2</property> <property name="numeric">True</property> @@ -2572,12 +2537,10 @@ used sparingly.</property> <object class="GtkEntry" id="entry_interface"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="tooltip_text" translatable="yes">The IP address of the interface to listen for incoming bittorrent connections on. Leave this empty if you want to use the default.</property> - <property name="max_length">15</property> + <property name="tooltip_text" translatable="yes">IP address or network interface name to listen for incoming BitTorrent connections. Leave empty to use system default.</property> + <property name="max_length">40</property> <property name="width_chars">15</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> </child> </object> @@ -2586,7 +2549,7 @@ used sparingly.</property> <object class="GtkLabel" id="label110"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="label" translatable="yes">Incoming Address</property> + <property name="label" translatable="yes">Incoming Interface</property> <attributes> <attribute name="weight" value="bold"/> </attributes> @@ -2628,8 +2591,6 @@ used sparingly.</property> <property name="can_focus">True</property> <property name="max_length">5</property> <property name="max_width_chars">6</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_incoming_port</property> <property name="climb_rate">1</property> <property name="snap_to_ticks">True</property> @@ -2728,6 +2689,24 @@ used sparingly.</property> </packing> </child> <child> + <object class="GtkAlignment" id="alignment31"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="left_padding">10</property> + <child> + <object class="GtkSpinner" id="port_spinner"> + <property name="visible">False</property> + <property name="can_focus">False</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> <object class="GtkAlignment" id="alignment48"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -2742,7 +2721,7 @@ used sparingly.</property> <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">2</property> + <property name="position">3</property> </packing> </child> </object> @@ -2793,14 +2772,12 @@ used sparingly.</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="tooltip_text" translatable="yes"> -The network interface name or IP address for outgoing BitTorrent connections. (Leave empty for default.) + IP address or network interface name for outgoing BitTorrent connections. Leave empty to use system default. </property> - <property name="max_length">15</property> + <property name="max_length">40</property> <property name="invisible_char">●</property> <property name="width_chars">15</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> </child> </object> @@ -2880,8 +2857,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <property name="can_focus">True</property> <property name="max_length">5</property> <property name="width_chars">7</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_outgoing_port_min</property> <property name="climb_rate">1</property> <property name="snap_to_ticks">True</property> @@ -2913,8 +2888,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <property name="can_focus">True</property> <property name="max_length">5</property> <property name="width_chars">7</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_outgoing_port_max</property> <property name="climb_rate">1</property> <property name="snap_to_ticks">True</property> @@ -3236,8 +3209,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <property name="width_chars">1</property> <property name="text">0x00</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">False</property> @@ -3345,8 +3316,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <property name="can_focus">True</property> <property name="visibility">False</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="left_attach">1</property> @@ -3370,8 +3339,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <property name="visible">True</property> <property name="can_focus">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <signal name="paste-clipboard" handler="on_entry_proxy_host_paste_clipboard" swapped="no"/> </object> <packing> @@ -3399,8 +3366,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <object class="GtkSpinButton" id="spin_proxy_port"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_proxy_port</property> <property name="numeric">True</property> </object> @@ -3416,8 +3381,6 @@ The network interface name or IP address for outgoing BitTorrent connections. (L <property name="visible">True</property> <property name="can_focus">True</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="left_attach">1</property> @@ -3696,8 +3659,6 @@ the proxy instead of using the local DNS service</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="invisible_char">●</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_cache_size</property> <property name="numeric">True</property> <property name="update_policy">if-valid</property> @@ -3713,8 +3674,6 @@ the proxy instead of using the local DNS service</property> <property name="can_focus">True</property> <property name="max_length">5</property> <property name="invisible_char">●</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_cache_expiry</property> </object> <packing> @@ -4248,8 +4207,6 @@ the proxy instead of using the local DNS service</property> <property name="tooltip_text" translatable="yes">If Deluge cannot find the database file at this location it will fallback to using DNS to resolve the peer's country.</property> <property name="invisible_char">●</property> <property name="truncate_multiline">True</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> </object> <packing> <property name="expand">False</property> @@ -4427,8 +4384,6 @@ the proxy instead of using the local DNS service</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="max_width_chars">6</property> - <property name="primary_icon_activatable">False</property> - <property name="secondary_icon_activatable">False</property> <property name="adjustment">adjustment_spin_daemon_port</property> </object> <packing> diff --git a/deluge/ui/gtk3/glade/torrent_menu.ui b/deluge/ui/gtk3/glade/torrent_menu.ui index c1b77b4ad..c9ee289c1 100644 --- a/deluge/ui/gtk3/glade/torrent_menu.ui +++ b/deluge/ui/gtk3/glade/torrent_menu.ui @@ -31,6 +31,11 @@ <property name="icon_name">media-playback-pause-symbolic</property> <property name="icon_size">1</property> </object> + <object class="GtkImage" id="menu-item-image15"> + <property name="can_focus">False</property> + <property name="icon_name">edit-copy-symbolic</property> + <property name="icon_size">1</property> + </object> <object class="GtkImage" id="menu-item-image19"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -155,6 +160,17 @@ </object> </child> <child> + <object class="GtkImageMenuItem" id="menuitem_copymagnet"> + <property name="label" translatable="yes">_Copy Magnet URI</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="use_underline">True</property> + <property name="image">menu-item-image15</property> + <property name="use_stock">False</property> + <signal name="activate" handler="on_menuitem_copymagnet_activate" swapped="no"/> + </object> + </child> + <child> <object class="GtkImageMenuItem" id="menuitem_updatetracker"> <property name="label" translatable="yes">_Update Tracker</property> <property name="visible">True</property> diff --git a/deluge/ui/gtk3/gtkui.py b/deluge/ui/gtk3/gtkui.py index 02b309078..ddb2eb529 100644 --- a/deluge/ui/gtk3/gtkui.py +++ b/deluge/ui/gtk3/gtkui.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # @@ -8,8 +7,6 @@ # # pylint: disable=wrong-import-position -from __future__ import division, unicode_literals - import logging import os import signal @@ -18,8 +15,8 @@ import time import gi # isort:skip (Required before Gtk import). -gi.require_version('Gtk', '3.0') # NOQA: E402 -gi.require_version('Gdk', '3.0') # NOQA: E402 +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') # isort:imports-thirdparty from gi.repository.GLib import set_prgname @@ -32,7 +29,7 @@ try: # Install twisted reactor, before any other modules import reactor. reactor = gtk3reactor.install() except ReactorAlreadyInstalledError: - # Running unit tests so trial already installed a rector + # Running unit tests so already installed a rector from twisted.internet import reactor # isort:imports-firstparty @@ -140,7 +137,7 @@ DEFAULT_PREFS = { } -class GtkUI(object): +class GtkUI: def __init__(self, args): # Setup gtkbuilder/glade translation setup_translation() diff --git a/deluge/ui/gtk3/ipcinterface.py b/deluge/ui/gtk3/ipcinterface.py index 78858c443..0ef28d8c0 100644 --- a/deluge/ui/gtk3/ipcinterface.py +++ b/deluge/ui/gtk3/ipcinterface.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008-2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,14 +6,14 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os import sys from base64 import b64encode from glob import glob from tempfile import mkstemp +from urllib.parse import urlparse +from urllib.request import url2pathname import rencode import twisted.internet.error @@ -26,14 +25,6 @@ from deluge.common import decode_bytes, is_magnet, is_url, windows_check from deluge.configmanager import ConfigManager, get_config_dir from deluge.ui.client import client -try: - from urllib.parse import urlparse - from urllib.request import url2pathname -except ImportError: - # PY2 fallback - from urllib import url2pathname # pylint: disable=ungrouped-imports - from urlparse import urlparse # pylint: disable=ungrouped-imports - log = logging.getLogger(__name__) diff --git a/deluge/ui/gtk3/listview.py b/deluge/ui/gtk3/listview.py index 4e9fe5db8..e9f6b1084 100644 --- a/deluge/ui/gtk3/listview.py +++ b/deluge/ui/gtk3/listview.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,20 +6,18 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from gi.repository import GObject, Gtk -from deluge.common import PY2, decode_bytes +from deluge.common import decode_bytes from .common import cmp, load_pickled_state_file, save_pickled_state_file log = logging.getLogger(__name__) -class ListViewColumnState(object): +class ListViewColumnState: """Class used for saving/loading column state.""" def __init__(self, name, position, width, visible, sort, sort_order): @@ -32,13 +29,13 @@ class ListViewColumnState(object): self.sort_order = sort_order -class ListView(object): +class ListView: """ListView is used to make custom GtkTreeViews. It supports the adding and removing of columns, creating a menu for a column toggle list and support for 'status_field's which are used while updating the columns data. """ - class ListViewColumn(object): + class ListViewColumn: """Holds information regarding a column in the ListView""" def __init__(self, name, column_indices): @@ -66,7 +63,7 @@ class ListView(object): self.pixbuf_index = 0 self.data_func = None - class TreeviewColumn(Gtk.TreeViewColumn, object): + class TreeviewColumn(Gtk.TreeViewColumn): """ TreeViewColumn does not signal right-click events, and we need them This subclass is equivalent to TreeViewColumn, but it signals these events @@ -75,13 +72,11 @@ class ListView(object): """ __gsignals__ = { - 'button-press-event' - if not PY2 - else b'button-press-event': (GObject.SIGNAL_RUN_LAST, None, (object,)) + 'button-press-event': (GObject.SIGNAL_RUN_LAST, None, (object,)) } def __init__(self, title=None, cell_renderer=None, **args): - """ Constructor, see Gtk.TreeViewColumn """ + """Constructor, see Gtk.TreeViewColumn""" Gtk.TreeViewColumn.__init__(self, title, cell_renderer, **args) label = Gtk.Label(label=title) self.set_widget(label) diff --git a/deluge/ui/gtk3/mainwindow.py b/deluge/ui/gtk3/mainwindow.py index b7d751410..d11ff317a 100644 --- a/deluge/ui/gtk3/mainwindow.py +++ b/deluge/ui/gtk3/mainwindow.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os.path from hashlib import sha1 as sha @@ -45,7 +42,7 @@ if windowing('X11'): log = logging.getLogger(__name__) -class _GtkBuilderSignalsHolder(object): +class _GtkBuilderSignalsHolder: def connect_signals(self, mapping_or_class): if isinstance(mapping_or_class, dict): @@ -341,7 +338,9 @@ class MainWindow(component.Component): return self.previous_clipboard_text = text if text and ( - (is_url(text) and text.endswith('.torrent')) or is_magnet(text) + (is_url(text) and text.endswith('.torrent')) + or is_magnet(text) + and not component.get('MenuBar').magnet_copied() ): component.get('AddTorrentDialog').show() component.get('AddTorrentDialog').on_button_url_clicked(window) diff --git a/deluge/ui/gtk3/menubar.py b/deluge/ui/gtk3/menubar.py index e09f394fc..a812a8cac 100644 --- a/deluge/ui/gtk3/menubar.py +++ b/deluge/ui/gtk3/menubar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me> @@ -9,8 +8,6 @@ # -from __future__ import unicode_literals - import logging import os.path @@ -21,7 +18,7 @@ import deluge.component as component from deluge.configmanager import ConfigManager from deluge.ui.client import client -from .dialogs import ErrorDialog, OtherDialog +from .dialogs import CopyMagnetDialog, ErrorDialog, OtherDialog from .path_chooser import PathChooser log = logging.getLogger(__name__) @@ -34,6 +31,7 @@ class MenuBar(component.Component): self.mainwindow = component.get('MainWindow') self.main_builder = self.mainwindow.get_builder() self.config = ConfigManager('gtk3ui.conf') + self._magnet_copied = False self.builder = Gtk.Builder() # Get the torrent menu from the gtk builder file @@ -142,6 +140,19 @@ class MenuBar(component.Component): self.change_sensitivity = ['menuitem_addtorrent'] + def magnet_copied(self): + """ + lets the caller know whether a magnet was copied internally + + the `mainwindow` checks every time the data in the clipboard, + so it will automatically open the AddTorrentURL dialog in case it + contains a valid link (URL to a torrent or a magnet URI). + + """ + val = self._magnet_copied + self._magnet_copied = False + return val + def start(self): for widget in self.change_sensitivity: self.main_builder.get_object(widget).set_sensitive(True) @@ -282,6 +293,21 @@ class MenuBar(component.Component): component.get('TorrentView').get_selected_torrents() ) + def on_menuitem_copymagnet_activate(self, data=None): + log.debug('on_menuitem_copymagnet_activate') + torrent_ids = component.get('TorrentView').get_selected_torrents() + if torrent_ids: + + def _on_magnet_uri(magnet_uri): + def update_copied(response_id): + if dialog.copied: + self._magnet_copied = True + + dialog = CopyMagnetDialog(magnet_uri) + dialog.run().addCallback(update_copied) + + client.core.get_magnet_uri(torrent_ids[0]).addCallback(_on_magnet_uri) + def on_menuitem_updatetracker_activate(self, data=None): log.debug('on_menuitem_updatetracker_activate') client.core.force_reannounce( @@ -541,7 +567,7 @@ class MenuBar(component.Component): account_to_log = {} for key, value in account.copy().items(): if key == 'password': - value = '*' * len(value) + value = '*' * 10 account_to_log[key] = value known_accounts_to_log.append(account_to_log) log.debug('_on_known_accounts: %s', known_accounts_to_log) diff --git a/deluge/ui/gtk3/menubar_osx.py b/deluge/ui/gtk3/menubar_osx.py index 1df6fab08..53150fbf3 100644 --- a/deluge/ui/gtk3/menubar_osx.py +++ b/deluge/ui/gtk3/menubar_osx.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from gi.repository.Gdk import ModifierType from gi.repository.Gtk import SeparatorMenuItem, accel_groups_from_object from gi.repository.Gtk.AccelFlags import VISIBLE diff --git a/deluge/ui/gtk3/new_release_dialog.py b/deluge/ui/gtk3/new_release_dialog.py index 6aa328260..a635bd2cd 100644 --- a/deluge/ui/gtk3/new_release_dialog.py +++ b/deluge/ui/gtk3/new_release_dialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from gi.repository.Gtk import IconSize import deluge.common @@ -17,7 +14,7 @@ from deluge.configmanager import ConfigManager from deluge.ui.client import client -class NewReleaseDialog(object): +class NewReleaseDialog: def __init__(self): pass diff --git a/deluge/ui/gtk3/options_tab.py b/deluge/ui/gtk3/options_tab.py index 6a25fd1e8..b0411a8b6 100644 --- a/deluge/ui/gtk3/options_tab.py +++ b/deluge/ui/gtk3/options_tab.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # 2017 Calum Lind <calumlind+deluge@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from gi.repository.Gdk import keyval_name import deluge.component as component @@ -21,7 +18,7 @@ from .torrentdetails import Tab class OptionsTab(Tab): def __init__(self): - super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label') + super().__init__('Options', 'options_tab', 'options_tab_label') self.prev_torrent_ids = None self.prev_status = None @@ -191,8 +188,9 @@ class OptionsTab(Tab): ): options[status_key] = widget_value - if options.get('move_completed', False): - options['move_completed_path'] = self.move_completed_path_chooser.get_text() + move_completed_path = self.move_completed_path_chooser.get_text() + if move_completed_path != self.prev_status['move_completed_path']: + options['move_completed_path'] = move_completed_path client.core.set_torrent_options(self.prev_torrent_ids, options) self.button_apply.set_sensitive(False) diff --git a/deluge/ui/gtk3/path_chooser.py b/deluge/ui/gtk3/path_chooser.py index b7228415e..805819660 100644 --- a/deluge/ui/gtk3/path_chooser.py +++ b/deluge/ui/gtk3/path_chooser.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2013 Bro <bro.development@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -125,7 +122,7 @@ class PathChoosersHandler(component.Component): class PathChooser(PathChooserComboBox): def __init__(self, paths_config_key=None, parent=None): self.paths_config_key = paths_config_key - super(PathChooser, self).__init__(parent=parent) + super().__init__(parent=parent) self.chooser_handler = PathChoosersHandler() self.chooser_handler.register_chooser(self) self.set_auto_completer_func(self.on_completion) diff --git a/deluge/ui/gtk3/path_combo_chooser.py b/deluge/ui/gtk3/path_combo_chooser.py index 72e98e497..74d9055b7 100755 --- a/deluge/ui/gtk3/path_combo_chooser.py +++ b/deluge/ui/gtk3/path_combo_chooser.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # # Copyright (C) 2013 Bro <bro.development@gmail.com> # @@ -8,15 +7,13 @@ # See LICENSE for more details. # -from __future__ import division, print_function, unicode_literals - import os import warnings from gi.repository import Gdk, GObject, Gtk from gi.repository.GObject import SignalFlags -from deluge.common import PY2, resource_filename +from deluge.common import resource_filename from deluge.path_chooser_common import get_completion_paths # Filter the pygobject signal warning: @@ -64,7 +61,7 @@ def path_without_trailing_path_sep(path): return path -class ValueList(object): +class ValueList: paths_without_trailing_path_sep = False @@ -176,7 +173,7 @@ class ValueList(object): """ for i, row in enumerate(self.tree_store): if row[0] == value: - self.treeview.set_cursor((i)) + self.treeview.set_cursor(i) return # The value was not found if select_first: @@ -374,7 +371,7 @@ class StoredValuesList(ValueList): """ # This is left click if event.button != 3: - super(StoredValuesList, self).on_treeview_mouse_button_press_event( + super().on_treeview_mouse_button_press_event( treeview, event, double_click=True ) return False @@ -412,9 +409,7 @@ class StoredValuesList(ValueList): PathChooserPopup.popup(self) def on_stored_values_treeview_key_press_event(self, widget, event): - super(StoredValuesList, self).on_value_list_treeview_key_press_event( - widget, event - ) + super().on_value_list_treeview_key_press_event(widget, event) # Prevent the default event handler to move the cursor in the list if key_is_up_or_down(event.keyval): return True @@ -479,9 +474,9 @@ class CompletionList(ValueList): ] = self.on_completion_treeview_motion_notify_event # Add super class signal handler - self.signal_handlers['on_completion_treeview_mouse_button_press_event'] = super( - CompletionList, self - ).on_treeview_mouse_button_press_event + self.signal_handlers[ + 'on_completion_treeview_mouse_button_press_event' + ] = super().on_treeview_mouse_button_press_event def reduce_values(self, prefix): """ @@ -499,9 +494,7 @@ class CompletionList(ValueList): self.add_values(matching_values, clear=True) def on_completion_treeview_key_press_event(self, widget, event): - ret = super(CompletionList, self).on_value_list_treeview_key_press_event( - widget, event - ) + ret = super().on_value_list_treeview_key_press_event(widget, event) if ret: return ret keyval = event.keyval @@ -529,7 +522,7 @@ class CompletionList(ValueList): self.handle_list_scroll(path=path[0], _next=None) -class PathChooserPopup(object): +class PathChooserPopup: """This creates the popop window for the ComboEntry.""" def __init__(self, min_visible_rows, max_visible_rows, popup_alignment_widget): @@ -983,7 +976,7 @@ class PathCompletionPopup(CompletionList, PathChooserPopup): return True -class PathAutoCompleter(object): +class PathAutoCompleter: def __init__(self, builder, path_entry, max_visible_rows): self.completion_popup = PathCompletionPopup( builder, path_entry, max_visible_rows @@ -1106,9 +1099,7 @@ class PathAutoCompleter(object): class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject): __gsignals__ = { - signal - if not PY2 - else signal.encode(): (SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,)) + signal: (SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,)) for signal in [ 'text-changed', 'accelerator-set', @@ -1414,7 +1405,7 @@ class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject): self.set_text(self.get_text()) def _on_entry_combobox_hbox_realize(self, widget): - """ Must do this when the widget is realized """ + """Must do this when the widget is realized""" self.set_filechooser_button_visible(self.filechooser_visible) self.set_path_entry_visible(self.path_entry_visible) @@ -1466,7 +1457,7 @@ class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject): ) return True elif is_ascii_value(keyval, 's'): - super(PathChooserComboBox, self).add_current_value_to_saved_list() + super().add_current_value_to_saved_list() return True elif is_ascii_value(keyval, 'd'): # Set the default value in the text entry @@ -1696,7 +1687,7 @@ if __name__ == '__main__': box1 = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=0) def get_resource2(filename): - return '%s/glade/%s' % (os.path.abspath(os.path.dirname(sys.argv[0])), filename) + return f'{os.path.abspath(os.path.dirname(sys.argv[0]))}/glade/{filename}' # Override get_resource which fetches from deluge install # get_resource = get_resource2 diff --git a/deluge/ui/gtk3/peers_tab.py b/deluge/ui/gtk3/peers_tab.py index e0282becf..b458f7a7d 100644 --- a/deluge/ui/gtk3/peers_tab.py +++ b/deluge/ui/gtk3/peers_tab.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os.path @@ -42,18 +39,12 @@ from .torrentview_data_funcs import ( cell_data_speed_up, ) -try: - from future_builtins import zip -except ImportError: - # Ignore on Py3. - pass - log = logging.getLogger(__name__) class PeersTab(Tab): def __init__(self): - super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label') + super().__init__('Peers', 'peers_tab', 'peers_tab_label') self.peer_menu = self.main_builder.get_object('menu_peer_tab') component.get('MainWindow').connect_signals(self) @@ -313,7 +304,7 @@ class PeersTab(Tab): ip_int = int( binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16 ) - peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1]) + peer_ip = '[{}]:{}'.format(ip, peer['ip'].split(':')[-1]) if peer['seed']: icon = self.seed_pixbuf diff --git a/deluge/ui/gtk3/piecesbar.py b/deluge/ui/gtk3/piecesbar.py index 549f9c048..8665328c0 100644 --- a/deluge/ui/gtk3/piecesbar.py +++ b/deluge/ui/gtk3/piecesbar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me> # @@ -7,15 +6,13 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - from math import pi import gi # isort:skip (Version check required before import). -gi.require_version('PangoCairo', '1.0') # NOQA: E402 -gi.require_foreign('cairo') # NOQA: E402 -gi.require_version('cairo', '1.0') # NOQA: E402 +gi.require_version('PangoCairo', '1.0') +gi.require_foreign('cairo') +gi.require_version('cairo', '1.0') # isort:imports-thirdparty import cairo # Backward compat cairo <= 1.15 @@ -24,7 +21,6 @@ from gi.repository.Gtk import DrawingArea, ProgressBar, StateFlags from gi.repository.Pango import SCALE, Weight # isort:imports-firstparty -from deluge.common import PY2 from deluge.configmanager import ConfigManager COLOR_STATES = ['missing', 'waiting', 'downloading', 'completed'] @@ -32,10 +28,10 @@ COLOR_STATES = ['missing', 'waiting', 'downloading', 'completed'] class PiecesBar(DrawingArea): # Draw in response to an draw - __gsignals__ = {'draw': 'override'} if not PY2 else {b'draw': b'override'} + __gsignals__ = {'draw': 'override'} def __init__(self): - super(PiecesBar, self).__init__() + super().__init__() # Get progress bar styles, in order to keep font consistency pb = ProgressBar() pb_style = pb.get_style_context() diff --git a/deluge/ui/gtk3/pluginmanager.py b/deluge/ui/gtk3/pluginmanager.py index d60f8d390..63353c0df 100644 --- a/deluge/ui/gtk3/pluginmanager.py +++ b/deluge/ui/gtk3/pluginmanager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component diff --git a/deluge/ui/gtk3/preferences.py b/deluge/ui/gtk3/preferences.py index 13930fc55..a008a9562 100644 --- a/deluge/ui/gtk3/preferences.py +++ b/deluge/ui/gtk3/preferences.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me> @@ -8,11 +7,10 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os from hashlib import sha1 as sha +from urllib.parse import urlparse from gi import require_version from gi.repository import Gtk @@ -21,6 +19,7 @@ from gi.repository.Gdk import Color import deluge.common import deluge.component as component from deluge.configmanager import ConfigManager, get_config_dir +from deluge.decorators import maybe_coroutine from deluge.error import AuthManagerError, NotAuthorizedError from deluge.i18n import get_languages from deluge.ui.client import client @@ -31,12 +30,6 @@ from .dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog from .path_chooser import PathChooser try: - from urllib.parse import urlparse -except ImportError: - # PY2 fallback - from urlparse import urlparse # pylint: disable=ungrouped-imports - -try: require_version('AppIndicator3', '0.1') from gi.repository import AppIndicator3 # noqa: F401 except (ImportError, ValueError): @@ -679,11 +672,15 @@ class Preferences(component.Component): 'chk_random_outgoing_ports' ).get_active() incoming_address = self.builder.get_object('entry_interface').get_text().strip() - if deluge.common.is_ip(incoming_address) or not incoming_address: + if deluge.common.is_interface(incoming_address) or not incoming_address: new_core_config['listen_interface'] = incoming_address - new_core_config['outgoing_interface'] = ( + outgoing_address = ( self.builder.get_object('entry_outgoing_interface').get_text().strip() ) + if deluge.common.is_interface(outgoing_address) or not outgoing_address: + new_core_config['outgoing_interface'] = ( + self.builder.get_object('entry_outgoing_interface').get_text().strip() + ) new_core_config['peer_tos'] = self.builder.get_object( 'entry_peer_tos' ).get_text() @@ -948,6 +945,7 @@ class Preferences(component.Component): def hide(self): self.window_open = False + self.builder.get_object('port_spinner').stop() self.builder.get_object('port_img').hide() self.pref_dialog.hide() @@ -1092,6 +1090,8 @@ class Preferences(component.Component): log.debug('on_test_port_clicked') def on_get_test(status): + self.builder.get_object('port_spinner').stop() + self.builder.get_object('port_spinner').hide() if status: self.builder.get_object('port_img').set_from_icon_name( 'emblem-ok-symbolic', Gtk.IconSize.MENU @@ -1104,12 +1104,9 @@ class Preferences(component.Component): self.builder.get_object('port_img').show() client.core.test_listen_port().addCallback(on_get_test) - # XXX: Consider using gtk.Spinner() instead of the loading gif - # It requires gtk.ver > 2.12 - self.builder.get_object('port_img').set_from_file( - deluge.common.get_pixmap('loading.gif') - ) - self.builder.get_object('port_img').show() + self.builder.get_object('port_spinner').start() + self.builder.get_object('port_spinner').show() + self.builder.get_object('port_img').hide() client.force_call() def on_plugin_toggled(self, renderer, path): @@ -1333,58 +1330,46 @@ class Preferences(component.Component): (model, itr) = treeselection.get_selected() if not itr: return - username = model[itr][0] - if username: + level = model[itr][1] + if level: self.builder.get_object('accounts_edit').set_sensitive(True) self.builder.get_object('accounts_delete').set_sensitive(True) else: self.builder.get_object('accounts_edit').set_sensitive(False) self.builder.get_object('accounts_delete').set_sensitive(False) - def on_accounts_add_clicked(self, widget): + @maybe_coroutine + async def on_accounts_add_clicked(self, widget): dialog = AccountDialog( levels_mapping=client.auth_levels_mapping, parent=self.pref_dialog ) + response = await dialog.run() + if response != Gtk.ResponseType.OK: + return - def dialog_finished(response_id): - username = dialog.get_username() - password = dialog.get_password() - authlevel = dialog.get_authlevel() - - def add_ok(rv): - accounts_iter = self.accounts_liststore.append() - self.accounts_liststore.set_value( - accounts_iter, ACCOUNTS_USERNAME, username - ) - self.accounts_liststore.set_value( - accounts_iter, ACCOUNTS_LEVEL, authlevel - ) - self.accounts_liststore.set_value( - accounts_iter, ACCOUNTS_PASSWORD, password - ) - - def add_fail(failure): - if failure.type == AuthManagerError: - ErrorDialog( - _('Error Adding Account'), - _('Authentication failed'), - parent=self.pref_dialog, - details=failure.getErrorMessage(), - ).run() - else: - ErrorDialog( - _('Error Adding Account'), - _('An error occurred while adding account'), - parent=self.pref_dialog, - details=failure.getErrorMessage(), - ).run() - - if response_id == Gtk.ResponseType.OK: - client.core.create_account(username, password, authlevel).addCallback( - add_ok - ).addErrback(add_fail) - - dialog.run().addCallback(dialog_finished) + account = dialog.account + try: + await client.core.create_account(*account) + except AuthManagerError as ex: + return ErrorDialog( + _('Error Adding Account'), + _('Authentication failed'), + parent=self.pref_dialog, + details=ex, + ).run() + except Exception as ex: + return ErrorDialog( + _('Error Adding Account'), + _(f'An error occurred while adding account: {account}'), + parent=self.pref_dialog, + details=ex, + ).run() + + self.accounts_liststore.set( + self.accounts_liststore.append(), + [ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD], + [account.username, account.authlevel, account.password], + ) def on_accounts_edit_clicked(self, widget): (model, itr) = self.accounts_listview.get_selection().get_selected() diff --git a/deluge/ui/gtk3/queuedtorrents.py b/deluge/ui/gtk3/queuedtorrents.py index 0f08c24c6..6fdecec76 100644 --- a/deluge/ui/gtk3/queuedtorrents.py +++ b/deluge/ui/gtk3/queuedtorrents.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os.path diff --git a/deluge/ui/gtk3/removetorrentdialog.py b/deluge/ui/gtk3/removetorrentdialog.py index 48806a5d5..06fca7704 100644 --- a/deluge/ui/gtk3/removetorrentdialog.py +++ b/deluge/ui/gtk3/removetorrentdialog.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os @@ -21,7 +18,7 @@ from deluge.ui.client import client log = logging.getLogger(__name__) -class RemoveTorrentDialog(object): +class RemoveTorrentDialog: """ This class is used to create and show a Remove Torrent Dialog. diff --git a/deluge/ui/gtk3/sidebar.py b/deluge/ui/gtk3/sidebar.py index 1d751918f..5a2b15466 100644 --- a/deluge/ui/gtk3/sidebar.py +++ b/deluge/ui/gtk3/sidebar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com> @@ -8,8 +7,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from gi.repository.Gtk import Label, PolicyType, ScrolledWindow diff --git a/deluge/ui/gtk3/status_tab.py b/deluge/ui/gtk3/status_tab.py index 938c2dd7d..6a9010b6f 100644 --- a/deluge/ui/gtk3/status_tab.py +++ b/deluge/ui/gtk3/status_tab.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - import logging import deluge.component as component @@ -32,7 +29,7 @@ log = logging.getLogger(__name__) class StatusTab(Tab): def __init__(self): - super(StatusTab, self).__init__('Status', 'status_tab', 'status_tab_label') + super().__init__('Status', 'status_tab', 'status_tab_label') self.config = ConfigManager('gtk3ui.conf') diff --git a/deluge/ui/gtk3/statusbar.py b/deluge/ui/gtk3/statusbar.py index 18db753fa..0a2e80095 100644 --- a/deluge/ui/gtk3/statusbar.py +++ b/deluge/ui/gtk3/statusbar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - import logging from gi.repository import Gtk @@ -25,7 +22,7 @@ from .dialogs import OtherDialog log = logging.getLogger(__name__) -class StatusBarItem(object): +class StatusBarItem: def __init__( self, image=None, @@ -320,18 +317,22 @@ class StatusBar(component.Component): def send_status_request(self): # Sends an async request for data from the core keys = [ - 'num_peers', + 'peer.num_peers_connected', 'upload_rate', 'download_rate', 'payload_upload_rate', 'payload_download_rate', + 'net.sent_bytes', + 'net.recv_bytes', + 'net.sent_payload_bytes', + 'net.recv_payload_bytes', ] if self.dht_status: - keys.append('dht_nodes') + keys.append('dht.dht_nodes') if not self.health: - keys.append('has_incoming_connections') + keys.append('net.has_incoming_connections') client.core.get_session_status(keys).addCallback(self._on_get_session_status) client.core.get_free_space().addCallback(self._on_get_free_space) @@ -370,18 +371,18 @@ class StatusBar(component.Component): self.upload_protocol_rate = ( status['upload_rate'] - status['payload_upload_rate'] ) // 1024 - self.num_connections = status['num_peers'] + self.num_connections = status['peer.num_peers_connected'] self.update_download_label() self.update_upload_label() self.update_traffic_label() self.update_connections_label() - if 'dht_nodes' in status: - self.dht_nodes = status['dht_nodes'] + if 'dht.dht_nodes' in status: + self.dht_nodes = status['dht.dht_nodes'] self.update_dht_label() - if 'has_incoming_connections' in status: - self.health = status['has_incoming_connections'] + if 'net.has_incoming_connections' in status: + self.health = status['net.has_incoming_connections'] if self.health: self.remove_item(self.health_item) @@ -412,7 +413,7 @@ class StatusBar(component.Component): if self.max_connections_global < 0: label_string = '%s' % self.num_connections else: - label_string = '%s <small>(%s)</small>' % ( + label_string = '{} <small>({})</small>'.format( self.num_connections, self.max_connections_global, ) diff --git a/deluge/ui/gtk3/systemtray.py b/deluge/ui/gtk3/systemtray.py index a2435223b..f65fde590 100644 --- a/deluge/ui/gtk3/systemtray.py +++ b/deluge/ui/gtk3/systemtray.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os @@ -238,13 +235,13 @@ class SystemTray(component.Component): if max_download_speed == -1: max_download_speed = _('Unlimited') else: - max_download_speed = '%s %s' % (max_download_speed, _('K/s')) + max_download_speed = '{} {}'.format(max_download_speed, _('K/s')) if max_upload_speed == -1: max_upload_speed = _('Unlimited') else: - max_upload_speed = '%s %s' % (max_upload_speed, _('K/s')) + max_upload_speed = '{} {}'.format(max_upload_speed, _('K/s')) - msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % ( + msg = '{}\n{}: {} ({})\n{}: {} ({})'.format( _('Deluge'), _('Down'), self.download_rate, diff --git a/deluge/ui/gtk3/tab_data_funcs.py b/deluge/ui/gtk3/tab_data_funcs.py index 6fa0ba59c..a78994f69 100644 --- a/deluge/ui/gtk3/tab_data_funcs.py +++ b/deluge/ui/gtk3/tab_data_funcs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,14 +6,12 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - from deluge.common import fdate, fsize, fspeed, ftime from deluge.ui.common import TRACKER_STATUS_TRANSLATION def ftotal_sized(first, second): - return '%s (%s)' % (fsize(first, shortform=True), fsize(second, shortform=True)) + return f'{fsize(first, shortform=True)} ({fsize(second, shortform=True)})' def fratio(value): @@ -24,7 +21,7 @@ def fratio(value): def fpcnt(value, state, message): state_i18n = _(state) if state not in ('Error', 'Seeding') and value < 100: - percent = '{:.2f}'.format(value).rstrip('0').rstrip('.') + percent = f'{value:.2f}'.rstrip('0').rstrip('.') return _('{state} {percent}%').format(state=state_i18n, percent=percent) elif state == 'Error': return _('{state}: {err_msg}').format(state=state_i18n, err_msg=message) @@ -34,7 +31,7 @@ def fpcnt(value, state, message): def fspeed_max(value, max_value=-1): value = fspeed(value, shortform=True) - return '%s (%s %s)' % (value, max_value, _('K/s')) if max_value > -1 else value + return '{} ({} {})'.format(value, max_value, _('K/s')) if max_value > -1 else value def fdate_or_never(value): @@ -73,7 +70,7 @@ def fseed_rank_or_dash(seed_rank, seeding_time): def fpieces_num_size(num_pieces, piece_size): - return '%s (%s)' % (num_pieces, fsize(piece_size, precision=0)) + return f'{num_pieces} ({fsize(piece_size, precision=0)})' def fcount(value): diff --git a/deluge/ui/gtk3/toolbar.py b/deluge/ui/gtk3/toolbar.py index 7bc029e4b..1b6952e74 100644 --- a/deluge/ui/gtk3/toolbar.py +++ b/deluge/ui/gtk3/toolbar.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from gi.repository.Gtk import SeparatorToolItem, ToolButton diff --git a/deluge/ui/gtk3/torrentdetails.py b/deluge/ui/gtk3/torrentdetails.py index a586c2997..08c37a1de 100644 --- a/deluge/ui/gtk3/torrentdetails.py +++ b/deluge/ui/gtk3/torrentdetails.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # @@ -9,8 +8,6 @@ """The torrent details component shows info about the selected torrent.""" -from __future__ import unicode_literals - import logging from collections import namedtuple @@ -33,7 +30,7 @@ log = logging.getLogger(__name__) TabWidget = namedtuple('TabWidget', ('obj', 'func', 'status_keys')) -class Tab(object): +class Tab: def __init__(self, name=None, child_widget=None, tab_label=None): self._name = name self.is_visible = True diff --git a/deluge/ui/gtk3/torrentview.py b/deluge/ui/gtk3/torrentview.py index 3aee11e5c..16de16ea7 100644 --- a/deluge/ui/gtk3/torrentview.py +++ b/deluge/ui/gtk3/torrentview.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -8,8 +7,6 @@ # """The torrent view component that lists all torrents in the session.""" -from __future__ import unicode_literals - import logging from locale import strcoll @@ -77,13 +74,13 @@ def eta_column_sort(model, iter1, iter2, data): if v1 == v2: return 0 if v1 == 0: - return 1 - if v2 == 0: return -1 - if v1 > v2: + if v2 == 0: return 1 - if v2 > v1: + if v1 > v2: return -1 + if v2 > v1: + return 1 def seed_peer_column_sort(model, iter1, iter2, data): @@ -107,7 +104,7 @@ def progress_sort(model, iter1, iter2, sort_column_id): return cmp(progress1, progress2) -class SearchBox(object): +class SearchBox: def __init__(self, torrentview): self.torrentview = torrentview mainwindow = component.get('MainWindow') diff --git a/deluge/ui/gtk3/torrentview_data_funcs.py b/deluge/ui/gtk3/torrentview_data_funcs.py index 8bd1f9c51..0b2545d8c 100644 --- a/deluge/ui/gtk3/torrentview_data_funcs.py +++ b/deluge/ui/gtk3/torrentview_data_funcs.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import print_function, unicode_literals - import warnings from functools import partial @@ -17,7 +14,7 @@ import deluge.component as component from .common import ( create_blank_pixbuf, - get_pixbuf_at_size, + get_pixbuf, icon_alert, icon_checking, icon_downloading, @@ -42,7 +39,6 @@ ICON_STATE = { # renderer. This is much cheaper than fetch the current value and test if # it's equal. func_last_value = { - 'cell_data_time': None, 'cell_data_ratio_seeds_peers': None, 'cell_data_ratio_ratio': None, 'cell_data_ratio_avail': None, @@ -86,7 +82,7 @@ def set_tracker_icon(tracker_icon, cell): if tracker_icon: pixbuf = tracker_icon.get_cached_icon() if pixbuf is None: - pixbuf = get_pixbuf_at_size(tracker_icon.get_filename(), 16) + pixbuf = get_pixbuf(tracker_icon.get_filename(), 16) tracker_icon.set_cached_icon(pixbuf) else: pixbuf = create_blank_pixbuf() @@ -162,7 +158,7 @@ def cell_data_speed(cell, model, row, data): if speed > 0: speed_str = common.fspeed(speed, shortform=True) cell.set_property( - 'markup', '{0} <small>{1}</small>'.format(*tuple(speed_str.split())) + 'markup', '{} <small>{}</small>'.format(*tuple(speed_str.split())) ) else: cell.set_property('text', '') @@ -189,7 +185,7 @@ def cell_data_speed_limit(cell, model, row, data, cache_key): if speed > 0: speed_str = common.fspeed(speed * 1024, shortform=True) cell.set_property( - 'markup', '{0} <small>{1}</small>'.format(*tuple(speed_str.split())) + 'markup', '{} <small>{}</small>'.format(*tuple(speed_str.split())) ) else: cell.set_property('text', '') @@ -222,10 +218,6 @@ def cell_data_peer(column, cell, model, row, data): def cell_data_time(column, cell, model, row, data): """Display value as time, eg 1m10s""" time = model.get_value(row, data) - if func_last_value['cell_data_time'] == time: - return - func_last_value['cell_data_time'] = time - if time <= 0: time_str = '' else: diff --git a/deluge/ui/gtk3/trackers_tab.py b/deluge/ui/gtk3/trackers_tab.py index d83b9956c..d671471b0 100644 --- a/deluge/ui/gtk3/trackers_tab.py +++ b/deluge/ui/gtk3/trackers_tab.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import deluge.component as component @@ -22,9 +19,7 @@ log = logging.getLogger(__name__) class TrackersTab(Tab): def __init__(self): - super(TrackersTab, self).__init__( - 'Trackers', 'trackers_tab', 'trackers_tab_label' - ) + super().__init__('Trackers', 'trackers_tab', 'trackers_tab_label') self.add_tab_widget('summary_next_announce', ftime, ('next_announce',)) self.add_tab_widget('summary_tracker', None, ('tracker_host',)) diff --git a/deluge/ui/hostlist.py b/deluge/ui/hostlist.py index b4bb538fb..0fc3eabd8 100644 --- a/deluge/ui/hostlist.py +++ b/deluge/ui/hostlist.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) Calum Lind 2017 <calumlind+deluge@gmail.com> # @@ -7,12 +6,10 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os import uuid -from socket import gaierror, gethostbyname +from socket import gaierror, getaddrinfo from twisted.internet import defer @@ -25,7 +22,7 @@ log = logging.getLogger(__name__) DEFAULT_HOST = '127.0.0.1' DEFAULT_PORT = 58846 -LOCALHOST = ('127.0.0.1', 'localhost') +LOCALHOST = ('127.0.0.1', 'localhost', '::1') def default_hostlist(): @@ -47,7 +44,7 @@ def validate_host_info(hostname, port): """ try: - gethostbyname(hostname) + getaddrinfo(hostname, None) except gaierror as ex: raise ValueError('Host %s: %s', hostname, ex.args[1]) @@ -87,7 +84,15 @@ def migrate_config_2_to_3(config): return config -class HostList(object): +def mask_hosts_password(hosts): + """Replace passwords in hosts list with *'s for log output""" + if not hosts: + return hosts + + return [list(host)[:-1] + ['*' * 10] for host in hosts] + + +class HostList: """This class contains methods for adding, removing and looking up hosts in hostlist.conf.""" def __init__(self): @@ -97,6 +102,7 @@ class HostList(object): default_hostlist(), config_dir=get_config_dir(), file_version=3, + log_mask_funcs={'hosts': mask_hosts_password}, ) self.config.run_converter((1, 2), 3, migrate_config_2_to_3) self.config.save() @@ -210,30 +216,35 @@ class HostList(object): return defer.succeed(status_offline) try: - ip = gethostbyname(host) - except gaierror as ex: - log.error('Error resolving host %s to ip: %s', host, ex.args[1]) + ips = list({addrinfo[4][0] for addrinfo in getaddrinfo(host, None)}) + except (gaierror, IndexError) as ex: + log.warning('Unable to resolve host %s to IP: %s', host, ex.args[1]) return defer.succeed(status_offline) - host_conn_info = ( - ip, - port, - 'localclient' if not user and host in LOCALHOST else user, - ) - if client.connected() and host_conn_info == client.connection_info(): - # Currently connected to host_id daemon. - def on_info(info, host_id): - log.debug('Client connected, query info: %s', info) - return host_id, 'Connected', info - - return client.daemon.info().addCallback(on_info, host_id) - else: - # Attempt to connect to daemon with host_id details. - c = Client() - d = c.connect(host, port, skip_authentication=True) - d.addCallback(on_connect, c, host_id) - d.addErrback(on_connect_failed, host_id) - return d + host_conn_list = [ + ( + host_ip, + port, + 'localclient' if not user and host_ip in LOCALHOST else user, + ) + for host_ip in ips + ] + + for host_conn_info in host_conn_list: + if client.connected() and host_conn_info == client.connection_info(): + # Currently connected to host_id daemon. + def on_info(info, host_id): + log.debug('Client connected, query info: %s', info) + return host_id, 'Connected', info + + return client.daemon.info().addCallback(on_info, host_id) + else: + # Attempt to connect to daemon with host_id details. + c = Client() + d = c.connect(host, port, skip_authentication=True) + d.addCallback(on_connect, c, host_id) + d.addErrback(on_connect_failed, host_id) + return d def update_host(self, host_id, hostname, port, username, password): """Update the supplied host id with new connection details. diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py index 5af8e79cd..b50ba6c3d 100644 --- a/deluge/ui/sessionproxy.py +++ b/deluge/ui/sessionproxy.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2010 Andrew Resch <andrewresch@gmail.com> # @@ -6,8 +5,6 @@ # the additional special exception to link portions of this program with the OpenSSL library. # See LICENSE for more details. # -from __future__ import unicode_literals - import logging from time import time @@ -148,11 +145,17 @@ class SessionProxy(component.Component): def on_status(result, torrent_id): t = time() - self.torrents[torrent_id][0] = t - self.torrents[torrent_id][1].update(result) - for key in keys_to_get: - self.cache_times[torrent_id][key] = t - return self.create_status_dict([torrent_id], keys)[torrent_id] + try: + self.torrents[torrent_id][0] = t + self.torrents[torrent_id][1].update(result) + for key in keys_to_get: + self.cache_times[torrent_id][key] = t + return self.create_status_dict([torrent_id], keys)[torrent_id] + except KeyError: + log.debug( + f'Status missing for torrent (removed?): {torrent_id}' + ) + return {} return d.addCallback(on_status, torrent_id) else: diff --git a/deluge/ui/tracker_icons.py b/deluge/ui/tracker_icons.py index c10cd2f8e..5f619af63 100644 --- a/deluge/ui/tracker_icons.py +++ b/deluge/ui/tracker_icons.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com> # @@ -7,14 +6,14 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os -from tempfile import mkstemp +import tempfile +from html.parser import HTMLParser +from urllib.parse import urljoin, urlparse from twisted.internet import defer, threads -from twisted.web.error import PageRedirect +from twisted.python.failure import Failure from twisted.web.resource import ForbiddenResource, NoResource from deluge.component import Component @@ -23,12 +22,9 @@ from deluge.decorators import proxy from deluge.httpdownloader import download_file try: - from html.parser import HTMLParser - from urllib.parse import urljoin, urlparse + import chardet except ImportError: - # PY2 fallback - from HTMLParser import HTMLParser - from urlparse import urljoin, urlparse # pylint: disable=ungrouped-imports + chardet = None try: from PIL import Image @@ -38,7 +34,7 @@ except ImportError: log = logging.getLogger(__name__) -class TrackerIcon(object): +class TrackerIcon: """ Represents a tracker's icon """ @@ -207,17 +203,19 @@ class TrackerIcons(Component): else: # We need to fetch it self.pending[host] = [] + tmp_file = tempfile.mkstemp(prefix='deluge_trackericon_html.') + filename = tmp_file[1] # Start callback chain - d = self.download_page(host) + d = self.download_page(host, filename) d.addCallbacks( self.on_download_page_complete, self.on_download_page_fail, - errbackArgs=(host,), ) d.addCallback(self.parse_html_page) d.addCallbacks( self.on_parse_complete, self.on_parse_fail, callbackArgs=(host,) ) + d.addBoth(self.del_tmp_file, tmp_file) d.addCallback(self.download_icon, host) d.addCallbacks( self.on_download_icon_complete, @@ -229,24 +227,38 @@ class TrackerIcons(Component): d.addCallback(self.store_icon, host) return d - def download_page(self, host, url=None): - """ - Downloads a tracker host's page + @staticmethod + def del_tmp_file(result, tmp_file): + """Remove tmp_file created when downloading tracker page""" + fd, filename = tmp_file + try: + os.close(fd) + os.remove(filename) + except OSError: + log.debug(f'Unable to delete temporary file: {filename}') + + return result + + def download_page( + self, host: str, filename: str, url: str = None + ) -> 'defer.Deferred[str]': + """Downloads a tracker host's page + If no url is provided, it bases the url on the host - :param host: the tracker host - :type host: string - :param url: the (optional) url of the host - :type url: string - :returns: the filename of the tracker host's page - :rtype: Deferred + Args: + host: The tracker host + filename: Location to download page + url: The url of the host + + Returns: + The filename of the tracker host's page """ if not url: url = self.host_to_url(host) - log.debug('Downloading %s %s', host, url) - tmp_fd, tmp_file = mkstemp(prefix='deluge_ticon.') - os.close(tmp_fd) - return download_file(url, tmp_file, force_filename=True, handle_redirects=False) + + log.debug(f'Downloading {host} {url} to {filename}') + return download_file(url, filename, force_filename=True) def on_download_page_complete(self, page): """ @@ -260,33 +272,18 @@ class TrackerIcons(Component): log.debug('Finished downloading %s', page) return page - def on_download_page_fail(self, f, host): - """ - Recovers from download error + def on_download_page_fail(self, failure: 'Failure') -> 'Failure': + """Runs any download failure clean-up functions - :param f: the failure that occurred - :type f: Failure - :param host: the name of the host whose page failed to download - :type host: string - :returns: a Deferred if recovery was possible - else the original failure - :rtype: Deferred or Failure - """ - error_msg = f.getErrorMessage() - log.debug('Error downloading page: %s', error_msg) - d = f - if f.check(PageRedirect): - # Handle redirect errors - location = urljoin(self.host_to_url(host), error_msg.split(' to ')[1]) - self.redirects[host] = url_to_host(location) - d = self.download_page(host, url=location) - d.addCallbacks( - self.on_download_page_complete, - self.on_download_page_fail, - errbackArgs=(host,), - ) + Args: + failure: The failure that occurred. - return d + Returns: + The original failure. + + """ + log.debug(f'Error downloading page: {failure.getErrorMessage()}') + return failure @proxy(threads.deferToThread) def parse_html_page(self, page): @@ -298,17 +295,19 @@ class TrackerIcons(Component): :returns: a Deferred which callbacks a list of available favicons (url, type) :rtype: Deferred """ - with open(page, 'r') as _file: + encoding = 'UTF-8' + if chardet: + with open(page, 'rb') as _file: + result = chardet.detect(_file.read()) + encoding = result['encoding'] + + with open(page, encoding=encoding) as _file: parser = FaviconParser() for line in _file: parser.feed(line) if parser.left_head: break parser.close() - try: - os.remove(page) - except OSError as ex: - log.warning('Could not remove temp file: %s', ex) return parser.get_icons() @@ -382,7 +381,7 @@ class TrackerIcons(Component): try: with Image.open(icon_name): pass - except IOError as ex: + except OSError as ex: raise InvalidIconError(ex) else: if not os.path.getsize(icon_name): @@ -423,22 +422,7 @@ class TrackerIcons(Component): error_msg = f.getErrorMessage() log.debug('Error downloading icon from %s: %s', host, error_msg) d = f - if f.check(PageRedirect): - # Handle redirect errors - location = urljoin(self.host_to_url(host), error_msg.split(' to ')[1]) - d = self.download_icon( - [(location, extension_to_mimetype(location.rpartition('.')[2]))] - + icons, - host, - ) - if not icons: - d.addCallbacks( - self.on_download_icon_complete, - self.on_download_icon_fail, - callbackArgs=(host,), - errbackArgs=(host,), - ) - elif f.check(NoResource, ForbiddenResource) and icons: + if f.check(NoResource, ForbiddenResource) and icons: d = self.download_icon(icons, host) elif f.check(NoIconsError): # No icons, try favicon.ico as an act of desperation @@ -477,14 +461,17 @@ class TrackerIcons(Component): # Requires Pillow(PIL) to resize. if icon and Image: filename = icon.get_filename() + remove_old = False with Image.open(filename) as img: if img.size > (16, 16): new_filename = filename.rpartition('.')[0] + '.png' img = img.resize((16, 16), Image.ANTIALIAS) img.save(new_filename) if new_filename != filename: - os.remove(filename) - icon = TrackerIcon(new_filename) + remove_old = True + if remove_old: + os.remove(filename) + icon = TrackerIcon(new_filename) return icon def store_icon(self, icon, host): @@ -617,11 +604,13 @@ MIME_MAP = { 'image/png': 'png', 'image/vnd.microsoft.icon': 'ico', 'image/x-icon': 'ico', + 'image/svg+xml': 'svg', 'gif': 'image/gif', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'ico': 'image/vnd.microsoft.icon', + 'svg': 'image/svg+xml', } diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py index 0986ec777..338f8a8e0 100644 --- a/deluge/ui/ui.py +++ b/deluge/ui/ui.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # @@ -7,9 +6,8 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging +import sys import deluge.common import deluge.configmanager @@ -27,7 +25,7 @@ except ImportError: return -class UI(object): +class UI: """ Base class for UI implementations. @@ -60,7 +58,7 @@ class UI(object): return self.__options def start(self, parser=None): - args = deluge.common.unicode_argv()[1:] + args = sys.argv[1:] if parser is None: parser = self.parser self.__options = self.parse_args(parser, args) diff --git a/deluge/ui/ui_entry.py b/deluge/ui/ui_entry.py index 71ce83783..e185fda33 100644 --- a/deluge/ui/ui_entry.py +++ b/deluge/ui/ui_entry.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com> # Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me> @@ -12,8 +11,6 @@ # user runs the command 'deluge'. """Main starting point for Deluge""" -from __future__ import unicode_literals - import argparse import logging import os @@ -100,7 +97,7 @@ def start_ui(): # If the UI is set as default, indicate this in help by prefixing with a star. subactions = subparsers._get_subactions() prefix = '*' if ui == default_ui else ' ' - subactions[-1].metavar = '%s %s' % (prefix, ui) + subactions[-1].metavar = f'{prefix} {ui}' # Insert a default UI subcommand unless one of the ambiguous_args are specified parser.set_default_subparser(default_ui, abort_opts=AMBIGUOUS_CMD_ARGS) @@ -115,7 +112,7 @@ def start_ui(): try: ui = ui_entrypoints[selected_ui]( - prog='%s %s' % (os.path.basename(sys.argv[0]), selected_ui), ui_args=ui_args + prog=f'{os.path.basename(sys.argv[0])} {selected_ui}', ui_args=ui_args ) except KeyError: log.error( diff --git a/deluge/ui/web/__init__.py b/deluge/ui/web/__init__.py index 0be7eedb9..3757e0b1c 100644 --- a/deluge/ui/web/__init__.py +++ b/deluge/ui/web/__init__.py @@ -1,5 +1,3 @@ -from __future__ import unicode_literals - from deluge.ui.web.web import Web diff --git a/deluge/ui/web/auth.py b/deluge/ui/web/auth.py index d631f9186..eacbbf526 100644 --- a/deluge/ui/web/auth.py +++ b/deluge/ui/web/auth.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Damien Churchill <damoxc@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import hashlib import logging import os @@ -64,7 +61,7 @@ class Auth(JSONComponent): """ def __init__(self, config): - super(Auth, self).__init__('Auth') + super().__init__('Auth') self.worker = LoopingCall(self._clean_sessions) self.config = config diff --git a/deluge/ui/web/common.py b/deluge/ui/web/common.py index 475f33565..32c29c8c0 100644 --- a/deluge/ui/web/common.py +++ b/deluge/ui/web/common.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Damien Churchill <damoxc@gmail.com> # @@ -7,19 +6,15 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import gettext from mako.template import Template as MakoTemplate -from deluge.common import PY2, get_version +from deluge.common import get_version def _(text): text_local = gettext.gettext(text) - if PY2: - return text_local.decode('utf-8') return text_local diff --git a/deluge/ui/web/css/deluge.css b/deluge/ui/web/css/deluge.css index c026b6d8e..946028639 100644 --- a/deluge/ui/web/css/deluge.css +++ b/deluge/ui/web/css/deluge.css @@ -6,6 +6,8 @@ body { border: 0 none; overflow: hidden; height: 100%; + color: black; + background: white; } input { diff --git a/deluge/ui/web/js/deluge-all/AboutWindow.js b/deluge/ui/web/js/deluge-all/AboutWindow.js index 15fede9b8..cfae7a862 100644 --- a/deluge/ui/web/js/deluge-all/AboutWindow.js +++ b/deluge/ui/web/js/deluge-all/AboutWindow.js @@ -104,8 +104,7 @@ Deluge.about.AboutWindow = Ext.extend(Ext.Window, { { xtype: 'label', style: 'padding-top: 5px; font-size: 12px;', - html: - '<a href="https://deluge-torrent.org" target="_blank">deluge-torrent.org</a>', + html: '<a href="https://deluge-torrent.org" target="_blank">deluge-torrent.org</a>', }, ]); this.addButton(_('Close'), this.onCloseClick, this); diff --git a/deluge/ui/web/js/deluge-all/AddTrackerWindow.js b/deluge/ui/web/js/deluge-all/AddTrackerWindow.js index 8fbe0b221..aaf4a3ff9 100644 --- a/deluge/ui/web/js/deluge-all/AddTrackerWindow.js +++ b/deluge/ui/web/js/deluge-all/AddTrackerWindow.js @@ -10,7 +10,8 @@ Ext.ns('Deluge'); // Custom VType validator for tracker urls -var trackerUrlTest = /(((^https?)|(^udp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i; +var trackerUrlTest = + /(((^https?)|(^udp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i; Ext.apply(Ext.form.VTypes, { trackerUrl: function (val, field) { return trackerUrlTest.test(val); diff --git a/deluge/ui/web/js/deluge-all/Deluge.js b/deluge/ui/web/js/deluge-all/Deluge.js index 86cae6d89..260ad978f 100644 --- a/deluge/ui/web/js/deluge-all/Deluge.js +++ b/deluge/ui/web/js/deluge-all/Deluge.js @@ -25,11 +25,6 @@ Ext.state.Manager.setProvider( // Add some additional functions to ext and setup some of the // configurable parameters Ext.apply(Ext, { - escapeHTML: function (text) { - text = String(text).replace('<', '<').replace('>', '>'); - return text.replace('&', '&'); - }, - isObjectEmpty: function (obj) { for (var i in obj) { return false; diff --git a/deluge/ui/web/js/deluge-all/EditTrackersWindow.js b/deluge/ui/web/js/deluge-all/EditTrackersWindow.js index f6733aaa6..178fd583f 100644 --- a/deluge/ui/web/js/deluge-all/EditTrackersWindow.js +++ b/deluge/ui/web/js/deluge-all/EditTrackersWindow.js @@ -57,6 +57,7 @@ Deluge.EditTrackersWindow = Ext.extend(Ext.Window, { header: _('Tracker'), width: 0.9, dataIndex: 'url', + tpl: new Ext.XTemplate('{url:htmlEncode}'), }, ], columnSort: { diff --git a/deluge/ui/web/js/deluge-all/FilterPanel.js b/deluge/ui/web/js/deluge-all/FilterPanel.js index b6e5ec5ca..f1fade120 100644 --- a/deluge/ui/web/js/deluge-all/FilterPanel.js +++ b/deluge/ui/web/js/deluge-all/FilterPanel.js @@ -171,5 +171,5 @@ Deluge.FilterPanel.templates = { tracker_host: '<div class="x-deluge-filter" style="background-image: url(' + deluge.config.base + - 'tracker/{filter});">{filter} ({count})</div>', + 'tracker/{filter});">{filter:htmlEncode} ({count})</div>', }; diff --git a/deluge/ui/web/js/deluge-all/Formatters.js b/deluge/ui/web/js/deluge-all/Formatters.js index 443bfdf56..6b09abee5 100644 --- a/deluge/ui/web/js/deluge-all/Formatters.js +++ b/deluge/ui/web/js/deluge-all/Formatters.js @@ -15,7 +15,23 @@ * @version 1.3 * @singleton */ -Deluge.Formatters = { +Deluge.Formatters = (function () { + var charToEntity = { + '&': '&', + '>': '>', + '<': '<', + '"': '"', + "'": ''', + }; + + var charToEntityRegex = new RegExp( + '(' + Object.keys(charToEntity).join('|') + ')', + 'g' + ); + var htmlEncodeReplaceFn = function (match, capture) { + return charToEntity[capture]; + }; + /** * Formats a date string in the date representation of the current locale, * based on the systems timezone. @@ -24,154 +40,162 @@ Deluge.Formatters = { * @return {String} a string in the date representation of the current locale * or "" if seconds < 0. */ - date: function (timestamp) { - function zeroPad(num, count) { - var numZeropad = num + ''; - while (numZeropad.length < count) { - numZeropad = '0' + numZeropad; + return (Formatters = { + date: function (timestamp) { + function zeroPad(num, count) { + var numZeropad = num + ''; + while (numZeropad.length < count) { + numZeropad = '0' + numZeropad; + } + return numZeropad; + } + timestamp = timestamp * 1000; + var date = new Date(timestamp); + return String.format( + '{0}/{1}/{2} {3}:{4}:{5}', + zeroPad(date.getDate(), 2), + zeroPad(date.getMonth() + 1, 2), + date.getFullYear(), + zeroPad(date.getHours(), 2), + zeroPad(date.getMinutes(), 2), + zeroPad(date.getSeconds(), 2) + ); + }, + + /** + * Formats the bytes value into a string with KiB, MiB or GiB units. + * + * @param {Number} bytes the filesize in bytes + * @param {Boolean} showZero pass in true to displays 0 values + * @return {String} formatted string with KiB, MiB or GiB units. + */ + size: function (bytes, showZero) { + if (!bytes && !showZero) return ''; + bytes = bytes / 1024.0; + + if (bytes < 1024) { + return bytes.toFixed(1) + ' KiB'; + } else { + bytes = bytes / 1024; } - return numZeropad; - } - timestamp = timestamp * 1000; - var date = new Date(timestamp); - return String.format( - '{0}/{1}/{2} {3}:{4}:{5}', - zeroPad(date.getDate(), 2), - zeroPad(date.getMonth() + 1, 2), - date.getFullYear(), - zeroPad(date.getHours(), 2), - zeroPad(date.getMinutes(), 2), - zeroPad(date.getSeconds(), 2) - ); - }, - - /** - * Formats the bytes value into a string with KiB, MiB or GiB units. - * - * @param {Number} bytes the filesize in bytes - * @param {Boolean} showZero pass in true to displays 0 values - * @return {String} formatted string with KiB, MiB or GiB units. - */ - size: function (bytes, showZero) { - if (!bytes && !showZero) return ''; - bytes = bytes / 1024.0; - - if (bytes < 1024) { - return bytes.toFixed(1) + ' KiB'; - } else { - bytes = bytes / 1024; - } - - if (bytes < 1024) { - return bytes.toFixed(1) + ' MiB'; - } else { - bytes = bytes / 1024; - } - - return bytes.toFixed(1) + ' GiB'; - }, - - /** - * Formats the bytes value into a string with K, M or G units. - * - * @param {Number} bytes the filesize in bytes - * @param {Boolean} showZero pass in true to displays 0 values - * @return {String} formatted string with K, M or G units. - */ - sizeShort: function (bytes, showZero) { - if (!bytes && !showZero) return ''; - bytes = bytes / 1024.0; - if (bytes < 1024) { - return bytes.toFixed(1) + ' K'; - } else { - bytes = bytes / 1024; - } + if (bytes < 1024) { + return bytes.toFixed(1) + ' MiB'; + } else { + bytes = bytes / 1024; + } - if (bytes < 1024) { - return bytes.toFixed(1) + ' M'; - } else { - bytes = bytes / 1024; - } + return bytes.toFixed(1) + ' GiB'; + }, + + /** + * Formats the bytes value into a string with K, M or G units. + * + * @param {Number} bytes the filesize in bytes + * @param {Boolean} showZero pass in true to displays 0 values + * @return {String} formatted string with K, M or G units. + */ + sizeShort: function (bytes, showZero) { + if (!bytes && !showZero) return ''; + bytes = bytes / 1024.0; + + if (bytes < 1024) { + return bytes.toFixed(1) + ' K'; + } else { + bytes = bytes / 1024; + } - return bytes.toFixed(1) + ' G'; - }, + if (bytes < 1024) { + return bytes.toFixed(1) + ' M'; + } else { + bytes = bytes / 1024; + } - /** - * Formats a string to display a transfer speed utilizing {@link #size} - * - * @param {Number} bytes the number of bytes per second - * @param {Boolean} showZero pass in true to displays 0 values - * @return {String} formatted string with KiB, MiB or GiB units. - */ - speed: function (bytes, showZero) { - return !bytes && !showZero ? '' : fsize(bytes, showZero) + '/s'; - }, + return bytes.toFixed(1) + ' G'; + }, + + /** + * Formats a string to display a transfer speed utilizing {@link #size} + * + * @param {Number} bytes the number of bytes per second + * @param {Boolean} showZero pass in true to displays 0 values + * @return {String} formatted string with KiB, MiB or GiB units. + */ + speed: function (bytes, showZero) { + return !bytes && !showZero ? '' : fsize(bytes, showZero) + '/s'; + }, + + /** + * Formats a string to show time in a human readable form. + * + * @param {Number} time the number of seconds + * @return {String} a formatted time string. will return '' if seconds == 0 + */ + timeRemaining: function (time) { + if (time <= 0) { + return '∞'; + } + time = time.toFixed(0); + if (time < 60) { + return time + 's'; + } else { + time = time / 60; + } - /** - * Formats a string to show time in a human readable form. - * - * @param {Number} time the number of seconds - * @return {String} a formatted time string. will return '' if seconds == 0 - */ - timeRemaining: function (time) { - if (time <= 0) { - return '∞'; - } - time = time.toFixed(0); - if (time < 60) { - return time + 's'; - } else { - time = time / 60; - } - - if (time < 60) { - var minutes = Math.floor(time); - var seconds = Math.round(60 * (time - minutes)); - if (seconds > 0) { - return minutes + 'm ' + seconds + 's'; + if (time < 60) { + var minutes = Math.floor(time); + var seconds = Math.round(60 * (time - minutes)); + if (seconds > 0) { + return minutes + 'm ' + seconds + 's'; + } else { + return minutes + 'm'; + } } else { - return minutes + 'm'; + time = time / 60; } - } else { - time = time / 60; - } - - if (time < 24) { - var hours = Math.floor(time); - var minutes = Math.round(60 * (time - hours)); - if (minutes > 0) { - return hours + 'h ' + minutes + 'm'; + + if (time < 24) { + var hours = Math.floor(time); + var minutes = Math.round(60 * (time - hours)); + if (minutes > 0) { + return hours + 'h ' + minutes + 'm'; + } else { + return hours + 'h'; + } } else { - return hours + 'h'; + time = time / 24; } - } else { - time = time / 24; - } - - var days = Math.floor(time); - var hours = Math.round(24 * (time - days)); - if (hours > 0) { - return days + 'd ' + hours + 'h'; - } else { - return days + 'd'; - } - }, - /** - * Simply returns the value untouched, for when no formatting is required. - * - * @param {Mixed} value the value to be displayed - * @return the untouched value. - */ - plain: function (value) { - return value; - }, - - cssClassEscape: function (value) { - return value.toLowerCase().replace('.', '_'); - }, -}; + var days = Math.floor(time); + var hours = Math.round(24 * (time - days)); + if (hours > 0) { + return days + 'd ' + hours + 'h'; + } else { + return days + 'd'; + } + }, + + /** + * Simply returns the value untouched, for when no formatting is required. + * + * @param {Mixed} value the value to be displayed + * @return the untouched value. + */ + plain: function (value) { + return value; + }, + + cssClassEscape: function (value) { + return value.toLowerCase().replace('.', '_'); + }, + + htmlEncode: function (value) { + return !value + ? value + : String(value).replace(charToEntityRegex, htmlEncodeReplaceFn); + }, + }); +})(); var fsize = Deluge.Formatters.size; var fsize_short = Deluge.Formatters.sizeShort; var fspeed = Deluge.Formatters.speed; @@ -179,3 +203,4 @@ var ftime = Deluge.Formatters.timeRemaining; var fdate = Deluge.Formatters.date; var fplain = Deluge.Formatters.plain; Ext.util.Format.cssClassEscape = Deluge.Formatters.cssClassEscape; +Ext.util.Format.htmlEncode = Deluge.Formatters.htmlEncode; diff --git a/deluge/ui/web/js/deluge-all/TorrentGrid.js b/deluge/ui/web/js/deluge-all/TorrentGrid.js index f664c765c..333d1335c 100644 --- a/deluge/ui/web/js/deluge-all/TorrentGrid.js +++ b/deluge/ui/web/js/deluge-all/TorrentGrid.js @@ -17,7 +17,7 @@ return String.format( '<div class="torrent-name x-deluge-{0}">{1}</div>', r.data['state'].toLowerCase(), - value + Ext.util.Format.htmlEncode(value) ); } function torrentSpeedRenderer(value) { @@ -62,12 +62,14 @@ '<div style="background: url(' + deluge.config.base + 'tracker/{0}) no-repeat; padding-left: 20px;">{0}</div>', - value + Ext.util.Format.htmlEncode(value) ); } function etaSorter(eta) { - return eta * -1; + if (eta === 0) return Number.MAX_VALUE; + if (eta <= -1) return Number.MAX_SAFE_INTEGER; + return eta; } function dateOrNever(date) { @@ -75,7 +77,9 @@ } function timeOrInf(time) { - return time < 0 ? '∞' : ftime(time); + if (time === 0) return ''; + if (time <= -1) return '∞'; + return ftime(time); } /** @@ -320,6 +324,8 @@ { name: 'ratio', type: 'float' }, { name: 'distributed_copies', type: 'float' }, { name: 'time_added', type: 'int' }, + { name: 'last_seen_complete', type: 'int' }, + { name: 'completed_time', type: 'int' }, { name: 'tracker_host' }, { name: 'download_location' }, { name: 'total_done', type: 'int' }, diff --git a/deluge/ui/web/js/deluge-all/add/AddWindow.js b/deluge/ui/web/js/deluge-all/add/AddWindow.js index a4aff067b..f5f2fdf07 100644 --- a/deluge/ui/web/js/deluge-all/add/AddWindow.js +++ b/deluge/ui/web/js/deluge-all/add/AddWindow.js @@ -64,20 +64,6 @@ Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, { this.addButton(_('Cancel'), this.onCancelClick, this); this.addButton(_('Add'), this.onAddClick, this); - function torrentRenderer(value, p, r) { - if (r.data['info_hash']) { - return String.format( - '<div class="x-deluge-add-torrent-name">{0}</div>', - value - ); - } else { - return String.format( - '<div class="x-deluge-add-torrent-name-loading">{0}</div>', - value - ); - } - } - this.list = new Ext.list.ListView({ store: new Ext.data.SimpleStore({ fields: [ @@ -91,8 +77,10 @@ Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, { id: 'torrent', width: 150, sortable: true, - renderer: torrentRenderer, dataIndex: 'text', + tpl: new Ext.XTemplate( + '<div class="x-deluge-add-torrent-name">{text:htmlEncode}</div>' + ), }, ], stripeRows: true, diff --git a/deluge/ui/web/js/deluge-all/add/FilesTab.js b/deluge/ui/web/js/deluge-all/add/FilesTab.js index fed52282d..d712c023d 100644 --- a/deluge/ui/web/js/deluge-all/add/FilesTab.js +++ b/deluge/ui/web/js/deluge-all/add/FilesTab.js @@ -28,6 +28,7 @@ Deluge.add.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, { header: _('Filename'), width: 295, dataIndex: 'filename', + tpl: new Ext.XTemplate('{filename:htmlEncode}'), }, { header: _('Size'), diff --git a/deluge/ui/web/js/deluge-all/add/OptionsPanel.js b/deluge/ui/web/js/deluge-all/add/OptionsPanel.js index 6b75686a3..365b00190 100644 --- a/deluge/ui/web/js/deluge-all/add/OptionsPanel.js +++ b/deluge/ui/web/js/deluge-all/add/OptionsPanel.js @@ -134,9 +134,8 @@ Deluge.add.OptionsPanel = Ext.extend(Ext.TabPanel, { nodes, function (node) { if (node.attributes.fileindex < 0) return; - var priorities = this.form.optionsManager.get( - 'file_priorities' - ); + var priorities = + this.form.optionsManager.get('file_priorities'); priorities[node.attributes.fileindex] = newValue; this.form.optionsManager.update('file_priorities', priorities); }, diff --git a/deluge/ui/web/js/deluge-all/details/DetailsTab.js b/deluge/ui/web/js/deluge-all/details/DetailsTab.js index fdb4f7f0d..f1da178b1 100644 --- a/deluge/ui/web/js/deluge-all/details/DetailsTab.js +++ b/deluge/ui/web/js/deluge-all/details/DetailsTab.js @@ -91,7 +91,9 @@ Deluge.details.DetailsTab = Ext.extend(Ext.Panel, { for (var field in this.fields) { if (!Ext.isDefined(data[field])) continue; // This is a field we are not responsible for. if (data[field] == this.oldData[field]) continue; - this.fields[field].dom.innerHTML = Ext.escapeHTML(data[field]); + this.fields[field].dom.innerHTML = Ext.util.Format.htmlEncode( + data[field] + ); } this.oldData = data; }, diff --git a/deluge/ui/web/js/deluge-all/details/FilesTab.js b/deluge/ui/web/js/deluge-all/details/FilesTab.js index edc388d19..60de832a6 100644 --- a/deluge/ui/web/js/deluge-all/details/FilesTab.js +++ b/deluge/ui/web/js/deluge-all/details/FilesTab.js @@ -18,6 +18,7 @@ Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, { header: _('Filename'), width: 330, dataIndex: 'filename', + tpl: new Ext.XTemplate('{filename:htmlEncode}'), }, { header: _('Size'), diff --git a/deluge/ui/web/js/deluge-all/details/PeersTab.js b/deluge/ui/web/js/deluge-all/details/PeersTab.js index 66d4a4b95..a1919630d 100644 --- a/deluge/ui/web/js/deluge-all/details/PeersTab.js +++ b/deluge/ui/web/js/deluge-all/details/PeersTab.js @@ -73,7 +73,7 @@ header: _('Client'), width: 125, sortable: true, - renderer: fplain, + renderer: 'htmlEncode', dataIndex: 'client', }, { diff --git a/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js b/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js index 4c3720198..8c32da501 100644 --- a/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js +++ b/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js @@ -117,8 +117,7 @@ Deluge.preferences.Bandwidth = Ext.extend(Ext.form.FormPanel, { border: false, title: '', defaultType: 'checkbox', - style: - 'padding-top: 0px; padding-bottom: 5px; margin-top: 0px; margin-bottom: 0px;', + style: 'padding-top: 0px; padding-bottom: 5px; margin-top: 0px; margin-bottom: 0px;', autoHeight: true, }); om.bind( diff --git a/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js b/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js index ed2abdcdc..4cfed016b 100644 --- a/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js +++ b/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js @@ -46,7 +46,6 @@ Deluge.preferences.PreferencesWindow = Ext.extend(Ext.Window, { columns: [ { id: 'name', - renderer: fplain, dataIndex: 'name', }, ], diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js index 31eca735c..ee761aa6f 100644 --- a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js +++ b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js @@ -80,9 +80,8 @@ Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, { // Generate the column configs with the correct width setting for (var i = 0; i < numCols; i++) { var cc = Ext.apply({ items: [] }, colCfg); - cc[ - this.columns[i] <= 1 ? 'columnWidth' : 'width' - ] = this.columns[i]; + cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = + this.columns[i]; if (this.defaults) { cc.defaults = Ext.apply( cc.defaults || {}, diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py index ffdee342c..3f256140e 100644 --- a/deluge/ui/web/json_api.py +++ b/deluge/ui/web/json_api.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009-2010 Damien Churchill <damoxc@gmail.com> # @@ -7,8 +6,7 @@ # See LICENSE for more details. # -from __future__ import division, unicode_literals - +import cgi import json import logging import os @@ -16,7 +14,6 @@ import shutil import tempfile from base64 import b64encode from types import FunctionType -from xml.sax.saxutils import escape as xml_escape from twisted.internet import defer, reactor from twisted.internet.defer import Deferred, DeferredList @@ -39,7 +36,7 @@ log = logging.getLogger(__name__) class JSONComponent(component.Component): def __init__(self, name, interval=1, depend=None): - super(JSONComponent, self).__init__(name, interval, depend) + super().__init__(name, interval, depend) self._json = component.get('JSON') self._json.register_object(self, name) @@ -146,7 +143,7 @@ class JSON(resource.Resource, component.Component): params = request_data['params'] request_id = request_data['id'] except KeyError as ex: - message = 'Invalid JSON request, missing param %s in %s' % ( + message = 'Invalid JSON request, missing param {} in {}'.format( ex, request_data, ) @@ -167,7 +164,7 @@ class JSON(resource.Resource, component.Component): except Exception as ex: log.error('Error calling method `%s`: %s', method, ex) log.exception(ex) - error = {'message': '%s: %s' % (ex.__class__.__name__, str(ex)), 'code': 3} + error = {'message': f'{ex.__class__.__name__}: {str(ex)}', 'code': 3} return request_id, result, error @@ -184,7 +181,7 @@ class JSON(resource.Resource, component.Component): """ log.error(reason) response['error'] = { - 'message': '%s: %s' % (reason.__class__.__name__, str(reason)), + 'message': f'{reason.__class__.__name__}: {str(reason)}', 'code': 4, } return self._send_response(request, response) @@ -194,7 +191,7 @@ class JSON(resource.Resource, component.Component): Handler to take the json data as a string and pass it on to the _handle_request method for further processing. """ - content_type = request.getHeader(b'content-type').decode() + content_type, _ = cgi.parse_header(request.getHeader(b'content-type').decode()) if content_type != 'application/json': message = 'Invalid JSON request content-type: %s' % content_type raise JSONException(message) @@ -221,7 +218,7 @@ class JSON(resource.Resource, component.Component): 'id': None, 'error': { 'code': 5, - 'message': '%s: %s' % (reason.__class__.__name__, str(reason)), + 'message': f'{reason.__class__.__name__}: {str(reason)}', }, } return self._send_response(request, response) @@ -288,7 +285,7 @@ class JSON(resource.Resource, component.Component): FILES_KEYS = ['files', 'file_progress', 'file_priorities'] -class EventQueue(object): +class EventQueue: """ This class subscribes to events from the core and stores them until all the subscribed listeners have received the events. @@ -378,10 +375,8 @@ class WebApi(JSONComponent): methods available from the core RPC. """ - XSS_VULN_KEYS = ['name', 'message', 'comment', 'tracker_status', 'peers'] - def __init__(self): - super(WebApi, self).__init__('Web', depend=['SessionProxy']) + super().__init__('Web', depend=['SessionProxy']) self.hostlist = HostList() self.core_config = CoreConfig() self.event_queue = EventQueue() @@ -518,7 +513,7 @@ class WebApi(JSONComponent): return d def got_stats(stats): - ui_info['stats']['num_connections'] = stats['num_peers'] + ui_info['stats']['num_connections'] = stats['peer.num_peers_connected'] ui_info['stats']['upload_rate'] = stats['payload_upload_rate'] ui_info['stats']['download_rate'] = stats['payload_download_rate'] ui_info['stats']['download_protocol_rate'] = ( @@ -527,9 +522,9 @@ class WebApi(JSONComponent): ui_info['stats']['upload_protocol_rate'] = ( stats['upload_rate'] - stats['payload_upload_rate'] ) - ui_info['stats']['dht_nodes'] = stats['dht_nodes'] + ui_info['stats']['dht_nodes'] = stats['dht.dht_nodes'] ui_info['stats']['has_incoming_connections'] = stats[ - 'has_incoming_connections' + 'net.has_incoming_connections' ] def got_filters(filters): @@ -555,13 +550,13 @@ class WebApi(JSONComponent): d3 = client.core.get_session_status( [ - 'num_peers', + 'peer.num_peers_connected', 'payload_download_rate', 'payload_upload_rate', 'download_rate', 'upload_rate', - 'dht_nodes', - 'has_incoming_connections', + 'dht.dht_nodes', + 'net.has_incoming_connections', ] ) d3.addCallback(got_stats) @@ -584,7 +579,7 @@ class WebApi(JSONComponent): paths = [] info = {} for index, torrent_file in enumerate(files): - path = xml_escape(torrent_file['path']) + path = torrent_file['path'] paths.append(path) torrent_file['progress'] = file_progress[index] torrent_file['priority'] = file_priorities[index] @@ -621,25 +616,10 @@ class WebApi(JSONComponent): file_tree.walk(walk) d.callback(file_tree.get_tree()) - def _on_torrent_status(self, torrent, d): - for key in self.XSS_VULN_KEYS: - try: - if key == 'peers': - for peer in torrent[key]: - peer['client'] = xml_escape(peer['client']) - else: - torrent[key] = xml_escape(torrent[key]) - except KeyError: - pass - d.callback(torrent) - @export def get_torrent_status(self, torrent_id, keys): """Get the status for a torrent, filtered by status keys.""" - main_deferred = Deferred() - d = component.get('SessionProxy').get_torrent_status(torrent_id, keys) - d.addCallback(self._on_torrent_status, main_deferred) - return main_deferred + return component.get('SessionProxy').get_torrent_status(torrent_id, keys) @export def get_torrent_files(self, torrent_id): @@ -1006,7 +986,7 @@ class WebUtils(JSONComponent): """ def __init__(self): - super(WebUtils, self).__init__('WebUtils') + super().__init__('WebUtils') @export def get_languages(self): diff --git a/deluge/ui/web/pluginmanager.py b/deluge/ui/web/pluginmanager.py index 24f20ce94..2da5b6177 100644 --- a/deluge/ui/web/pluginmanager.py +++ b/deluge/ui/web/pluginmanager.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Damien Churchill <damoxc@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import logging import os @@ -74,22 +71,20 @@ class PluginManager(PluginManagerBase, component.Component): scripts = component.get('Scripts') for script in info['scripts']: - scripts.remove_script( - '%s/%s' % (name.lower(), os.path.basename(script).lower()) - ) + scripts.remove_script(f'{name.lower()}/{os.path.basename(script).lower()}') for script in info['debug_scripts']: scripts.remove_script( - '%s/%s' % (name.lower(), os.path.basename(script).lower()), 'debug' + f'{name.lower()}/{os.path.basename(script).lower()}', 'debug' ) scripts.remove_script( - '%s/%s' % (name.lower(), os.path.basename(script).lower()), 'dev' + f'{name.lower()}/{os.path.basename(script).lower()}', 'dev' ) - super(PluginManager, self).disable_plugin(name) + super().disable_plugin(name) def enable_plugin(self, name): - super(PluginManager, self).enable_plugin(name) + super().enable_plugin(name) # Get the plugin instance try: @@ -105,17 +100,15 @@ class PluginManager(PluginManagerBase, component.Component): scripts = component.get('Scripts') for script in info['scripts']: log.debug('adding script %s for %s', name, os.path.basename(script)) - scripts.add_script( - '%s/%s' % (name.lower(), os.path.basename(script)), script - ) + scripts.add_script(f'{name.lower()}/{os.path.basename(script)}', script) for script in info['debug_scripts']: log.debug('adding debug script %s for %s', name, os.path.basename(script)) scripts.add_script( - '%s/%s' % (name.lower(), os.path.basename(script)), script, 'debug' + f'{name.lower()}/{os.path.basename(script)}', script, 'debug' ) scripts.add_script( - '%s/%s' % (name.lower(), os.path.basename(script)), script, 'dev' + f'{name.lower()}/{os.path.basename(script)}', script, 'dev' ) def start(self): @@ -151,11 +144,10 @@ class PluginManager(PluginManagerBase, component.Component): info = gather_info(plugin) info['name'] = name info['scripts'] = [ - 'js/%s/%s' % (name.lower(), os.path.basename(s)) for s in info['scripts'] + f'js/{name.lower()}/{os.path.basename(s)}' for s in info['scripts'] ] info['debug_scripts'] = [ - 'js/%s/%s' % (name.lower(), os.path.basename(s)) - for s in info['debug_scripts'] + f'js/{name.lower()}/{os.path.basename(s)}' for s in info['debug_scripts'] ] del info['script_directories'] return info diff --git a/deluge/ui/web/server.py b/deluge/ui/web/server.py index ea2658071..f391a78d2 100644 --- a/deluge/ui/web/server.py +++ b/deluge/ui/web/server.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009-2010 Damien Churchill <damoxc@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import unicode_literals - import fnmatch import json import logging @@ -23,8 +20,7 @@ from twisted.web.resource import EncodingResourceWrapper from deluge import common, component, configmanager from deluge.common import is_ipv6 -from deluge.core.rpcserver import check_ssl_keys -from deluge.crypto_utils import get_context_factory +from deluge.crypto_utils import check_ssl_keys, get_context_factory from deluge.i18n import set_language, setup_translation from deluge.ui.tracker_icons import TrackerIcons from deluge.ui.web.auth import Auth @@ -376,7 +372,7 @@ class ScriptResource(resource.Resource, component.Component): order_file = os.path.join(root, '.order') if os.path.isfile(order_file): - with open(order_file, 'r') as _file: + with open(order_file) as _file: for line in _file: if line.startswith('+ '): order_filename = line.split()[1] diff --git a/deluge/ui/web/web.py b/deluge/ui/web/web.py index 4d0624791..f855bd06c 100644 --- a/deluge/ui/web/web.py +++ b/deluge/ui/web/web.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Copyright (C) 2009 Damien Churchill <damoxc@gmail.com> # @@ -7,8 +6,6 @@ # See LICENSE for more details. # -from __future__ import print_function, unicode_literals - import logging from twisted.internet.error import CannotListenError @@ -24,7 +21,7 @@ class Web(UI): cmd_description = """Web-based user interface (http://localhost:8112)""" def __init__(self, *args, **kwargs): - super(Web, self).__init__( + super().__init__( 'web', *args, description='Starts the Deluge Web interface', **kwargs ) self.__server = None @@ -67,7 +64,7 @@ class Web(UI): return self.__server def start(self): - super(Web, self).start() + super().start() from deluge.ui.web import server |