Serve recent stages from SQLite with configurable background refresh
This commit is contained in:
@@ -23,6 +23,8 @@ from ..auth import get_current_user
|
||||
from ..runtime import get_runtime_settings
|
||||
from .images import cache_tmdb_image, is_tmdb_cached
|
||||
from ..db import (
|
||||
get_request_stage_cache,
|
||||
save_request_stage_cache,
|
||||
add_portal_item_activity,
|
||||
get_portal_item,
|
||||
save_action,
|
||||
@@ -157,6 +159,7 @@ async def _request_is_available_in_jellyfin(
|
||||
media_type: Optional[str],
|
||||
request_payload: Optional[Dict[str, Any]],
|
||||
availability_cache: Dict[str, bool],
|
||||
raise_errors: bool = False,
|
||||
) -> bool:
|
||||
if not jellyfin.configured() or not title:
|
||||
return False
|
||||
@@ -168,6 +171,8 @@ async def _request_is_available_in_jellyfin(
|
||||
try:
|
||||
search = await jellyfin.search_items(title, types, limit=50)
|
||||
except Exception:
|
||||
if raise_errors:
|
||||
raise
|
||||
availability_cache[cache_key] = False
|
||||
return False
|
||||
if isinstance(search, dict):
|
||||
@@ -2741,15 +2746,8 @@ async def recent_requests(
|
||||
runtime = get_runtime_settings()
|
||||
client = JellyseerrClient(runtime.jellyseerr_base_url, runtime.jellyseerr_api_key)
|
||||
mode = (runtime.requests_data_source or "prefer_cache").lower()
|
||||
allow_remote = mode == "always_js"
|
||||
if allow_remote:
|
||||
if not client.configured():
|
||||
raise HTTPException(status_code=400, detail="Seerr not configured")
|
||||
try:
|
||||
await _ensure_requests_cache(client)
|
||||
except httpx.HTTPStatusError as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from exc
|
||||
|
||||
# Browsing is always local. Synchronization is owned by background workers.
|
||||
allow_remote = False
|
||||
username_norm = _normalize_username(user.get("username", ""))
|
||||
requested_by_id = user.get("jellyseerr_user_id")
|
||||
requested_by = None if user.get("role") == "admin" else username_norm
|
||||
@@ -2773,7 +2771,8 @@ async def recent_requests(
|
||||
matched = 0
|
||||
cache_mode = (runtime.artwork_cache_mode or "remote").lower()
|
||||
allow_title_hydrate = False
|
||||
allow_artwork_hydrate = client.configured()
|
||||
allow_artwork_hydrate = False
|
||||
stage_cache = await asyncio.to_thread(get_request_stage_cache)
|
||||
jellyfin = JellyfinClient(runtime.jellyfin_base_url, runtime.jellyfin_api_key)
|
||||
jellyfin_cache: Dict[str, bool] = {}
|
||||
results = []
|
||||
@@ -2787,7 +2786,7 @@ async def recent_requests(
|
||||
)
|
||||
year = row.get("year")
|
||||
details = None
|
||||
if row.get("request_id") and mode != "always_js":
|
||||
if row.get("request_id"):
|
||||
cached_payload = get_request_cache_payload(int(row["request_id"]))
|
||||
if isinstance(cached_payload, dict):
|
||||
details = cached_payload
|
||||
@@ -2874,14 +2873,8 @@ async def recent_requests(
|
||||
status = 5
|
||||
status_label = "Repair in progress"
|
||||
elif status_label in {"Working on it", "Ready to watch", "Partially ready"}:
|
||||
is_available = await _request_is_available_in_jellyfin(
|
||||
jellyfin,
|
||||
title,
|
||||
year,
|
||||
row.get("media_type"),
|
||||
details if isinstance(details, dict) else None,
|
||||
jellyfin_cache,
|
||||
)
|
||||
saved = stage_cache.get(row.get("request_id")) or {}
|
||||
is_available = bool(saved.get("ready")) and saved.get("source_updated") == row.get("updated_at")
|
||||
status_label = _status_label_with_jellyfin(status, is_available)
|
||||
if status_label == STATUS_LABELS[4]:
|
||||
status = 4
|
||||
@@ -3755,3 +3748,38 @@ async def action_grab(
|
||||
save_action, request_id, "grab", "Download selected release", "failed", failure_message
|
||||
)
|
||||
raise HTTPException(status_code=502, detail=failure_message)
|
||||
|
||||
|
||||
async def refresh_local_request_stages():
|
||||
runtime = get_runtime_settings()
|
||||
jellyfin = JellyfinClient(runtime.jellyfin_base_url, runtime.jellyfin_api_key)
|
||||
if not jellyfin.configured():
|
||||
return
|
||||
rows = await asyncio.to_thread(get_cached_requests_since, (datetime.now(timezone.utc) - timedelta(days=RECENT_CACHE_MAX_DAYS)).isoformat())
|
||||
saved = await asyncio.to_thread(get_request_stage_cache)
|
||||
interval = max(1, min(1440, int(runtime.requests_stage_refresh_minutes))) * 60
|
||||
now = time.time()
|
||||
due = [row for row in rows if row.get('status') == 5 and
|
||||
(now - (saved.get(row['request_id']) or {}).get('checked_at', 0) >= interval or
|
||||
(saved.get(row['request_id']) or {}).get('source_updated') != row.get('updated_at'))]
|
||||
semaphore = asyncio.Semaphore(4)
|
||||
async def check(row):
|
||||
async with semaphore:
|
||||
payload = await asyncio.to_thread(get_request_cache_payload, row['request_id'])
|
||||
try:
|
||||
ready = await _request_is_available_in_jellyfin(jellyfin, row.get('title'), row.get('year'), row.get('media_type'), payload, {}, raise_errors=True)
|
||||
return (row['request_id'], row.get('updated_at'), int(ready), time.time())
|
||||
except Exception:
|
||||
logger.warning('Local request stage refresh failed request_id=%s', row['request_id'])
|
||||
return None
|
||||
checked = await asyncio.gather(*(check(row) for row in due))
|
||||
await asyncio.to_thread(save_request_stage_cache, [row for row in checked if row is not None])
|
||||
|
||||
|
||||
async def run_local_request_stage_loop():
|
||||
while True:
|
||||
try:
|
||||
await refresh_local_request_stages()
|
||||
except Exception:
|
||||
logger.exception('Local request stage refresh failed')
|
||||
await asyncio.sleep(30)
|
||||
|
||||
Reference in New Issue
Block a user