Fix root-relative playable path normalization
This commit is contained in:
@@ -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
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user