fix queue

This commit is contained in:
Mateusz Gruszczyński
2026-05-05 17:48:21 +02:00
parent fc5fedbde2
commit eedfce7207

View File

@@ -274,11 +274,25 @@ def _read_live_start_state(client: Any, torrent_hash: str) -> dict[str, Any]:
result[key] = int(value or 0) if key in {'state', 'active', 'open', 'priority'} else str(value or '') result[key] = int(value or 0) if key in {'state', 'active', 'open', 'priority'} else str(value or '')
except Exception as exc: except Exception as exc:
result[f'{key}_error'] = str(exc) result[f'{key}_error'] = str(exc)
# Note: Nie uznajemy d.is_open ani state=1 za wznowienie; Paused też potrafi mieć te wartości. # Note: Realny slot liczymy po d.is_active=1. Dodatkowo zwracamy state/open/priority,
# Smart Queue zalicza start dopiero po d.is_active=1, czyli po realnym zdjęciu pauzy. # bo przy masowym resume rTorrent czasem przyjmuje start, ale aktywuje transfer dopiero w kolejnym ticku.
result['started'] = bool(int(result.get('active') or 0)) result['started'] = bool(int(result.get('active') or 0))
result['start_accepted'] = bool(int(result.get('state') or 0) or int(result.get('open') or 0))
return result return result
def _refresh_active_slots(profile: dict, excluded: set[str], manage_stopped: bool) -> tuple[int, list[dict[str, Any]]]:
"""Read a fresh torrent snapshot and count real active Smart Queue slots."""
fresh = rtorrent.list_torrents(profile)
active = [
t for t in fresh
if str(t.get('hash') or '') not in excluded
and _is_running_download_slot(t)
]
# Note: Po batchowym resume nie ufamy staremu snapshotowi; odświeżenie z rTorrent
# pozwala dobić kolejkę także wtedy, gdy aktywacja nastąpiła z opóźnieniem.
return len(active), fresh
def _set_smart_queue_label(client: Any, torrent_hash: str, attempts: int = 3) -> bool: def _set_smart_queue_label(client: Any, torrent_hash: str, attempts: int = 3) -> bool:
for attempt in range(max(1, attempts)): for attempt in range(max(1, attempts)):
try: try:
@@ -508,18 +522,23 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool =
candidate_queue = [t for t in candidates if str(t.get('hash') or '') and str(t.get('hash') or '') not in pause_hashes] candidate_queue = [t for t in candidates if str(t.get('hash') or '') and str(t.get('hash') or '') not in pause_hashes]
active_slots = active_after_pause active_slots = active_after_pause
max_resume_attempts = max(len(candidate_queue), max_active * 3)
# Note: Resume dziala teraz w petli do pelnego limitu z ustawien. Gdy batch nie przejdzie # Note: Resume działa w rundach aż do pełnego limitu z ustawień. Po każdej rundzie
# na d.is_active=1, Smart Queue nie zatrzymuje sie, tylko probuje nastepnych kandydatow. # pobieramy świeży snapshot z rTorrent, bo masowe d.resume/d.start nie zawsze widać
while candidate_queue and active_slots < max_active: # natychmiast w d.is_active na pojedynczym RPC.
while candidate_queue and active_slots < max_active and len(attempted_hashes) < max_resume_attempts:
slots_left = max_active - active_slots slots_left = max_active - active_slots
batch = candidate_queue[:slots_left] # Note: Bierzemy mały nadmiar kandydatów tylko wtedy, gdy poprzednie resume nie zwiększyło
candidate_queue = candidate_queue[slots_left:] # liczby aktywnych slotów; to naprawia przypadek, gdy część pauzowanych nie wstaje po komendzie.
batch_size = min(len(candidate_queue), max(1, slots_left))
batch = candidate_queue[:batch_size]
candidate_queue = candidate_queue[batch_size:]
batch_requested: list[str] = [] batch_requested: list[str] = []
for t in batch: for t in batch:
h = str(t.get('hash') or '') h = str(t.get('hash') or '')
if not h: if not h or h in attempted_hashes:
continue continue
attempted_hashes.add(h) attempted_hashes.add(h)
try: try:
@@ -529,6 +548,10 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool =
batch_requested.append(h) batch_requested.append(h)
except Exception as exc: except Exception as exc:
start_failed.append({'hash': h, 'error': str(exc)}) start_failed.append({'hash': h, 'error': str(exc)})
time.sleep(0.03)
if not batch_requested:
continue
active_verified, batch_no_effect = _verify_started_downloads(c, batch_requested) active_verified, batch_no_effect = _verify_started_downloads(c, batch_requested)
start_no_effect.extend(batch_no_effect) start_no_effect.extend(batch_no_effect)
@@ -536,10 +559,24 @@ def check(profile: dict | None = None, user_id: int | None = None, force: bool =
if h not in resumed: if h not in resumed:
_restore_auto_label(c, profile_id, h, None) _restore_auto_label(c, profile_id, h, None)
resumed.append(h) resumed.append(h)
active_slots += len([h for h in active_verified if h])
if not batch_requested: fresh_active_slots, fresh_torrents = _refresh_active_slots(profile, excluded, manage_stopped)
break active_slots = max(active_slots, fresh_active_slots)
# Note: Jeżeli rTorrent wznowił torrent dopiero po odświeżeniu listy, dopisujemy go
# do resumed i zdejmujemy techniczny label Smart Queue.
fresh_by_hash = {str(t.get('hash') or ''): t for t in fresh_torrents}
for h in batch_requested:
live_t = fresh_by_hash.get(h)
if live_t and _is_running_download_slot(live_t) and h not in resumed:
_restore_auto_label(c, profile_id, h, None)
resumed.append(h)
if active_slots < max_active and not candidate_queue:
# Note: Ostatnia próba dla pozycji, które przyjęły start, ale jeszcze nie pokazały active=1.
time.sleep(0.75)
fresh_active_slots, fresh_torrents = _refresh_active_slots(profile, excluded, manage_stopped)
active_slots = max(active_slots, fresh_active_slots)
resumed_set = set(resumed) resumed_set = set(resumed)
waiting_hashes = { waiting_hashes = {