Files
pyTorrent/pytorrent/services/websocket.py
Mateusz Gruszczyński dc78f8fd38 fix in queue
2026-05-05 11:07:54 +02:00

108 lines
5.5 KiB
Python

from __future__ import annotations
import threading
import psutil
from flask_socketio import emit
from ..config import POLL_INTERVAL
from .preferences import active_profile, get_profile
from .torrent_cache import torrent_cache
from .torrent_summary import cached_summary
from . import rtorrent, smart_queue, traffic_history, automation_rules, torrent_stats
_started = False
_start_lock = threading.Lock()
def register_socketio_handlers(socketio):
def poller():
tick = 0
while True:
profile = active_profile()
if profile:
diff = torrent_cache.refresh(profile)
heartbeat = {"ok": bool(diff.get("ok")), "profile_id": profile["id"], "tick": tick, "error": diff.get("error", "")}
if diff.get("ok") and (diff["added"] or diff["updated"] or diff["removed"]):
socketio.emit("torrent_patch", {**diff, "summary": cached_summary(profile["id"], torrent_cache.snapshot(profile["id"]), force=True)})
elif not diff.get("ok"):
socketio.emit("rtorrent_error", diff)
try:
status = rtorrent.system_status(profile)
if bool(profile.get("is_remote")):
status["usage_source"] = "remote-hidden"
status["usage_available"] = False
else:
status["cpu"] = psutil.cpu_percent(interval=None)
status["ram"] = psutil.virtual_memory().percent
status["usage_source"] = "local"
status["usage_available"] = True
status["profile_id"] = profile["id"]
traffic_history.record(profile["id"], status.get("down_rate", 0), status.get("up_rate", 0), status.get("total_down", 0), status.get("total_up", 0))
socketio.emit("system_stats", status)
heartbeat["ok"] = True
except Exception as exc:
heartbeat["ok"] = False
heartbeat["error"] = str(exc)
socketio.emit("rtorrent_error", {"profile_id": profile["id"], "error": str(exc)})
if tick % max(1, int(15 * 60 / POLL_INTERVAL)) == 0:
# Note: Queue heavier torrent statistics outside the fast system_stats poller.
torrent_stats.queue_refresh(socketio, profile, force=False)
if tick % max(1, int(30 / POLL_INTERVAL)) == 0:
try:
result = smart_queue.check(profile, force=False)
if result.get("enabled"):
socketio.emit("smart_queue_update", result)
if result.get("paused") or result.get("resumed") or result.get("resume_requested"):
# Note: Po zmianach Smart Queue natychmiast odświeżamy cache, żeby lista Downloading nie czekała na następny cykl pollera.
queue_diff = torrent_cache.refresh(profile)
if queue_diff.get("ok"):
socketio.emit("torrent_patch", {**queue_diff, "summary": cached_summary(profile["id"], torrent_cache.snapshot(profile["id"]), force=True)})
except Exception as exc:
socketio.emit("smart_queue_update", {"ok": False, "error": str(exc)})
try:
auto_result = automation_rules.check(profile, force=False)
if auto_result.get("applied"):
socketio.emit("automation_update", auto_result)
except Exception as exc:
socketio.emit("automation_update", {"ok": False, "error": str(exc)})
socketio.emit("heartbeat", heartbeat)
tick += 1
socketio.sleep(POLL_INTERVAL)
def ensure_poller_started():
global _started
with _start_lock:
if not _started:
# Note: Poller startuje przy starcie aplikacji, więc Smart Queue i automatyzacje działają bez otwartego UI.
socketio.start_background_task(poller)
_started = True
ensure_poller_started()
@socketio.on("connect")
def handle_connect():
ensure_poller_started()
profile = active_profile()
emit("connected", {"ok": True, "profile": profile})
if not profile:
# Note: Fresh installs have no rTorrent yet; tell the client to show setup instead of waiting for a snapshot.
emit("profile_required", {"ok": True, "profiles": []})
return
rows = torrent_cache.snapshot(profile["id"])
emit("torrent_snapshot", {"profile_id": profile["id"], "torrents": rows, "summary": cached_summary(profile["id"], rows)})
@socketio.on("select_profile")
def handle_select_profile(data):
profile_id = int((data or {}).get("profile_id") or 0)
if not profile_id:
# Note: Ignore empty profile selections created before the first rTorrent profile exists.
emit("profile_required", {"ok": True, "profiles": []})
return
profile = get_profile(profile_id)
if not profile:
emit("rtorrent_error", {"error": "Profile does not exist"})
return
diff = torrent_cache.refresh(profile)
rows = torrent_cache.snapshot(profile_id)
emit("torrent_snapshot", {"profile_id": profile_id, "torrents": rows, "summary": cached_summary(profile_id, rows, force=True), "error": diff.get("error", "")})