From 3551808351c7d4b54974ee4096bca06354ac6be8 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Sat, 30 May 2026 02:19:33 +0000 Subject: [PATCH] Fix root-relative playable path normalization --- frontend/src/composables/useMediaWebSocket.ts | 30 +++++++++++++++---- mediahive/server.py | 4 +-- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/frontend/src/composables/useMediaWebSocket.ts b/frontend/src/composables/useMediaWebSocket.ts index b1acacc..8d36788 100644 --- a/frontend/src/composables/useMediaWebSocket.ts +++ b/frontend/src/composables/useMediaWebSocket.ts @@ -23,6 +23,8 @@ interface RootState { reconnectTimer: ReturnType | null } +const MERGED_KEY_DELIMITER = "::" + /** * Composable that connects to per-root MediaHive WebSockets and keeps * a merged media index updated in real time. @@ -104,20 +106,28 @@ export function useMediaWebSocket() { k, { ...t, - playable_file: expandPlayablePath(k, t.playable_file || null), + playable_file: expandPlayablePath(resolveFilePathKey(k), t.playable_file || null), root_id: t.root_id || rootId, }, ]), ) } + function resolveFilePathKey(key: string): string { + const delimIndex = key.indexOf(MERGED_KEY_DELIMITER) + if (delimIndex >= 0) { + return key.slice(delimIndex + MERGED_KEY_DELIMITER.length) + } + return key + } + function mergeTorrentDicts( a: { [key: string]: Torrent }, b: { [key: string]: Torrent }, ): { [key: string]: Torrent } { const merged: { [key: string]: Torrent } = { ...a } for (const [k, t] of Object.entries(b)) { - const uniqueKey = merged[k] ? `${t.root_id || "unknown"}:${k}` : k + const uniqueKey = merged[k] ? `${t.root_id || "unknown"}${MERGED_KEY_DELIMITER}${k}` : k merged[uniqueKey] = t } const sorted = Object.entries(merged).sort(([, t1], [, t2]) => { @@ -130,11 +140,11 @@ export function useMediaWebSocket() { } function withMovieIdentity(id: string, movie: Movie, rootId: string): MovieUi { - return { ...normalizeMovie(movie), id, root_id: rootId } + return { ...normalizeMovie(movie, rootId), id, root_id: rootId } } function withSeriesIdentity(id: string, series: Series, rootId: string): SeriesUi { - return { ...normalizeSeries(series), id, root_id: rootId } + return { ...normalizeSeries(series, rootId), id, root_id: rootId } } function normalizeCastMember(member: unknown): { @@ -197,16 +207,24 @@ export function useMediaWebSocket() { return next } - function normalizeMovie(movie: Movie): Movie { + function normalizeMovie(movie: Movie, rootId: string | null): Movie { return { ...movie, + files: annotateFiles(movie.files, rootId), info: normalizeInfo(movie.info), } } - function normalizeSeries(series: Series): Series { + function normalizeSeries(series: Series, rootId: string | null): Series { return { ...series, + seasons: (series.seasons || []).map((season) => ({ + ...season, + episodes: (season.episodes || []).map((episode) => ({ + ...episode, + files: annotateFiles(episode.files, rootId), + })), + })), info: normalizeInfo(series.info), } } diff --git a/mediahive/server.py b/mediahive/server.py index ccf0795..ed218c6 100644 --- a/mediahive/server.py +++ b/mediahive/server.py @@ -491,7 +491,7 @@ async def play_media(root_id: str, request: Request): """Open a media file with the selected player.""" ctx = _get_context(root_id) req = msgspec.json.decode(await request.body(), type=PlayMediaRequest) - file_path = ctx.root_path / req.file_path + file_path = _resolve_root_scoped_path(ctx.root_path, req.file_path) if not file_path.exists(): raise HTTPException(status_code=404, detail=f"File not found: {req.file_path}") @@ -525,7 +525,7 @@ async def open_folder(root_id: str, request: Request): """Open a folder in the system file explorer.""" ctx = _get_context(root_id) req = msgspec.json.decode(await request.body(), type=OpenFolderRequest) - target_path = ctx.root_path / req.folder_path + target_path = _resolve_root_scoped_path(ctx.root_path, req.folder_path) if not target_path.exists(): raise HTTPException(