"""Dashboard router — media counts, per-library breakdown, activity.""" from __future__ import annotations import logging from typing import Any from fastapi import APIRouter, Depends from media_library_viewer_api.dependencies import get_jellyfin_client, get_user_id from media_library_viewer_api.clients.jellyfin import JellyfinClient logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/dashboard", tags=["dashboard"]) @router.get("/counts") def get_counts( client: JellyfinClient = Depends(get_jellyfin_client), user_id: str = Depends(get_user_id), ) -> dict[str, int]: """Return total movie/series/episode counts.""" counts = client.media_counts(user_id) logger.info("Dashboard counts user_id=%s counts=%s", user_id, counts) return counts @router.get("/libraries") def get_library_counts( client: JellyfinClient = Depends(get_jellyfin_client), user_id: str = Depends(get_user_id), ) -> list[dict[str, Any]]: """Return per-library item counts broken down by type.""" libraries = client.libraries(user_id) logger.info("Dashboard libraries user_id=%s count=%s", user_id, len(libraries)) return client.library_item_counts(user_id, libraries) def _map_sessions_to_activity_rows(sessions: list[dict[str, Any]]) -> list[dict[str, Any]]: """Normalize Jellyfin sessions into dashboard activity rows.""" results: list[dict[str, Any]] = [] for session in sessions: item = session.get("NowPlayingItem") or {} play_state = session.get("PlayState") or {} transcoding = session.get("TranscodingInfo") or {} has_item = bool(item) series = item.get("SeriesName") or "" title = ( f"{series} - {item.get('Name', '')}" if series else item.get("Name", "Unknown") ) if has_item else "(idle)" if not has_item: state_label = "idle" else: state_label = "paused" if play_state.get("IsPaused") else "playing" is_transcoding = bool(transcoding) transcode_type: list[str] = [] if is_transcoding: if transcoding.get("IsVideoDirect") is False: transcode_type.append("video") if transcoding.get("IsAudioDirect") is False: transcode_type.append("audio") if not transcode_type: transcode_type.append("active") results.append({ "user": session.get("UserName") or "Unknown", "title": title, "type": item.get("Type", "") if has_item else "", "state": state_label, "transcoding": "yes" if is_transcoding else "no", "transcoding_type": ", ".join(transcode_type), "device": session.get("DeviceName") or session.get("Client") or "", "session_id": session.get("Id") or "", }) return results @router.get("/activity") def get_activity( client: JellyfinClient = Depends(get_jellyfin_client), ) -> list[dict[str, Any]]: """Return activity rows for active sessions (playing and idle/logged-in).""" sessions = client.sessions() rows = _map_sessions_to_activity_rows(sessions) state_rank = {"playing": 0, "paused": 1, "idle": 2} rows.sort(key=lambda r: (state_rank.get(str(r.get("state")), 9), str(r.get("user", "")).lower())) logger.info("Dashboard activity sessions=%s rows=%s", len(sessions), len(rows)) return rows @router.get("/now-playing") def get_now_playing( client: JellyfinClient = Depends(get_jellyfin_client), ) -> list[dict[str, Any]]: """Backward-compatible alias; returns full activity rows.""" return get_activity(client)