music: fresh-on-refresh reindex, resync logs, and stop the junk-cover rebuild loop
- POST /reindex now runs the build to completion before responding (via a coalescing reindexNow), and GET /manifest ensures a fresh (debounced 3s) index first — so on-disk changes show up on a plain app refresh, not only via the explicit reindex sheet. - Log each resync in the officer-music sidecar (start + one-line summary, or a failure line). - Fix albums whose cover file isn't a decodable image (junk .jpg): the manifest cover flag and the skip check now reflect whether a cover was actually cached, so they settle to cover:false instead of rebuilding every run (and the app no longer 404s fetching a cover that was never there). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,8 @@ import type { SidecarCommand, SidecarEvent } from '../protocol';
|
||||
import { createSidecarConnector } from '../connect';
|
||||
import { streamAudioFile } from './stream-audio';
|
||||
import {
|
||||
buildMusicIndex,
|
||||
reindexNow,
|
||||
ensureIndexFresh,
|
||||
getIndexStatus,
|
||||
getManifest,
|
||||
albumVersion,
|
||||
@@ -31,7 +32,8 @@ const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||
//
|
||||
// GET /stream?path=<home-relative> audio with Range→206 (Content-Range/Length/Accept-Ranges)
|
||||
// + `X-Audio-Duration` (seconds, ffprobe). 400/404/416.
|
||||
// GET /manifest { version, generatedAt, albums: { "<rel>": { v, cover, tracks, disco? } } }
|
||||
// GET /manifest ensures a fresh index (debounced rebuild) then returns
|
||||
// { version, generatedAt, albums: { "<rel>": { v, cover, tracks, disco? } } }
|
||||
// GET /meta?path=<rel> album meta.json (IndexMeta). ETag: <v>; If-None-Match → 304.
|
||||
// GET /cover?path=<rel> compressed cover.jpg. ETag: <v>; If-None-Match → 304.
|
||||
// GET /discography?path=<artist rel> artist discography.json (only where manifest entry has
|
||||
@@ -39,7 +41,7 @@ const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
|
||||
// "<Type>" } }, Type ∈ Studio/Live/Compilation/Single/EP/…
|
||||
// ETag: <v>; If-None-Match → 304. Source: each artist folder's
|
||||
// _discography.md (normalized; the md itself is never modified).
|
||||
// POST /reindex start an async build; returns IndexStatus (running: true).
|
||||
// POST /reindex run the build to COMPLETION, then return the final IndexStatus.
|
||||
// GET /reindex/status IndexStatus snapshot.
|
||||
// GET /reindex/stream SSE. Triggers a build if idle (`?trigger=0` = watch-only).
|
||||
// `event: progress` (IndexStatus) throttled ~200ms, then one
|
||||
@@ -85,8 +87,11 @@ const server = Bun.serve({
|
||||
// ── Index build ──
|
||||
if (url.pathname === '/reindex') {
|
||||
if (req.method !== 'POST') return new Response('Method not allowed', { status: 405 });
|
||||
void buildMusicIndex(); // fire-and-forget; sets running=true synchronously before the first await
|
||||
return json(getIndexStatus());
|
||||
// Run the resync to completion, THEN respond — so the caller's manifest read right after is fresh.
|
||||
// Incremental builds are near-instant (unchanged albums skip by version stamp). reindexNow joins
|
||||
// an in-flight build rather than starting a second.
|
||||
const result = await reindexNow();
|
||||
return json(result);
|
||||
}
|
||||
if (url.pathname === '/reindex/status') return json(getIndexStatus());
|
||||
|
||||
@@ -94,7 +99,7 @@ const server = Bun.serve({
|
||||
// streams `progress` events until the build finishes, ending with a `done` event carrying the report.
|
||||
if (url.pathname === '/reindex/stream') {
|
||||
const trigger = url.searchParams.get('trigger') !== '0';
|
||||
if (trigger && !getIndexStatus().running) void buildMusicIndex();
|
||||
if (trigger) void reindexNow();
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new ReadableStream({
|
||||
@@ -139,7 +144,12 @@ const server = Bun.serve({
|
||||
}
|
||||
|
||||
// ── Sync surface ──
|
||||
if (url.pathname === '/manifest') return json(await getManifest());
|
||||
if (url.pathname === '/manifest') {
|
||||
// Every app refresh funnels through here, so rebuild the index first (debounced) — this is what
|
||||
// makes on-disk changes show up on a plain refresh, not only via the explicit reindex sheet.
|
||||
await ensureIndexFresh();
|
||||
return json(await getManifest());
|
||||
}
|
||||
|
||||
if (url.pathname === '/meta' || url.pathname === '/cover' || url.pathname === '/discography') {
|
||||
const rel = url.searchParams.get('path');
|
||||
|
||||
Reference in New Issue
Block a user