queue changes

This commit is contained in:
Mateusz Gruszczyński
2026-05-05 14:03:31 +02:00
parent dc78f8fd38
commit 2d19481c4c
3 changed files with 76 additions and 19 deletions

View File

@@ -179,6 +179,62 @@ def _restore_auto_label(client: Any, profile_id: int, torrent_hash: str, current
def _ensure_rtorrent_download_cap(client: Any, max_active: int) -> dict[str, Any]:
"""Raise rTorrent's own download cap when it is lower than Smart Queue's target."""
result: dict[str, Any] = {'checked': False, 'updated': False}
try:
current = int(client.call('throttle.max_downloads.global') or 0)
result.update({'checked': True, 'current': current, 'target': max_active})
# Note: 0 means unlimited in rTorrent, so only smaller positive caps are raised.
if 0 < current < max_active:
try:
client.call('throttle.max_downloads.global.set', '', int(max_active))
except Exception:
client.call('throttle.max_downloads.global.set', int(max_active))
result.update({'updated': True, 'new': int(max_active)})
except Exception as exc:
# Note: Missing/older rTorrent throttle RPC should not block queue processing.
result.update({'error': str(exc)})
return result
def _start_download(client: Any, torrent: dict[str, Any]) -> None:
"""Resume paused torrents and start stopped torrents using the smallest safe RPC sequence."""
h = str(torrent.get('hash') or '')
if not h:
return
# Note: Paused wymaga resume+start; stopped startujemy bez resume, gdy ustawienie na to pozwala.
if bool(torrent.get('paused')):
client.call('d.resume', h)
client.call('d.start', h)
def _verify_started_downloads(client: Any, hashes: list[str], attempts: int = 3, delay: float = 0.25) -> tuple[list[str], list[dict[str, Any]]]:
"""Verify starts after a short scheduler delay instead of immediately after each RPC."""
pending = [h for h in hashes if h]
started: list[str] = []
no_effect: list[dict[str, Any]] = []
seen_started: set[str] = set()
last_state: dict[str, dict[str, Any]] = {}
for attempt in range(max(1, attempts)):
if attempt:
time.sleep(delay)
for h in list(pending):
live = _read_live_start_state(client, h)
last_state[h] = live
if live.get('started'):
seen_started.add(h)
pending.remove(h)
if not pending:
break
started = [h for h in hashes if h in seen_started]
no_effect = [last_state.get(h, {'hash': h, 'started': False}) for h in hashes if h and h not in seen_started]
return started, no_effect
def _read_live_start_state(client: Any, torrent_hash: str) -> dict[str, Any]:
result: dict[str, Any] = {'hash': torrent_hash}
for key, method in (('state', 'd.state'), ('active', 'd.is_active'), ('message', 'd.message')):
@@ -352,12 +408,14 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool =
to_resume = candidates[:available_slots]
c = rtorrent.client_for(profile)
rtorrent_cap = _ensure_rtorrent_download_cap(c, max_active)
paused: list[str] = []
resumed: list[str] = []
label_failed: list[str] = []
start_failed: list[dict[str, str]] = []
start_no_effect: list[dict[str, Any]] = []
resume_requested: list[str] = []
for t in to_pause:
try:
c.call('d.pause', t['hash'])
@@ -366,25 +424,24 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool =
paused.append(t['hash'])
except Exception:
pass
# Note: Startujemy całą pulę kandydatów w jednej rundzie, a dopiero potem weryfikujemy efekt.
for t in to_resume:
h = t['hash']
h = str(t.get('hash') or '')
if not h:
continue
try:
# Note: Paused wymaga resume+start, a stopped startujemy bez wcześniejszego resume tylko wtedy, gdy switch na to pozwala.
_restore_auto_label(c, profile_id, h, str(t.get('label') or ''))
if bool(t.get('paused')):
c.call('d.resume', h)
c.call('d.start', h)
time.sleep(0.05)
live = _read_live_start_state(c, h)
if live.get('started'):
_restore_auto_label(c, profile_id, h, None)
resumed.append(h)
else:
# Note: Nie liczymy torrenta jako realnie wznowionego, dopóki rTorrent nie potwierdzi state=1 i active=1.
start_no_effect.append(live)
resume_requested.append(h)
_start_download(c, t)
resume_requested.append(h)
except Exception as exc:
start_failed.append({'hash': h, 'error': str(exc)})
verified, start_no_effect = _verify_started_downloads(c, resume_requested)
for h in verified:
_restore_auto_label(c, profile_id, h, None)
resumed = verified
restored = _cleanup_auto_labels(c, profile_id, torrents, set(paused), manage_stopped)
add_history(profile_id, 'force_check' if force else 'auto_check', paused, resumed, len(torrents), {'excluded': len(excluded), 'enabled': bool(settings.get('enabled')), 'auto_label': SMART_QUEUE_LABEL, 'labels_restored': restored, 'labels_failed': label_failed, 'start_failed': start_failed, 'start_no_effect': start_no_effect, 'resume_requested': resume_requested, 'manage_stopped': manage_stopped, 'max_active_downloads': max_active, 'active_before': len(downloading), 'active_after': active_after_pause + len(resumed)}, user_id)
return {'ok': True, 'enabled': bool(settings.get('enabled')), 'paused': paused, 'resumed': resumed, 'resume_requested': resume_requested, 'labels_restored': restored, 'labels_failed': label_failed, 'start_failed': start_failed, 'start_no_effect': start_no_effect, 'checked': len(torrents), 'excluded': len(excluded), 'settings': settings}
details = {'excluded': len(excluded), 'enabled': bool(settings.get('enabled')), 'auto_label': SMART_QUEUE_LABEL, 'labels_restored': restored, 'labels_failed': label_failed, 'start_failed': start_failed, 'start_no_effect': start_no_effect, 'resume_requested': resume_requested, 'manage_stopped': manage_stopped, 'max_active_downloads': max_active, 'active_before': len(downloading), 'active_after': active_after_pause + len(resumed), 'rtorrent_cap': rtorrent_cap}
add_history(profile_id, 'force_check' if force else 'auto_check', paused, resumed, len(torrents), details, user_id)
return {'ok': True, 'enabled': bool(settings.get('enabled')), 'paused': paused, 'resumed': resumed, 'resume_requested': resume_requested, 'labels_restored': restored, 'labels_failed': label_failed, 'start_failed': start_failed, 'start_no_effect': start_no_effect, 'rtorrent_cap': rtorrent_cap, 'checked': len(torrents), 'excluded': len(excluded), 'settings': settings}