feat: add typed qBittorrent scheduled polling
This commit is contained in:
@@ -41,6 +41,13 @@ from media_library_viewer_api.widgets.stats_provider import get_stats_provider
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _safe_int(value: Any, default: int = 0) -> int:
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
@dataclass
|
||||
class ServiceRecord:
|
||||
"""Runtime view of a service instance with decrypted secrets."""
|
||||
@@ -179,7 +186,7 @@ class MetricSource:
|
||||
|
||||
try:
|
||||
return await asyncio.wait_for(asyncio.to_thread(_do_post), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
except asyncio.TimeoutError as _timeout_error:
|
||||
return {"error": "Grafana query timed out"}
|
||||
except requests.RequestException as exc:
|
||||
logger.exception("grafana gateway query failed")
|
||||
@@ -299,7 +306,7 @@ class AlertmanagerWidgetSource:
|
||||
payload = response.json()
|
||||
alerts = payload.get("data", []) if isinstance(payload, dict) else []
|
||||
return summarize_alerts(alerts, severity_filter=severity_filter)
|
||||
except asyncio.TimeoutError:
|
||||
except asyncio.TimeoutError as _timeout_error:
|
||||
return {"error": "Widget data fetch timed out"}
|
||||
except requests.RequestException as exc:
|
||||
logger.exception("alertmanager adapter failed")
|
||||
@@ -334,7 +341,7 @@ class JellyfinWidgetSource:
|
||||
]
|
||||
rows = _map_sessions_to_activity_rows(sessions)
|
||||
return {"sessions": rows}
|
||||
except asyncio.TimeoutError:
|
||||
except asyncio.TimeoutError as _timeout_error:
|
||||
return {"error": "Widget data fetch timed out"}
|
||||
except Exception as exc:
|
||||
logger.exception("jellyfin adapter failed")
|
||||
@@ -365,7 +372,7 @@ class SshTaskWidgetSource:
|
||||
timeout=timeout,
|
||||
)
|
||||
return {"exit_status": result.exit_status, "stdout": result.stdout, "stderr": result.stderr}
|
||||
except asyncio.TimeoutError:
|
||||
except asyncio.TimeoutError as _timeout_error:
|
||||
_record_timeout(service, config, timeout)
|
||||
return {"error": "Widget data fetch timed out"}
|
||||
except Exception as exc:
|
||||
@@ -410,6 +417,26 @@ class QbittorrentWidgetSource:
|
||||
try:
|
||||
if service is None:
|
||||
return {"error": "qBittorrent widget is missing its service"}
|
||||
|
||||
if widget_kind == "speed":
|
||||
window_seconds = _safe_int(
|
||||
config.get("window_seconds") or service.config.get("sample_retention_seconds") or 1_800
|
||||
)
|
||||
window_seconds = max(60, min(window_seconds, 86_400))
|
||||
since_ts = _safe_int(time.time()) - window_seconds
|
||||
samples = QbittorrentSampleStore().window(service.id, since_ts=since_ts)
|
||||
series = [
|
||||
{
|
||||
"label": "download",
|
||||
"points": [{"t": s["ts"] * 1000, "v": s["dl_speed"]} for s in samples],
|
||||
},
|
||||
{
|
||||
"label": "upload",
|
||||
"points": [{"t": s["ts"] * 1000, "v": s["up_speed"]} for s in samples],
|
||||
},
|
||||
]
|
||||
return {"series": series}
|
||||
|
||||
base_url = str(service.config.get("base_url") or "")
|
||||
username = str(service.secrets.get("username") or "")
|
||||
password = str(service.secrets.get("password") or "")
|
||||
@@ -425,7 +452,6 @@ class QbittorrentWidgetSource:
|
||||
# gateway timeouts. The client re-logins itself on a 403.
|
||||
client = _qbittorrent_client((service.id, base_url, username, password, timeout))
|
||||
data = await asyncio.wait_for(asyncio.to_thread(client.maindata), timeout=timeout)
|
||||
server_state = data.get("server_state", {})
|
||||
torrents = data.get("torrents", {})
|
||||
|
||||
if widget_kind == "totals":
|
||||
@@ -450,21 +476,8 @@ class QbittorrentWidgetSource:
|
||||
]
|
||||
return {"torrents": active}
|
||||
|
||||
if widget_kind == "speed":
|
||||
dl = int(server_state.get("dl_info_speed", 0))
|
||||
up = int(server_state.get("up_info_speed", 0))
|
||||
ts = int(time.time())
|
||||
store = QbittorrentSampleStore()
|
||||
store.append(service.id, ts, dl, up)
|
||||
samples = store.window(service.id)
|
||||
series = [
|
||||
{"label": "download", "points": [{"t": s["ts"] * 1000, "v": s["dl_speed"]} for s in samples]},
|
||||
{"label": "upload", "points": [{"t": s["ts"] * 1000, "v": s["up_speed"]} for s in samples]},
|
||||
]
|
||||
return {"series": series}
|
||||
|
||||
return {"error": f"Unknown qBittorrent widget kind: {widget_kind}"}
|
||||
except asyncio.TimeoutError:
|
||||
except asyncio.TimeoutError as _timeout_error:
|
||||
return {"error": "qBittorrent data fetch timed out"}
|
||||
except Exception as exc:
|
||||
logger.exception("qbittorrent adapter failed")
|
||||
@@ -512,12 +525,10 @@ class StatsWidgetSource:
|
||||
provider = get_stats_provider(service.service_type)
|
||||
if provider is None:
|
||||
return {"error": f"No stats provider for service type '{service.service_type}'"}
|
||||
timeout = int(service.config.get("timeout_seconds") or 30)
|
||||
timeout = _safe_int(service.config.get("timeout_seconds") or 30, 30)
|
||||
try:
|
||||
result = await asyncio.wait_for(
|
||||
asyncio.to_thread(provider.fetch_stats, service), timeout=timeout
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
result = await asyncio.wait_for(asyncio.to_thread(provider.fetch_stats, service), timeout=timeout)
|
||||
except asyncio.TimeoutError as _timeout_error:
|
||||
return {"error": "Stats fetch timed out"}
|
||||
except Exception as exc:
|
||||
logger.exception("stats provider failed service=%s", service.id)
|
||||
|
||||
Reference in New Issue
Block a user