Fix root-relative playable path normalization

This commit is contained in:
2026-05-30 02:19:33 +00:00
parent 0ef6896181
commit 3551808351
2 changed files with 26 additions and 8 deletions
+24 -6
View File
@@ -23,6 +23,8 @@ interface RootState {
reconnectTimer: ReturnType<typeof setTimeout> | null reconnectTimer: ReturnType<typeof setTimeout> | null
} }
const MERGED_KEY_DELIMITER = "::"
/** /**
* Composable that connects to per-root MediaHive WebSockets and keeps * Composable that connects to per-root MediaHive WebSockets and keeps
* a merged media index updated in real time. * a merged media index updated in real time.
@@ -104,20 +106,28 @@ export function useMediaWebSocket() {
k, k,
{ {
...t, ...t,
playable_file: expandPlayablePath(k, t.playable_file || null), playable_file: expandPlayablePath(resolveFilePathKey(k), t.playable_file || null),
root_id: t.root_id || rootId, 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( function mergeTorrentDicts(
a: { [key: string]: Torrent }, a: { [key: string]: Torrent },
b: { [key: string]: Torrent }, b: { [key: string]: Torrent },
): { [key: string]: Torrent } { ): { [key: string]: Torrent } {
const merged: { [key: string]: Torrent } = { ...a } const merged: { [key: string]: Torrent } = { ...a }
for (const [k, t] of Object.entries(b)) { 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 merged[uniqueKey] = t
} }
const sorted = Object.entries(merged).sort(([, t1], [, t2]) => { const sorted = Object.entries(merged).sort(([, t1], [, t2]) => {
@@ -130,11 +140,11 @@ export function useMediaWebSocket() {
} }
function withMovieIdentity(id: string, movie: Movie, rootId: string): MovieUi { 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 { 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): { function normalizeCastMember(member: unknown): {
@@ -197,16 +207,24 @@ export function useMediaWebSocket() {
return next return next
} }
function normalizeMovie(movie: Movie): Movie { function normalizeMovie(movie: Movie, rootId: string | null): Movie {
return { return {
...movie, ...movie,
files: annotateFiles(movie.files, rootId),
info: normalizeInfo(movie.info), info: normalizeInfo(movie.info),
} }
} }
function normalizeSeries(series: Series): Series { function normalizeSeries(series: Series, rootId: string | null): Series {
return { return {
...series, ...series,
seasons: (series.seasons || []).map((season) => ({
...season,
episodes: (season.episodes || []).map((episode) => ({
...episode,
files: annotateFiles(episode.files, rootId),
})),
})),
info: normalizeInfo(series.info), info: normalizeInfo(series.info),
} }
} }
+2 -2
View File
@@ -491,7 +491,7 @@ async def play_media(root_id: str, request: Request):
"""Open a media file with the selected player.""" """Open a media file with the selected player."""
ctx = _get_context(root_id) ctx = _get_context(root_id)
req = msgspec.json.decode(await request.body(), type=PlayMediaRequest) 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(): if not file_path.exists():
raise HTTPException(status_code=404, detail=f"File not found: {req.file_path}") 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.""" """Open a folder in the system file explorer."""
ctx = _get_context(root_id) ctx = _get_context(root_id)
req = msgspec.json.decode(await request.body(), type=OpenFolderRequest) 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(): if not target_path.exists():
raise HTTPException( raise HTTPException(