diff options
-rw-r--r-- | deluge/core/alertmanager.py | 59 | ||||
-rw-r--r-- | deluge/core/torrentmanager.py | 4 | ||||
-rw-r--r-- | deluge/tests/test_common.py | 21 | ||||
-rw-r--r-- | deluge/ui/gtk3/menubar.py | 44 | ||||
-rw-r--r-- | deluge/ui/gtk3/preferences.py | 1 | ||||
-rw-r--r-- | deluge/ui/web/js/deluge-all/TorrentGrid.js | 2 | ||||
-rw-r--r-- | deluge/ui/web/json_api.py | 5 | ||||
-rw-r--r-- | docs/requirements.txt | 10 |
8 files changed, 66 insertions, 80 deletions
diff --git a/deluge/core/alertmanager.py b/deluge/core/alertmanager.py index 71045b0..cf541f0 100644 --- a/deluge/core/alertmanager.py +++ b/deluge/core/alertmanager.py @@ -17,10 +17,12 @@ This should typically only be used by the Core. Plugins should utilize the import contextlib import logging import threading +import time from collections import defaultdict +from functools import partial from typing import Any, Callable -from twisted.internet import reactor, threads +from twisted.internet import reactor, task, threads import deluge.component as component from deluge._libtorrent import lt @@ -56,8 +58,7 @@ class AlertManager(component.Component): # handlers is a dictionary of lists {"alert_type": [handler1,h2,..]} self.handlers = defaultdict(list) - self.handlers_retry_timeout = 0.3 - self.handlers_retry_count = 6 + self.handlers_timeout_secs = 2 self.delayed_calls = [] self._event = threading.Event() @@ -82,45 +83,33 @@ class AlertManager(component.Component): def wait_for_alert_in_thread(self): while self._component_state not in ('Stopping', 'Stopped'): + if self.check_delayed_calls(): + time.sleep(0.05) + continue + if self.session.wait_for_alert(1000) is None: continue if self._event.wait(): threads.blockingCallFromThread(reactor, self.maybe_handle_alerts) + def on_delayed_call_timeout(self, result, timeout, **kwargs): + log.warning('Alert handler was timed-out before being called %s', kwargs) + def cancel_delayed_calls(self): """Cancel all delayed handlers.""" for delayed_call in self.delayed_calls: - if delayed_call.active(): - delayed_call.cancel() + delayed_call.cancel() self.delayed_calls = [] - def check_delayed_calls(self, retries: int = 0) -> bool: - """Returns True if any handler calls are delayed (upto retry limit).""" - self.delayed_calls = [dc for dc in self.delayed_calls if dc.active()] - if not self.delayed_calls: - return False - - if retries > self.handlers_retry_count: - log.warning( - 'Alert handlers timeout reached, cancelling: %s', self.delayed_calls - ) - self.cancel_delayed_calls() - return False + def check_delayed_calls(self) -> bool: + """Returns True if any handler calls are delayed.""" + self.delayed_calls = [dc for dc in self.delayed_calls if not dc.called] + return len(self.delayed_calls) > 0 - return True - - def maybe_handle_alerts(self, retries: int = 0) -> None: + def maybe_handle_alerts(self) -> None: if self._component_state != 'Started': return - if self.check_delayed_calls(retries): - log.debug('Waiting for delayed alerts: %s', self.delayed_calls) - retries += 1 - reactor.callLater( - self.handlers_retry_timeout, self.maybe_handle_alerts, retries - ) - return - self.handle_alerts() def register_handler(self, alert_type: str, handler: Callable[[Any], None]) -> None: @@ -182,8 +171,18 @@ class AlertManager(component.Component): for handler in self.handlers[alert_type]: if log.isEnabledFor(logging.DEBUG): log.debug('Handling alert: %s', alert_type) - - self.delayed_calls.append(reactor.callLater(0, handler, alert)) + d = task.deferLater(reactor, 0, handler, alert) + on_handler_timeout = partial( + self.on_delayed_call_timeout, + handler=handler.__qualname__, + alert_type=alert_type, + ) + d.addTimeout( + self.handlers_timeout_secs, + reactor, + onTimeoutCancel=on_handler_timeout, + ) + self.delayed_calls.append(d) def set_alert_queue_size(self, queue_size): """Sets the maximum size of the libtorrent alert queue""" diff --git a/deluge/core/torrentmanager.py b/deluge/core/torrentmanager.py index a758d5c..c43a7a2 100644 --- a/deluge/core/torrentmanager.py +++ b/deluge/core/torrentmanager.py @@ -290,8 +290,8 @@ class TorrentManager(component.Component): if torrent.options['remove_at_ratio']: self.remove(torrent_id) break - if not torrent.status.paused: - torrent.pause() + + torrent.pause() def __getitem__(self, torrent_id): """Return the Torrent with torrent_id. diff --git a/deluge/tests/test_common.py b/deluge/tests/test_common.py index 780d368..a1af6cc 100644 --- a/deluge/tests/test_common.py +++ b/deluge/tests/test_common.py @@ -30,6 +30,7 @@ from deluge.common import ( is_ipv6, is_magnet, is_url, + parse_human_size, windows_check, ) @@ -150,26 +151,26 @@ class TestCommon: assert VersionSplit('1.4.0.dev1') < VersionSplit('1.4.0') assert VersionSplit('1.4.0a1') < VersionSplit('1.4.0') - def test_parse_human_size(self): - from deluge.common import parse_human_size - - sizes = [ + @pytest.mark.parametrize( + ('human_size', 'expected'), + [ ('1', 1), ('10 bytes', 10), ('2048 bytes', 2048), ('1MiB', 2 ** (10 * 2)), ('1 MiB', 2 ** (10 * 2)), ('1 GiB', 2 ** (10 * 3)), - ('1 GiB', 2 ** (10 * 3)), + ('1 TiB', 2 ** (10 * 4)), ('1M', 10**6), + ('1p', 10**15), ('1MB', 10**6), ('1 GB', 10**9), ('1 TB', 10**12), - ] - - for human_size, byte_size in sizes: - parsed = parse_human_size(human_size) - assert parsed == byte_size, 'Mismatch when converting: %s' % human_size + ], + ) + def test_parse_human_size(self, human_size, expected): + parsed = parse_human_size(human_size) + assert parsed == expected, 'Mismatch when converting: %s' % human_size def test_archive_files(self): arc_filelist = [ diff --git a/deluge/ui/gtk3/menubar.py b/deluge/ui/gtk3/menubar.py index 328cb7e..9165320 100644 --- a/deluge/ui/gtk3/menubar.py +++ b/deluge/ui/gtk3/menubar.py @@ -578,42 +578,26 @@ class MenuBar(component.Component): component.get('FilterTreeView').update() def _on_known_accounts(self, known_accounts): - known_accounts_to_log = [] - for account in known_accounts: - account_to_log = {} - for key, value in account.copy().items(): - if key == 'password': - 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) + menuitem_change_owner = self.builder.get_object('menuitem_change_owner') if len(known_accounts) <= 1: + menuitem_change_owner.set_visible(False) return - self.builder.get_object('menuitem_change_owner').set_visible(True) - - self.change_owner_submenu = Gtk.Menu() - self.change_owner_submenu_items = {} - maingroup = Gtk.RadioMenuItem() - - self.change_owner_submenu_items[None] = Gtk.RadioMenuItem(maingroup) + self.users_menu = Gtk.Menu() + self.users_menu_items = {} + menu_group = None for account in known_accounts: username = account['username'] - item = Gtk.RadioMenuItem.new_with_label(maingroup, username) - self.change_owner_submenu_items[username] = item - self.change_owner_submenu.append(item) + item = Gtk.RadioMenuItem.new_with_label(menu_group, username) + menu_group = item.get_group() item.connect('toggled', self._on_change_owner_toggled, username) + self.users_menu_items[username] = item + self.users_menu.append(item) - self.change_owner_submenu.show_all() - self.change_owner_submenu_items[None].set_active(True) - self.change_owner_submenu_items[None].hide() - self.builder.get_object('menuitem_change_owner').connect( - 'activate', self._on_change_owner_submenu_active - ) - self.builder.get_object('menuitem_change_owner').set_submenu( - self.change_owner_submenu - ) + self.users_menu.show_all() + menuitem_change_owner.set_submenu(self.users_menu) + menuitem_change_owner.set_visible(True) def _on_known_accounts_fail(self, reason): self.builder.get_object('menuitem_change_owner').set_visible(False) @@ -622,13 +606,13 @@ class MenuBar(component.Component): log.debug('_on_change_owner_submenu_active') selected = component.get('TorrentView').get_selected_torrents() if len(selected) > 1: - self.change_owner_submenu_items[None].set_active(True) + self.users_menu_items[None].set_active(True) return torrent_owner = component.get('TorrentView').get_torrent_status(selected[0])[ 'owner' ] - for username, item in self.change_owner_submenu_items.items(): + for username, item in self.users_menu_items.items(): item.set_active(username == torrent_owner) def _on_change_owner_toggled(self, widget, username): diff --git a/deluge/ui/gtk3/preferences.py b/deluge/ui/gtk3/preferences.py index 3463b70..a024a59 100644 --- a/deluge/ui/gtk3/preferences.py +++ b/deluge/ui/gtk3/preferences.py @@ -117,7 +117,6 @@ class Preferences(component.Component): # Setup accounts tab lisview self.accounts_levels_mapping = None - self.accounts_authlevel = self.builder.get_object('accounts_authlevel') self.accounts_liststore = Gtk.ListStore(str, str, str, int) self.accounts_liststore.set_sort_column_id( ACCOUNTS_USERNAME, Gtk.SortType.ASCENDING diff --git a/deluge/ui/web/js/deluge-all/TorrentGrid.js b/deluge/ui/web/js/deluge-all/TorrentGrid.js index 333d133..5db7e9f 100644 --- a/deluge/ui/web/js/deluge-all/TorrentGrid.js +++ b/deluge/ui/web/js/deluge-all/TorrentGrid.js @@ -61,7 +61,7 @@ return String.format( '<div style="background: url(' + deluge.config.base + - 'tracker/{0}) no-repeat; padding-left: 20px;">{0}</div>', + 'tracker/{0}) no-repeat; background-size: contain; padding-left: 20px;">{0}</div>', Ext.util.Format.htmlEncode(value) ); } diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py index 3f25614..ea8105d 100644 --- a/deluge/ui/web/json_api.py +++ b/deluge/ui/web/json_api.py @@ -600,7 +600,10 @@ class WebApi(JSONComponent): progresses = dirinfo.setdefault('progresses', []) progresses.append(torrent_file['size'] * torrent_file['progress'] / 100) - dirinfo['progress'] = sum(progresses) / dirinfo['size'] * 100 + if dirinfo['size'] > 0: + dirinfo['progress'] = sum(progresses) / dirinfo['size'] * 100 + else: + dirinfo['progress'] = 100 dirinfo['path'] = dirname dirname = os.path.dirname(dirname) diff --git a/docs/requirements.txt b/docs/requirements.txt index de42439..3da1967 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,5 @@ -sphinx==4.* -myst-parser -sphinx_rtd_theme==1.0.* -sphinxcontrib-spelling==7.3.0 -sphinx-autodoc-typehints +sphinx==7.2.* +myst-parser==2.0.* +sphinx_rtd_theme==2.0.* +sphinxcontrib-spelling==8.0.* +sphinx-autodoc-typehints==1.25.* |