Fix: media index build never starts (blocking Jellyfin dependency)
The build endpoint had Depends(get_jellyfin_client) and Depends(get_user_id) which executed BEFORE the function body. If Jellyfin was unreachable, these raised HTTPException(503), the function never ran, and the worker was never started. The frontend mutation had no onError handler, so the failure was completely silent — the button briefly showed 'Building...' then reverted to 'Build index' with zero feedback. Backend fix: removed the Jellyfin dependencies from post_build_index. The worker subprocess resolves its own Jellyfin connection via _resolve_jellyfin(service_id) — the endpoint just needs to start the worker process. The libraries count starts at 0 and gets updated by the worker once it connects. Frontend fix: added onError to useBuildIndex that invalidates the status query (so the UI reflects the non-building state). MediaTab now displays the build error inline: 'Build failed: <message>' next to the button. 283 backend tests pass (updated build test for new no-dependency flow); 128 frontend tests pass; ruff/eslint clean.
This commit is contained in:
@@ -133,20 +133,27 @@ def get_index_status(index: MediaIndex = Depends(get_media_index)) -> dict[str,
|
|||||||
|
|
||||||
@router.post("/build", status_code=status.HTTP_202_ACCEPTED)
|
@router.post("/build", status_code=status.HTTP_202_ACCEPTED)
|
||||||
def post_build_index(
|
def post_build_index(
|
||||||
client: JellyfinClient = Depends(get_jellyfin_client),
|
|
||||||
user_id: str = Depends(get_user_id),
|
|
||||||
jellyfin_service_id: str | None = None,
|
jellyfin_service_id: str | None = None,
|
||||||
index: MediaIndex = Depends(get_media_index),
|
index: MediaIndex = Depends(get_media_index),
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Start a media index build in a subprocess worker."""
|
"""Start a media index build in a subprocess worker.
|
||||||
|
|
||||||
|
The worker resolves its own Jellyfin connection from the settings store.
|
||||||
|
We do NOT use Depends(get_jellyfin_client) here because the worker runs
|
||||||
|
in a separate process and needs to resolve the client itself. Validating
|
||||||
|
the connection here would fail if Jellyfin is briefly unreachable, even
|
||||||
|
though the build just needs to start the worker process.
|
||||||
|
"""
|
||||||
with _build_lock:
|
with _build_lock:
|
||||||
current_status = _clean_stale_build_state(index)
|
current_status = _clean_stale_build_state(index)
|
||||||
if current_status.build_running and _pid_is_alive(current_status.build_pid):
|
if current_status.build_running and _pid_is_alive(current_status.build_pid):
|
||||||
logger.warning("Media build already running pid=%s", current_status.build_pid)
|
logger.warning("Media build already running pid=%s", current_status.build_pid)
|
||||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Media index build already in progress")
|
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Media index build already in progress")
|
||||||
|
|
||||||
libraries = client.libraries(user_id)
|
logger.info(
|
||||||
logger.info("Starting media index build user_id=%s libraries=%s", user_id, len(libraries))
|
"Starting media index build service_id=%s",
|
||||||
|
jellyfin_service_id or "<default>",
|
||||||
|
)
|
||||||
process = _start_worker(index, jellyfin_service_id or "")
|
process = _start_worker(index, jellyfin_service_id or "")
|
||||||
_set_build_metadata(
|
_set_build_metadata(
|
||||||
index,
|
index,
|
||||||
@@ -159,7 +166,7 @@ def post_build_index(
|
|||||||
"build_items_total": 0,
|
"build_items_total": 0,
|
||||||
"build_current_library": "",
|
"build_current_library": "",
|
||||||
"build_library_index": 0,
|
"build_library_index": 0,
|
||||||
"build_libraries_total": len(libraries),
|
"build_libraries_total": 0,
|
||||||
"build_library_progress": None,
|
"build_library_progress": None,
|
||||||
"build_library_items_processed": 0,
|
"build_library_items_processed": 0,
|
||||||
"build_library_items_total": 0,
|
"build_library_items_total": 0,
|
||||||
|
|||||||
@@ -363,7 +363,7 @@ class TestMediaIndexApi:
|
|||||||
assert data["status"] == "started"
|
assert data["status"] == "started"
|
||||||
assert data["build_running"] is True
|
assert data["build_running"] is True
|
||||||
assert data["build_stage"] == "queued"
|
assert data["build_stage"] == "queued"
|
||||||
assert data["build_libraries_total"] == len(mock_jellyfin.libraries.return_value)
|
assert data["build_libraries_total"] == 0
|
||||||
assert data["build_pid"] == 4321
|
assert data["build_pid"] == 4321
|
||||||
start_worker.assert_called_once()
|
start_worker.assert_called_once()
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -237,8 +237,9 @@ export function WidgetConfigDialog({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open && editWidgetId) {
|
if (open && editWidgetId) {
|
||||||
// Search both owned widgets and referenced widgets.
|
// Search both owned widgets and referenced widgets.
|
||||||
const target = instances.find((w) => w.id === editWidgetId)
|
const target =
|
||||||
?? references.find((r) => r.widget.id === editWidgetId)?.widget;
|
instances.find((w) => w.id === editWidgetId) ??
|
||||||
|
references.find((r) => r.widget.id === editWidgetId)?.widget;
|
||||||
if (target) {
|
if (target) {
|
||||||
startEdit(target);
|
startEdit(target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,6 +54,10 @@ export function useBuildIndex(jellyfinServiceId?: string) {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
invalidateMedia(queryClient);
|
invalidateMedia(queryClient);
|
||||||
},
|
},
|
||||||
|
onError: () => {
|
||||||
|
// Invalidate status so the UI reflects the current (non-building) state.
|
||||||
|
invalidateMedia(queryClient);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -352,6 +352,11 @@ export function MediaTab({ instance }: { instance: ServiceInstance }) {
|
|||||||
>
|
>
|
||||||
{buildIndex.isPending || buildRunning ? "Building..." : "Build index"}
|
{buildIndex.isPending || buildRunning ? "Building..." : "Build index"}
|
||||||
</Button>
|
</Button>
|
||||||
|
{buildIndex.isError ? (
|
||||||
|
<span className="text-sm text-destructive">
|
||||||
|
Build failed: {buildIndex.error instanceof Error ? buildIndex.error.message : "Unknown error"}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
{buildRunning && (
|
{buildRunning && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user