diff --git a/pytorrent/services/smart_queue.py b/pytorrent/services/smart_queue.py index 3d11bab..da609cd 100644 --- a/pytorrent/services/smart_queue.py +++ b/pytorrent/services/smart_queue.py @@ -369,15 +369,15 @@ def _cleanup_auto_labels(client: Any, profile_id: int, torrents: list[dict[str, def _is_running_download_slot(t: dict[str, Any]) -> bool: """Return True for incomplete torrents that already occupy a Smart Queue slot.""" - # Note: Limit Smart Queue oznacza docelową liczbę slotów włączonych do pobierania. - # rTorrent często pokazuje d.is_active=0 dla torrentów bez chwilowego transferu, więc slot liczymy - # po d.state=1. Techniczny label Smart Queue jest wyjątkiem: oznacza pozycję oczekującą/pauzowaną, - # której nie wolno liczyć jako aktywnej. Dzięki temu przy limicie 100 i 82 slotach dobieramy 18. + # Note: Limit Smart Queue oznacza docelową liczbę realnie uruchomionych slotów. + # rTorrent potrafi trzymać paused jako state=1, dlatego slot liczymy po state=1 tylko wtedy, + # gdy torrent nie ma statusu Paused i nie jest oznaczony technicznym labelem Smart Queue. if int(t.get('complete') or 0): return False if str(t.get('label') or '') == SMART_QUEUE_LABEL: return False - if str(t.get('status') or '').lower() == 'checking': + status = str(t.get('status') or '').lower() + if status == 'checking' or status == 'paused' or bool(t.get('paused')): return False return bool(int(t.get('state') or 0)) @@ -484,9 +484,9 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool = to_pause: list[dict[str, Any]] = pause_rank[:max(0, len(downloading) - max_active)] pause_hashes = {str(t.get('hash') or '') for t in to_pause} - # When the cap is not exceeded, stalled downloads can still be rotated out - # one-for-one with better stopped candidates while staying within max_active. - if candidates: + # Note: Rotacja stalled działa tylko przy pełnej kolejce. Gdy brakuje slotów, Smart Queue ma + # najpierw dobrać brakujące pozycje, a nie pauzować już istniejące lub błędnie uznane za stalled. + if candidates and len(downloading) >= max_active: replaceable_stalled = [t for t in stalled if str(t.get('hash') or '') not in pause_hashes] for t in replaceable_stalled[:max(0, len(candidates) - len(to_pause))]: to_pause.append(t) @@ -547,6 +547,6 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool = | {str(t.get('hash') or '') for t in stopped if str(t.get('label') or '') == SMART_QUEUE_LABEL and str(t.get('hash') or '') not in set(resumed)} ) restored = _cleanup_auto_labels(c, profile_id, torrents, keep_labels, manage_stopped) - 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, 'active_verified': active_verified, 'waiting_labeled': len(to_label_waiting), 'manage_stopped': manage_stopped, 'max_active_downloads': max_active, 'active_before': len(downloading), 'active_after': active_after_pause + len(resumed), 'rtorrent_cap': rtorrent_cap} + 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, 'active_verified': active_verified, 'waiting_labeled': len(to_label_waiting), 'manage_stopped': manage_stopped, 'max_active_downloads': max_active, 'active_before': len(downloading), 'active_after_expected': active_after_pause + len(resumed), 'paused_planned': len(to_pause), 'resumed_planned': len(to_resume), '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, 'waiting_labeled': len(to_label_waiting), 'labels_restored': restored, 'labels_failed': label_failed, 'start_failed': start_failed, 'start_no_effect': start_no_effect, 'active_verified': active_verified, 'rtorrent_cap': rtorrent_cap, 'checked': len(torrents), 'excluded': len(excluded), 'settings': settings}