multilang_and_other #8
@@ -11,10 +11,11 @@ from ..config import POLL_INTERVAL, MIN_POLL_INTERVAL_SECONDS
|
|||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
"adaptive_enabled": True,
|
"adaptive_enabled": True,
|
||||||
"safe_fallback_enabled": True,
|
"safe_fallback_enabled": True,
|
||||||
"active_interval_seconds": 5.0,
|
"active_interval_seconds": 3.0,
|
||||||
"idle_interval_seconds": 15.0,
|
"idle_interval_seconds": 15.0,
|
||||||
"error_interval_seconds": 30.0,
|
"error_interval_seconds": 30.0,
|
||||||
"torrent_list_interval_seconds": 5.0,
|
"live_stats_interval_seconds": 3.0,
|
||||||
|
"torrent_list_interval_seconds": 30.0,
|
||||||
"system_stats_interval_seconds": 5.0,
|
"system_stats_interval_seconds": 5.0,
|
||||||
"tracker_stats_interval_seconds": 300.0,
|
"tracker_stats_interval_seconds": 300.0,
|
||||||
"disk_stats_interval_seconds": 60.0,
|
"disk_stats_interval_seconds": 60.0,
|
||||||
@@ -52,6 +53,7 @@ def normalize_settings(data: dict | None) -> dict:
|
|||||||
"active_interval_seconds": _coerce_float(raw.get("active_interval_seconds"), DEFAULTS["active_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 30.0),
|
"active_interval_seconds": _coerce_float(raw.get("active_interval_seconds"), DEFAULTS["active_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 30.0),
|
||||||
"idle_interval_seconds": _coerce_float(raw.get("idle_interval_seconds"), DEFAULTS["idle_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 120.0),
|
"idle_interval_seconds": _coerce_float(raw.get("idle_interval_seconds"), DEFAULTS["idle_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 120.0),
|
||||||
"error_interval_seconds": _coerce_float(raw.get("error_interval_seconds"), DEFAULTS["error_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 300.0),
|
"error_interval_seconds": _coerce_float(raw.get("error_interval_seconds"), DEFAULTS["error_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 300.0),
|
||||||
|
"live_stats_interval_seconds": _coerce_float(raw.get("live_stats_interval_seconds"), DEFAULTS["live_stats_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 60.0),
|
||||||
"torrent_list_interval_seconds": _coerce_float(raw.get("torrent_list_interval_seconds"), DEFAULTS["torrent_list_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 120.0),
|
"torrent_list_interval_seconds": _coerce_float(raw.get("torrent_list_interval_seconds"), DEFAULTS["torrent_list_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 120.0),
|
||||||
"system_stats_interval_seconds": _coerce_float(raw.get("system_stats_interval_seconds"), DEFAULTS["system_stats_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 120.0),
|
"system_stats_interval_seconds": _coerce_float(raw.get("system_stats_interval_seconds"), DEFAULTS["system_stats_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 120.0),
|
||||||
"tracker_stats_interval_seconds": _coerce_float(raw.get("tracker_stats_interval_seconds"), DEFAULTS["tracker_stats_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 1800.0),
|
"tracker_stats_interval_seconds": _coerce_float(raw.get("tracker_stats_interval_seconds"), DEFAULTS["tracker_stats_interval_seconds"], MIN_POLL_INTERVAL_SECONDS, 1800.0),
|
||||||
@@ -65,7 +67,7 @@ def normalize_settings(data: dict | None) -> dict:
|
|||||||
"recovery_after_errors": int(_coerce_float(raw.get("recovery_after_errors"), 3, 1, 20)),
|
"recovery_after_errors": int(_coerce_float(raw.get("recovery_after_errors"), 3, 1, 20)),
|
||||||
}
|
}
|
||||||
if settings["safe_fallback_enabled"]:
|
if settings["safe_fallback_enabled"]:
|
||||||
for key in ("active_interval_seconds", "idle_interval_seconds", "error_interval_seconds", "torrent_list_interval_seconds", "system_stats_interval_seconds", "queue_stats_interval_seconds"):
|
for key in ("active_interval_seconds", "idle_interval_seconds", "error_interval_seconds", "live_stats_interval_seconds", "torrent_list_interval_seconds", "system_stats_interval_seconds", "queue_stats_interval_seconds"):
|
||||||
if settings[key] <= 0:
|
if settings[key] <= 0:
|
||||||
settings[key] = DEFAULTS[key]
|
settings[key] = DEFAULTS[key]
|
||||||
return settings
|
return settings
|
||||||
@@ -102,6 +104,8 @@ def save_settings(profile_id: int, data: dict) -> dict:
|
|||||||
class ProfilePollState:
|
class ProfilePollState:
|
||||||
profile_id: int
|
profile_id: int
|
||||||
last_fast_at: float = 0.0
|
last_fast_at: float = 0.0
|
||||||
|
last_live_at: float = 0.0
|
||||||
|
last_list_at: float = 0.0
|
||||||
last_system_at: float = 0.0
|
last_system_at: float = 0.0
|
||||||
last_slow_at: float = 0.0
|
last_slow_at: float = 0.0
|
||||||
last_tracker_at: float = 0.0
|
last_tracker_at: float = 0.0
|
||||||
@@ -151,12 +155,29 @@ def interval_for(settings: dict, state: ProfilePollState) -> float:
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
|
def effective_live_interval(settings: dict, state: ProfilePollState) -> float:
|
||||||
|
return max(MIN_POLL_INTERVAL_SECONDS, interval_for(settings, state), float(settings.get("live_stats_interval_seconds") or DEFAULTS["live_stats_interval_seconds"]))
|
||||||
|
|
||||||
|
|
||||||
|
def effective_list_interval(settings: dict, state: ProfilePollState) -> float:
|
||||||
|
return max(MIN_POLL_INTERVAL_SECONDS, float(settings.get("torrent_list_interval_seconds") or DEFAULTS["torrent_list_interval_seconds"]))
|
||||||
|
|
||||||
|
|
||||||
def effective_fast_interval(settings: dict, state: ProfilePollState) -> float:
|
def effective_fast_interval(settings: dict, state: ProfilePollState) -> float:
|
||||||
return max(MIN_POLL_INTERVAL_SECONDS, interval_for(settings, state), float(settings.get("torrent_list_interval_seconds") or DEFAULTS["torrent_list_interval_seconds"]))
|
# Note: Kept for compatibility with older diagnostics; the fast interval now means lightweight live stats.
|
||||||
|
return effective_live_interval(settings, state)
|
||||||
|
|
||||||
|
|
||||||
|
def should_live_poll(now: float, settings: dict, state: ProfilePollState) -> bool:
|
||||||
|
return (now - state.last_live_at) >= effective_live_interval(settings, state)
|
||||||
|
|
||||||
|
|
||||||
|
def should_list_poll(now: float, settings: dict, state: ProfilePollState) -> bool:
|
||||||
|
return (now - state.last_list_at) >= effective_list_interval(settings, state)
|
||||||
|
|
||||||
|
|
||||||
def should_fast_poll(now: float, settings: dict, state: ProfilePollState) -> bool:
|
def should_fast_poll(now: float, settings: dict, state: ProfilePollState) -> bool:
|
||||||
return (now - state.last_fast_at) >= effective_fast_interval(settings, state)
|
return should_live_poll(now, settings, state)
|
||||||
|
|
||||||
|
|
||||||
def should_system_poll(now: float, settings: dict, state: ProfilePollState) -> bool:
|
def should_system_poll(now: float, settings: dict, state: ProfilePollState) -> bool:
|
||||||
@@ -194,7 +215,7 @@ def mark_tick(state: ProfilePollState, started_at: float, active: bool, ok: bool
|
|||||||
state.last_tick_gap_ms = round((started_at - previous_started_at) * 1000.0, 2) if previous_started_at else 0.0
|
state.last_tick_gap_ms = round((started_at - previous_started_at) * 1000.0, 2) if previous_started_at else 0.0
|
||||||
state.last_tick_started_at = started_at
|
state.last_tick_started_at = started_at
|
||||||
state.last_active = bool(active)
|
state.last_active = bool(active)
|
||||||
state.effective_interval_seconds = effective_fast_interval(effective_settings, state)
|
state.effective_interval_seconds = effective_live_interval(effective_settings, state)
|
||||||
state.last_ok = bool(ok)
|
state.last_ok = bool(ok)
|
||||||
state.last_error = str(error or "")
|
state.last_error = str(error or "")
|
||||||
state.emitted_payload_size = int(emitted_payload_size or 0)
|
state.emitted_payload_size = int(emitted_payload_size or 0)
|
||||||
@@ -234,6 +255,8 @@ def mark_tick(state: ProfilePollState, started_at: float, active: bool, ok: bool
|
|||||||
"last_ok": state.last_ok,
|
"last_ok": state.last_ok,
|
||||||
"last_tick_gap_ms": state.last_tick_gap_ms,
|
"last_tick_gap_ms": state.last_tick_gap_ms,
|
||||||
"effective_interval_seconds": state.effective_interval_seconds,
|
"effective_interval_seconds": state.effective_interval_seconds,
|
||||||
|
"live_stats_interval_seconds": effective_live_interval(effective_settings, state),
|
||||||
|
"torrent_list_interval_seconds": effective_list_interval(effective_settings, state),
|
||||||
"configured_min_interval_seconds": MIN_POLL_INTERVAL_SECONDS,
|
"configured_min_interval_seconds": MIN_POLL_INTERVAL_SECONDS,
|
||||||
"last_error": state.last_error,
|
"last_error": state.last_error,
|
||||||
"duration_ms": state.last_tick_ms,
|
"duration_ms": state.last_tick_ms,
|
||||||
|
|||||||
@@ -217,6 +217,13 @@ TORRENT_OPTIONAL_FIELDS = [
|
|||||||
"d.timestamp.finished=",
|
"d.timestamp.finished=",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
LIVE_TORRENT_FIELDS = [
|
||||||
|
"d.hash=", "d.state=", "d.complete=", "d.size_bytes=", "d.completed_bytes=",
|
||||||
|
"d.ratio=", "d.up.rate=", "d.down.rate=", "d.up.total=", "d.down.total=",
|
||||||
|
"d.peers_connected=", "d.peers_complete=", "d.message=", "d.hashing=", "d.is_active=",
|
||||||
|
"d.custom1=",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def human_duration(seconds: int) -> str:
|
def human_duration(seconds: int) -> str:
|
||||||
# Note: Download ETA is derived locally from remaining bytes and current download speed.
|
# Note: Download ETA is derived locally from remaining bytes and current download speed.
|
||||||
@@ -313,6 +320,65 @@ def normalize_row(row: list) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_live_row(row: list) -> dict:
|
||||||
|
"""Normalize the small row used by the fast live stats poller."""
|
||||||
|
# Note: The live poller intentionally reads only volatile fields so the main list poller can run less often.
|
||||||
|
size = int(row[3] or 0)
|
||||||
|
completed = int(row[4] or 0)
|
||||||
|
complete = int(row[2] or 0)
|
||||||
|
state = int(row[1] or 0)
|
||||||
|
down_rate = int(row[7] or 0)
|
||||||
|
up_rate = int(row[6] or 0)
|
||||||
|
ratio_raw = int(row[5] or 0)
|
||||||
|
remaining_bytes = max(0, size - completed)
|
||||||
|
eta_seconds = int(remaining_bytes / down_rate) if down_rate > 0 and not complete else 0
|
||||||
|
msg = str(row[12] or "")
|
||||||
|
hashing = int(row[13] or 0)
|
||||||
|
is_active = int(row[14] or 0)
|
||||||
|
labels = str(row[15] or "")
|
||||||
|
is_checking = bool(hashing) or _message_indicates_active_check(msg.lower())
|
||||||
|
post_check = POST_CHECK_DOWNLOAD_LABEL in _label_names(labels) and not is_checking and not bool(is_active)
|
||||||
|
is_paused = bool(state) and not bool(is_active) and not is_checking and not post_check
|
||||||
|
status = "Checking" if is_checking else "Post-check" if post_check else "Paused" if is_paused else "Seeding" if complete and state else "Downloading" if state else "Stopped"
|
||||||
|
progress = 100.0 if size <= 0 and complete else round((completed / size) * 100, 2) if size else 0.0
|
||||||
|
to_download_bytes = remaining_bytes if not complete else 0
|
||||||
|
return {
|
||||||
|
"hash": str(row[0] or ""),
|
||||||
|
"state": state,
|
||||||
|
"active": is_active,
|
||||||
|
"paused": is_paused,
|
||||||
|
"complete": complete,
|
||||||
|
"completed_bytes": completed,
|
||||||
|
"progress": progress,
|
||||||
|
"ratio": round(ratio_raw / 1000, 3),
|
||||||
|
"up_rate": up_rate,
|
||||||
|
"up_rate_h": human_rate(up_rate),
|
||||||
|
"down_rate": down_rate,
|
||||||
|
"down_rate_h": human_rate(down_rate),
|
||||||
|
"eta_seconds": eta_seconds,
|
||||||
|
"eta_h": human_duration(eta_seconds) if eta_seconds else "-",
|
||||||
|
"up_total": int(row[8] or 0),
|
||||||
|
"up_total_h": human_size(row[8] or 0),
|
||||||
|
"down_total": int(row[9] or 0),
|
||||||
|
"down_total_h": human_size(row[9] or 0),
|
||||||
|
"to_download": to_download_bytes,
|
||||||
|
"to_download_h": human_size(to_download_bytes) if to_download_bytes else "",
|
||||||
|
"peers": int(row[10] or 0),
|
||||||
|
"seeds": int(row[11] or 0),
|
||||||
|
"message": msg,
|
||||||
|
"status": status,
|
||||||
|
"post_check": post_check,
|
||||||
|
"hashing": hashing,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def list_torrent_live_stats(profile: dict) -> list[dict]:
|
||||||
|
"""Return lightweight live torrent stats for the fast poller."""
|
||||||
|
# Note: This avoids the full torrent row multicall on every speed/status tick.
|
||||||
|
rows = client_for(profile).d.multicall2("", "main", *LIVE_TORRENT_FIELDS)
|
||||||
|
return [normalize_live_row(list(row)) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
def list_torrents(profile: dict) -> list[dict]:
|
def list_torrents(profile: dict) -> list[dict]:
|
||||||
c = client_for(profile)
|
c = client_for(profile)
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from threading import RLock
|
|||||||
from time import time
|
from time import time
|
||||||
from . import rtorrent, operation_logs
|
from . import rtorrent, operation_logs
|
||||||
|
|
||||||
|
_LIVE_KEYS = {"state", "active", "paused", "complete", "completed_bytes", "progress", "ratio", "up_rate", "up_rate_h", "down_rate", "down_rate_h", "eta_seconds", "eta_h", "up_total", "up_total_h", "down_total", "down_total_h", "to_download", "to_download_h", "peers", "seeds", "message", "status", "post_check", "hashing"}
|
||||||
|
|
||||||
_VOLATILE = {"down_rate", "down_rate_h", "up_rate", "up_rate_h", "progress", "completed_bytes", "peers", "seeds", "ratio", "state", "status", "message", "down_total", "down_total_h", "to_download", "to_download_h", "up_total", "up_total_h"}
|
_VOLATILE = {"down_rate", "down_rate_h", "up_rate", "up_rate_h", "progress", "completed_bytes", "peers", "seeds", "ratio", "state", "status", "message", "down_total", "down_total_h", "to_download", "to_download_h", "up_total", "up_total_h"}
|
||||||
|
|
||||||
|
|
||||||
@@ -33,6 +35,42 @@ class TorrentCache:
|
|||||||
self._updated_at.pop(profile_id, None)
|
self._updated_at.pop(profile_id, None)
|
||||||
return removed
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
def refresh_live(self, profile: dict) -> dict:
|
||||||
|
"""Refresh only volatile live fields without replacing the full cached torrent rows."""
|
||||||
|
# Note: The fast poller uses this lightweight path so speeds/statuses can update often while the full list poller stays slower.
|
||||||
|
profile_id = int(profile["id"])
|
||||||
|
try:
|
||||||
|
rows = rtorrent.list_torrent_live_stats(profile)
|
||||||
|
live = {t["hash"]: t for t in rows if t.get("hash")}
|
||||||
|
with self._lock:
|
||||||
|
old = dict(self._data.get(profile_id, {}))
|
||||||
|
if not old:
|
||||||
|
self._errors[profile_id] = ""
|
||||||
|
return {"ok": True, "profile_id": profile_id, "updated": [], "missing": [], "unknown": list(live.keys()), "requires_full_refresh": bool(live)}
|
||||||
|
updated = []
|
||||||
|
for h, live_row in live.items():
|
||||||
|
current = old.get(h)
|
||||||
|
if not current:
|
||||||
|
continue
|
||||||
|
patch = {"hash": h}
|
||||||
|
for key in _LIVE_KEYS:
|
||||||
|
if key in live_row and current.get(key) != live_row.get(key):
|
||||||
|
patch[key] = live_row.get(key)
|
||||||
|
if len(patch) > 1:
|
||||||
|
current.update({k: v for k, v in patch.items() if k != "hash"})
|
||||||
|
updated.append(patch)
|
||||||
|
missing = [h for h in old.keys() if h not in live]
|
||||||
|
unknown = [h for h in live.keys() if h not in old]
|
||||||
|
self._data[profile_id] = old
|
||||||
|
self._errors[profile_id] = ""
|
||||||
|
self._updated_at[profile_id] = time()
|
||||||
|
return {"ok": True, "profile_id": profile_id, "updated": updated, "missing": missing, "unknown": unknown, "requires_full_refresh": bool(missing or unknown)}
|
||||||
|
except Exception as exc:
|
||||||
|
with self._lock:
|
||||||
|
self._errors[profile_id] = str(exc)
|
||||||
|
return {"ok": False, "profile_id": profile_id, "error": str(exc), "updated": [], "missing": [], "unknown": [], "requires_full_refresh": False}
|
||||||
|
|
||||||
def refresh(self, profile: dict) -> dict:
|
def refresh(self, profile: dict) -> dict:
|
||||||
profile_id = int(profile["id"])
|
profile_id = int(profile["id"])
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ def register_socketio_handlers(socketio):
|
|||||||
def poller():
|
def poller():
|
||||||
while True:
|
while True:
|
||||||
loop_started = time.monotonic()
|
loop_started = time.monotonic()
|
||||||
next_sleep = poller_control.MIN_POLL_INTERVAL_SECONDS
|
next_sleep = 10.0
|
||||||
for profile in _poller_profiles():
|
for profile in _poller_profiles():
|
||||||
if not profile:
|
if not profile:
|
||||||
continue
|
continue
|
||||||
@@ -114,47 +114,92 @@ def register_socketio_handlers(socketio):
|
|||||||
settings = poller_control.get_settings(pid)
|
settings = poller_control.get_settings(pid)
|
||||||
state = poller_control.state_for(pid)
|
state = poller_control.state_for(pid)
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
next_sleep = min(next_sleep, poller_control.effective_fast_interval(settings, state))
|
live_interval = poller_control.effective_live_interval(settings, state)
|
||||||
if not poller_control.should_fast_poll(now, settings, state):
|
list_interval = poller_control.effective_list_interval(settings, state)
|
||||||
|
next_sleep = min(
|
||||||
|
next_sleep,
|
||||||
|
max(poller_control.MIN_POLL_INTERVAL_SECONDS, live_interval - (now - state.last_live_at)),
|
||||||
|
max(poller_control.MIN_POLL_INTERVAL_SECONDS, list_interval - (now - state.last_list_at)),
|
||||||
|
max(poller_control.MIN_POLL_INTERVAL_SECONDS, float(settings["system_stats_interval_seconds"]) - (now - state.last_system_at)),
|
||||||
|
max(poller_control.MIN_POLL_INTERVAL_SECONDS, float(settings["slow_stats_interval_seconds"]) - (now - state.last_slow_at)),
|
||||||
|
max(poller_control.MIN_POLL_INTERVAL_SECONDS, float(settings["queue_stats_interval_seconds"]) - (now - state.last_queue_at)),
|
||||||
|
)
|
||||||
|
|
||||||
|
run_live = poller_control.should_live_poll(now, settings, state)
|
||||||
|
run_list = poller_control.should_list_poll(now, settings, state)
|
||||||
|
run_system = poller_control.should_system_poll(now, settings, state)
|
||||||
|
run_slow = poller_control.should_slow_poll(now, settings, state)
|
||||||
|
run_queue = poller_control.should_queue_poll(now, settings, state)
|
||||||
|
if not (run_live or run_list or run_system or run_slow or run_queue):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
tick_started = time.monotonic()
|
tick_started = time.monotonic()
|
||||||
changed = False
|
changed = False
|
||||||
ok = True
|
ok = True
|
||||||
error = ""
|
error = ""
|
||||||
active = False
|
active = state.last_active
|
||||||
emitted_payload_size = 0
|
emitted_payload_size = 0
|
||||||
rtorrent_call_count = 0
|
rtorrent_call_count = 0
|
||||||
skipped_emissions = 0
|
skipped_emissions = 0
|
||||||
heartbeat = {"ok": True, "profile_id": pid, "tick": state.tick_count + 1, "error": ""}
|
heartbeat = {"ok": True, "profile_id": pid, "tick": state.tick_count + 1, "error": ""}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
diff = torrent_cache.refresh(profile)
|
|
||||||
rtorrent_call_count += 1
|
|
||||||
state.last_fast_at = now
|
|
||||||
ok = bool(diff.get("ok"))
|
|
||||||
error = str(diff.get("error") or "")
|
|
||||||
rows = torrent_cache.snapshot(pid)
|
rows = torrent_cache.snapshot(pid)
|
||||||
active = _is_active_rows(rows)
|
speed_status = _speed_status_from_rows(pid, rows)
|
||||||
speed_status = _speed_status_from_rows(pid, rows) if diff.get("ok") else None
|
|
||||||
if diff.get("ok") and (diff["added"] or diff["updated"] or diff["removed"]):
|
if run_live:
|
||||||
changed = True
|
live = torrent_cache.refresh_live(profile)
|
||||||
payload = {**diff, "summary": cached_summary(pid, rows, force=True), "speed_status": speed_status}
|
rtorrent_call_count += 1
|
||||||
emitted_payload_size += len(json.dumps(payload, default=str))
|
state.last_live_at = now
|
||||||
_emit_profile(socketio, "torrent_patch", payload, pid)
|
state.last_fast_at = now
|
||||||
elif not diff.get("ok"):
|
ok = bool(live.get("ok"))
|
||||||
_emit_profile(socketio, "rtorrent_error", diff, pid)
|
error = str(live.get("error") or "")
|
||||||
else:
|
rows = torrent_cache.snapshot(pid)
|
||||||
# Note: Speeds and peak records may change even when no torrent rows need repainting.
|
active = _is_active_rows(rows)
|
||||||
if speed_status:
|
speed_status = _speed_status_from_rows(pid, rows) if live.get("ok") else speed_status
|
||||||
payload = {"ok": True, "profile_id": pid, "added": [], "updated": [], "removed": [], "speed_status": speed_status}
|
if live.get("ok"):
|
||||||
|
if live.get("updated") or speed_status:
|
||||||
|
changed = changed or bool(live.get("updated"))
|
||||||
|
payload = {
|
||||||
|
"ok": True,
|
||||||
|
"profile_id": pid,
|
||||||
|
"updated": live.get("updated") or [],
|
||||||
|
"speed_status": speed_status,
|
||||||
|
"requires_full_refresh": bool(live.get("requires_full_refresh")),
|
||||||
|
}
|
||||||
|
emitted_payload_size += len(json.dumps(payload, default=str))
|
||||||
|
_emit_profile(socketio, "torrent_live_patch", payload, pid)
|
||||||
|
else:
|
||||||
|
skipped_emissions += 1
|
||||||
|
if live.get("requires_full_refresh"):
|
||||||
|
# Note: Missing or unknown hashes mean the next slow list tick must reconcile rows.
|
||||||
|
state.last_list_at = 0.0
|
||||||
|
run_list = True
|
||||||
|
else:
|
||||||
|
_emit_profile(socketio, "rtorrent_error", live, pid)
|
||||||
|
|
||||||
|
if run_list:
|
||||||
|
diff = torrent_cache.refresh(profile)
|
||||||
|
rtorrent_call_count += 1
|
||||||
|
state.last_list_at = now
|
||||||
|
ok = bool(diff.get("ok"))
|
||||||
|
error = str(diff.get("error") or "")
|
||||||
|
rows = torrent_cache.snapshot(pid)
|
||||||
|
active = _is_active_rows(rows)
|
||||||
|
speed_status = _speed_status_from_rows(pid, rows) if diff.get("ok") else speed_status
|
||||||
|
if diff.get("ok") and (diff["added"] or diff["updated"] or diff["removed"]):
|
||||||
|
changed = True
|
||||||
|
payload = {**diff, "summary": cached_summary(pid, rows, force=True), "speed_status": speed_status}
|
||||||
emitted_payload_size += len(json.dumps(payload, default=str))
|
emitted_payload_size += len(json.dumps(payload, default=str))
|
||||||
_emit_profile(socketio, "torrent_patch", payload, pid)
|
_emit_profile(socketio, "torrent_patch", payload, pid)
|
||||||
|
elif not diff.get("ok"):
|
||||||
|
_emit_profile(socketio, "rtorrent_error", diff, pid)
|
||||||
else:
|
else:
|
||||||
skipped_emissions += 1
|
skipped_emissions += 1
|
||||||
|
|
||||||
if poller_control.should_system_poll(now, settings, state):
|
if run_system:
|
||||||
state.last_system_at = now
|
state.last_system_at = now
|
||||||
|
rows = torrent_cache.snapshot(pid)
|
||||||
status = rtorrent.system_status(profile, rows)
|
status = rtorrent.system_status(profile, rows)
|
||||||
rtorrent_call_count += 1
|
rtorrent_call_count += 1
|
||||||
if bool(profile.get("is_remote")):
|
if bool(profile.get("is_remote")):
|
||||||
@@ -185,9 +230,11 @@ def register_socketio_handlers(socketio):
|
|||||||
if poller_control.should_tracker_poll(now, settings, state):
|
if poller_control.should_tracker_poll(now, settings, state):
|
||||||
state.last_tracker_at = now
|
state.last_tracker_at = now
|
||||||
|
|
||||||
if poller_control.should_slow_poll(now, settings, state) or poller_control.should_queue_poll(now, settings, state):
|
if run_slow or run_queue:
|
||||||
state.last_slow_at = now
|
if run_slow:
|
||||||
state.last_queue_at = now
|
state.last_slow_at = now
|
||||||
|
if run_queue:
|
||||||
|
state.last_queue_at = now
|
||||||
if state.slow_task_running:
|
if state.slow_task_running:
|
||||||
skipped_emissions += 1
|
skipped_emissions += 1
|
||||||
else:
|
else:
|
||||||
|
|||||||
2
pytorrent/static/js/bootstrap.js
vendored
2
pytorrent/static/js/bootstrap.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user