fix queue

This commit is contained in:
Mateusz Gruszczyński
2026-05-05 19:19:47 +02:00
parent 45cb6cbb3a
commit aea3c92830
3 changed files with 161 additions and 213 deletions

View File

@@ -1167,61 +1167,123 @@ def apply_startup_overrides(profile: dict) -> dict:
return set_config(profile, values, apply_now=True, apply_on_start=True)
def start_or_resume_hash(c: ScgiRtorrentClient, torrent_hash: str) -> dict:
"""Start stopped torrents and resume torrents paused with d.pause."""
def _int_rpc(c: ScgiRtorrentClient, method: str, h: str, default: int = 0) -> int:
try:
return int(c.call(method, h) or 0)
except Exception:
return default
def _str_rpc(c: ScgiRtorrentClient, method: str, h: str, default: str = '') -> str:
try:
return str(c.call(method, h) or '')
except Exception:
return default
def _download_runtime_state(c: ScgiRtorrentClient, h: str) -> dict:
"""Read rTorrent state using the native pause model: stopped, paused or active."""
state = _int_rpc(c, 'd.state', h)
active = _int_rpc(c, 'd.is_active', h)
opened = _int_rpc(c, 'd.is_open', h)
# Note: W rTorrent pauza nie zmienia d.state. Paused to state=1, open=1, active=0.
return {
'state': state,
'open': opened,
'active': active,
'paused': bool(state and opened and not active),
'stopped': not bool(state),
'message': _str_rpc(c, 'd.message', h),
}
def pause_hash(c: ScgiRtorrentClient, torrent_hash: str) -> dict:
"""Pause an active rTorrent item without stopping or closing it."""
h = str(torrent_hash or '')
if not h:
return {'hash': h, 'ok': False, 'error': 'missing hash'}
result: dict = {'hash': h, 'commands': []}
before = _download_runtime_state(c, h)
result = {'hash': h, 'before': before, 'commands': []}
try:
result['state_before'] = int(c.call('d.state', h) or 0)
# Note: Smart Queue zatrzymuje slot przez d.pause, nie przez d.stop, żeby późniejsze d.resume działało jak w ruTorrent.
c.call('d.pause', h)
result['commands'].append('d.pause')
result['after'] = _download_runtime_state(c, h)
result['ok'] = True
except Exception as exc:
result['state_before_error'] = str(exc)
result['state_before'] = 0
result.update({'ok': False, 'error': str(exc), 'after': _download_runtime_state(c, h)})
return result
# Note: Ręczne Start i Smart Queue muszą zdejmować pause przez d.resume; samo d.start
# nie rusza torrentów zatrzymanych wcześniej komendą d.pause.
for method in ('d.resume',):
try:
c.call(method, h)
result['commands'].append(method)
except Exception as exc:
result.setdefault('ignored_errors', []).append(f'{method}: {exc}')
# Note: d.open bywa potrzebne po całkowitym stop/close; dla już otwartych torrentów jest bezpiecznie ignorowane.
def resume_paused_hash(c: ScgiRtorrentClient, torrent_hash: str) -> dict:
"""Resume only a paused rTorrent item; never convert it through stop/start."""
h = str(torrent_hash or '')
if not h:
return {'hash': h, 'ok': False, 'error': 'missing hash'}
before = _download_runtime_state(c, h)
result: dict = {'hash': h, 'before': before, 'commands': []}
if before.get('stopped'):
result.update({'ok': False, 'skipped': 'stopped_not_paused', 'after': before})
return result
if before.get('active'):
result.update({'ok': True, 'skipped': 'already_active', 'after': before})
return result
try:
# Note: ruTorrent dla od-pauzowania wysyła odpowiednik unpause/d.resume. Nie dokładamy d.start/d.open,
# bo to są komendy dla stanu Stopped/Open, a nie dla czystego Paused.
c.call('d.resume', h)
result['commands'].append('d.resume')
result['after'] = _download_runtime_state(c, h)
result['ok'] = True
except Exception as exc:
result.update({'ok': False, 'error': str(exc), 'after': _download_runtime_state(c, h)})
return result
def start_or_resume_hash(c: ScgiRtorrentClient, torrent_hash: str) -> dict:
"""Start stopped torrents or resume torrents paused with d.pause, without mixing both paths."""
h = str(torrent_hash or '')
if not h:
return {'hash': h, 'ok': False, 'error': 'missing hash'}
before = _download_runtime_state(c, h)
result: dict = {'hash': h, 'before': before, 'commands': []}
if before.get('active'):
result.update({'ok': True, 'skipped': 'already_active', 'after': before})
return result
if before.get('paused') or (before.get('state') and not before.get('active')):
# Note: Paused w rTorrent wznawiamy tylko przez d.resume; d.start jest tu celowo pomijane.
resumed = resume_paused_hash(c, h)
resumed['mode'] = 'resume_paused'
return resumed
try:
# Note: d.start zostaje wyłącznie dla Stopped/closed, czyli dla stanu innego niż pause->resume.
c.call('d.open', h)
result['commands'].append('d.open')
except Exception as exc:
result.setdefault('ignored_errors', []).append(f'd.open: {exc}')
for method in ('d.start', 'd.resume', 'd.try_start'):
try:
c.call('d.start', h)
result['commands'].append('d.start')
except Exception as exc:
result.setdefault('ignored_errors', []).append(f'd.start: {exc}')
try:
c.call(method, h)
result['commands'].append(method)
except Exception as exc:
result.setdefault('ignored_errors', []).append(f'{method}: {exc}')
try:
result['state_after'] = int(c.call('d.state', h) or 0)
except Exception as exc:
result['state_after_error'] = str(exc)
try:
result['active_after'] = int(c.call('d.is_active', h) or 0)
except Exception as exc:
result['active_after_error'] = str(exc)
result['ok'] = True
c.call('d.try_start', h)
result['commands'].append('d.try_start')
except Exception as exc2:
result.setdefault('ignored_errors', []).append(f'd.try_start: {exc2}')
result['ok'] = False
result['after'] = _download_runtime_state(c, h)
result['ok'] = result.get('ok', True)
return result
def action(profile: dict, torrent_hashes: list[str], name: str, payload: dict | None = None) -> dict:
payload = payload or {}
c = client_for(profile)
methods = {
"start": "d.start",
"pause": "d.pause",
"stop": "d.stop",
"resume": "d.resume",
"recheck": "d.check_hash",
"reannounce": "d.tracker_announce",
"remove": "d.erase",
@@ -1289,8 +1351,16 @@ def action(profile: dict, torrent_hashes: list[str], name: str, payload: dict |
c.call("d.directory.set", h, path)
results.append(item)
return {"ok": True, "count": len(torrent_hashes), "move_data": move_data, "results": results}
if name in {"start", "resume"}:
# Note: Start działa teraz także dla pozycji Paused, bo wykonuje pełną sekwencję resume/open/start.
if name == "pause":
# Note: Pauza aplikacji jest teraz czystym d.pause, żeby późniejszy resume działał bez stop/start.
results = [pause_hash(c, h) for h in torrent_hashes]
return {"ok": True, "count": len(torrent_hashes), "remove_data": False, "results": results}
if name in {"resume", "unpause"}:
# Note: Resume/Unpause używa wyłącznie d.resume dla stanu Paused.
results = [resume_paused_hash(c, h) for h in torrent_hashes]
return {"ok": True, "count": len(torrent_hashes), "remove_data": False, "results": results}
if name == "start":
# Note: Start rozdziela Stopped od Paused; paused idzie przez d.resume, stopped przez d.start.
results = [start_or_resume_hash(c, h) for h in torrent_hashes]
return {"ok": True, "count": len(torrent_hashes), "remove_data": False, "results": results}